or = character before empty string ''.
*
* @since 5.2
*
* @param string $sql SQL queries.
* @return string Filtered SQL queries.
*/
function db_sql_filter( $sql )
{
// Replace empty strings ('') with NULL values.
if ( stripos( $sql, 'INSERT INTO ' ) !== false )
{
// Check for ( or , character before empty string ''.
$sql = preg_replace( "/([,\(])[\r\n\t ]*''(?!')/", '\\1NULL', $sql );
}
// Check for <> or = character before empty string ''.
$sql = preg_replace( "/(<>|=)[\r\n\t ]*''(?!'|\w|\d)/", '\\1NULL', $sql );
/**
* IS NOT NULL cases
*
* Replace <>NULL & !=NULL with IS NOT NULL
*
* @link http://www.postgresql.org/docs/current/static/functions-comparison.html
*/
$sql = str_ireplace(
[ '<>NULL', '!=NULL' ],
' IS NOT NULL',
$sql
);
return $sql;
}
/**
* This function connects, and does the passed query, then returns a result resource
* Not receiving the return == unusable search.
*
* @example $processable_results = DBQuery( "SELECT * FROM students" );
*
* @uses db_sql_filter()
* @uses db_query()
* @see DBGet()
*
* @since 4.3 Do DBQuery after action hook.
*
* @param string $sql SQL statement.
* @return resource PostgreSQL result resource
*/
function DBQuery( $sql )
{
$sql = db_sql_filter( $sql );
$result = db_query( $sql );
// Do DBQuery after action hook.
do_action( 'database.inc.php|dbquery_after', [ $sql, $result ] );
return $result;
}
/**
* Return next row
*
* @since 10.0 Add MySQL support
* @since 10.2.1 Fix error mysqli_fetch_assoc(): Argument #1 must be of type mysqli_result, null given
*
* @global $DatabaseType Database type: mysql or postgresql
*
* @param resource PostgreSQL result resource $result Result.
* @return array|bool Next row in result set or false.
*/
function db_fetch_row( $result )
{
global $DatabaseType;
$return = false;
if ( $DatabaseType === 'mysql'
&& $result instanceof mysqli_result )
{
$return = mysqli_fetch_assoc( $result );
}
else
{
$return = pg_fetch_array( $result, null, PGSQL_ASSOC );
}
return is_array( $return ) ? array_change_key_case( $return, CASE_UPPER ) : $return;
}
/**
* Returns code to go into SQL statement for accessing the next value of a sequence
*
* @deprecated since 9.2.1 Use DBLastInsertID() instead
*
* @global $DatabaseType Database type: mysql or postgresql
*
* @param string $seqname PostgreSQL sequence name.
* @return sting nextval code
*/
function db_seq_nextval( $seqname )
{
global $DatabaseType;
if ( $DatabaseType === 'mysql' )
{
return DBSeqNextID( $seqname );
}
return "nextval('" . DBEscapeString( $seqname ) . "')";
}
/**
* DB Sequence Next ID
*
* @deprecated since 9.2.1 Use DBLastInsertID() instead (with the exception of student ID)
*
* @since 11.0.1 MySQL fix infinite loop, emulate PostgreSQL's nextval()
* @since 11.2.4 MySQL 8+ fix infinite loop due to cached AUTO_INCREMENT
*
* @example $id = DBSeqNextID( 'people_person_id_seq' );
*
* @global $DatabaseType Database type: mysql or postgresql
*
* @param string $seqname Sequence name (or table name for MySQL).
*
* @return int Next ID.
*/
function DBSeqNextID( $seqname )
{
global $DatabaseType;
static $auto_increment = [];
if ( $DatabaseType === 'mysql' )
{
if ( empty( $auto_increment ) // Set only once per session.
&& DBGetOne( "SHOW VARIABLES LIKE 'information_schema_stats_expiry';" ) )
{
/**
* Do NOT cache table statistics cache in MySQL 8+
*
* Note: only for MySQL, MariaDB does not have this system variable
*
* @link https://stackoverflow.com/questions/51283195/wrong-auto-increment-value-on-select
*
* @since 11.2.4 MySQL 8+ fix infinite loop due to cached AUTO_INCREMENT
*/
DBQuery( "SET @@SESSION.information_schema_stats_expiry = 0;" );
}
// Try to get table name from PostgreSQL sequence name by removing '_id_seq'.
// Will fail if PRIMARY KEY / serial column name is != id.
$table_name = str_ireplace( [ '_id_seq', '_seq' ], '', $seqname );
$seq_next_RET = db_fetch_row( DBQuery( "SELECT AUTO_INCREMENT
FROM information_schema.tables
WHERE table_schema=DATABASE()
AND table_name='" . mb_strtolower( DBEscapeString( $table_name ) ) . "'" ) );
// Return 0 if query failed. 0 in a MySQL query is valid for an AUTO_INCREMENT ID column.
$seq_next_id = empty( $seq_next_RET ) ? 0 : $seq_next_RET['AUTO_INCREMENT'];
if ( $seq_next_id )
{
if ( empty( $auto_increment[ $table_name ] ) )
{
$auto_increment[ $table_name ] = $seq_next_id;
}
elseif ( $auto_increment[ $table_name ] == $seq_next_id )
{
/**
* Manually increment AUTO_INCREMENT
*
* @since 11.0.1 MySQL fix infinite loop, emulate PostgreSQL's nextval()
*/
$seq_next_id++;
DBQuery( "ALTER TABLE " . DBEscapeIdentifier( $table_name ) . "
AUTO_INCREMENT=" . (int) $seq_next_id );
$auto_increment[ $table_name ] = $seq_next_id;
}
else
{
unset( $auto_increment[ $table_name ] );
}
}
}
else
{
$seq_next_RET = db_fetch_row( DBQuery( "SELECT " . db_seq_nextval( $seqname ) . ' AS ID' ) );
$seq_next_id = $seq_next_RET['ID'];
}
return $seq_next_id;
}
/**
* DB Last Inserted ID
*
* @since 9.2.1
* @since 10.0 Add MySQL support
*
* @link https://stackoverflow.com/questions/2944297/postgresql-function-for-last-inserted-id
*
* @return int Last ID.
*/
function DBLastInsertID()
{
global $DatabaseType;
$last_insert_id_function = $DatabaseType === 'mysql' ? 'LAST_INSERT_ID()' : 'LASTVAL()';
$last_insert_id_RET = db_fetch_row( DBQuery( "SELECT " . $last_insert_id_function . ' AS ID' ) );
return $last_insert_id_RET['ID'];
}
/**
* Start transaction
*
* @return void
*/
function db_trans_start()
{
db_query( 'BEGIN;' );
}
/**
* Run query on transaction -- if failure, runs rollback
*
* @since 5.2 $connection param removed.
*
* @param string $sql SQL statement.
* @return PostgreSQL result resource
*/
function db_trans_query( $sql, $show_error = true )
{
$sql = db_sql_filter( $sql );
// Use @ error control operator to silence PHP Warning in case of failure.
$result = @db_query( $sql, $show_error );
if ( $result === false )
{
// Rollback commands.
db_trans_rollback();
}
return $result;
}
/**
* Commit changes
*
* @param PostgreSQL connection resource $connection Connection. DEPRECATED.
* @return void
*/
function db_trans_commit()
{
db_query( 'COMMIT;' );
}
/**
* Rollback changes
*
* @since 5.2
*
* @return void
*/
function db_trans_rollback()
{
db_query( 'ROLLBACK;' );
}
/**
* Dry run query on transaction -- rollback anyway
* Useful to check first if foreign key constraints are preventing DELETE.
*
* @since 5.2
*
* @example $can_delete = DBTransDryRun( UserDeleteSQL( UserStaffID() ) );
*
* @param string $sql SQL statement.
* @return bool Can run the queries in the transaction without error?
*/
function DBTransDryRun( $sql )
{
db_trans_start();
$result = db_trans_query( $sql, false );
if ( $result !== false )
{
// Rollback transaction anyway.
db_trans_rollback();
}
return $result !== false;
}
/**
* Generate CASE-WHEN condition
*
* @example db_case( [ 'FAILED_LOGIN', "''", '1', 'FAILED_LOGIN+1' ] )
* will return ' CASE WHEN FAILED_LOGIN IS NULL THEN 1 ELSE FAILED_LOGIN+1 END '
*
* @param array $array [ Column, IS, THEN, ELSE ]
* @return string CASE-WHEN condition
*/
function db_case( $array )
{
$counter = 0;
$array_count = count( $array );
$string = ' CASE WHEN ' . $array[0] . ' =';
$counter++;
$arr_count = count( $array );
for ( $i = 1; $i < $arr_count; $i++ )
{
$value = $array[$i];
if ( $value == "''"
&& mb_substr( $string, -1 ) == '=' )
{
$value = ' IS NULL';
$string = mb_substr( $string, 0, -1 );
}
$string .= $value;
if ( $counter == ( $array_count - 2 )
&& $array_count % 2 == 0 )
{
$string .= ' ELSE ';
}
elseif ( $counter == ( $array_count - 1 ) )
{
$string .= ' END ';
}
elseif ( $counter % 2 == 0 )
{
$string .= ' WHEN ' . $array[0] . '=';
}
elseif ( $counter % 2 == 1 )
{
$string .= ' THEN ';
}
$counter++;
}
return $string;
}
/**
* Returns an array with the field names for the specified table as key with subkeys
* of SIZE, TYPE, SCALE and NULL. TYPE: varchar, numeric, etc.
*
* @since 10.0 Add MySQL support
*
* @global $DatabaseType Database type: mysql or postgresql
*
* @param string $table DB Table.
* @return array Table properties
*/
function db_properties( $table )
{
global $DatabaseType;
if ( $DatabaseType === 'mysql' )
{
$sql = "SHOW COLUMNS FROM " . DBEscapeIdentifier( $table );
}
else
{
$sql = "SELECT a.attnum,a.attname AS field,t.typname AS type,
a.attlen AS length,a.atttypmod AS lengthvar,a.attnotnull AS notnull
FROM pg_class c,pg_attribute a,pg_type t
WHERE c.relname='" . mb_strtolower( DBEscapeString( $table ) ) . "'
AND a.attnum > 0 AND a.attrelid = c.oid AND a.atttypid = t.oid
ORDER BY a.attnum";
}
$result = DBQuery( $sql );
while ( $row = db_fetch_row( $result ) )
{
$field = mb_strtoupper( $row['FIELD'] );
if ( $DatabaseType === 'mysql' )
{
$open_parens_pos = mb_strpos( $row['TYPE'], '(' );
$properties[$field]['TYPE'] = mb_strtoupper(
mb_substr( $row['TYPE'], 0, $open_parens_pos ? $open_parens_pos : null )
);
if ( ! $pos = mb_strpos( $row['TYPE'], ',' ) )
{
$pos = $open_parens_pos;
}
else
{
$properties[$field]['SCALE'] = mb_substr( $row['TYPE'], $pos + 1, -1 );
}
$properties[$field]['SIZE'] = '';
if ( $open_parens_pos )
{
$properties[$field]['SIZE'] = mb_substr(
$row['TYPE'],
$open_parens_pos + 1,
( $pos !== $open_parens_pos ? $pos - $open_parens_pos -1 : -1 )
);
}
$properties[$field]['NULL'] = $row['NULL'] != '' && $row['NULL'] !== 'NO' ? 'Y' : 'N';
continue;
}
$properties[$field]['TYPE'] = mb_strtoupper( $row['TYPE'] );
if ( mb_strtoupper( $row['TYPE'] ) == 'NUMERIC' )
{
$properties[$field]['SIZE'] = ( $row['LENGTHVAR'] >> 16 ) & 0xffff;
$properties[$field]['SCALE'] = ( $row['LENGTHVAR'] - 4 ) & 0xffff;
}
else
{
if ( $row['LENGTH'] > 0 )
{
$properties[$field]['SIZE'] = $row['LENGTH'];
}
elseif ( $row['LENGTHVAR'] > 0 )
{
$properties[$field]['SIZE'] = $row['LENGTHVAR'] - 4;
}
}
$properties[$field]['NULL'] = $row['NOTNULL'] === 't' ? 'N' : 'Y';
}
return $properties;
}
/**
* Show SQL error message
* Send notification email if `$RosarioNotifyAddress` or `$RosarioErrorsAddress` set
*
* @global string $RosarioNotifyAddress or $RosarioErrorsAddress email set in config.inc.php file
* @since 4.0 Uses ErrorSendEmail()
* @since 4.6 Show SQL query.
*
* @param string $sql SQL statement.
* @param string $failnote Failure Notice.
* @param string $additional Additional Information.
*/
function db_show_error( $sql, $failnote, $additional = '' )
{
global $RosarioNotifyAddress,
$RosarioErrorsAddress;
// TRANSLATION: do NOT translate these since error messages need to stay in English for technical support.
?>
|
|
Date |
Failure Notice |
|
SQL query |
|