2006-08-18 22:56:08 +00:00
< ? php // $Id$
///////////////////////////////////////////////////////////////////////////
// //
// NOTICE OF COPYRIGHT //
// //
// Moodle - Modular Object-Oriented Dynamic Learning Environment //
// http://moodle.com //
// //
// Copyright (C) 2001-3001 Martin Dougiamas http://dougiamas.com //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation; either version 2 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License for more details: //
// //
// http://www.gnu.org/copyleft/gpl.html //
// //
///////////////////////////////////////////////////////////////////////////
2006-09-20 21:00:45 +00:00
/// This library contains all the Data Manipulation Language (DML) functions
2006-08-18 22:56:08 +00:00
/// used to interact with the DB. All the dunctions in this library must be
/// generic and work against the major number of RDBMS possible. This is the
/// list of currently supported and tested DBs: mysql, postresql, mssql, oracle
2006-08-19 02:58:07 +00:00
/// This library is automatically included by Moodle core so you never need to
/// include it yourself.
2006-08-18 22:56:08 +00:00
/// For more info about the functions available in this library, please visit:
/// http://docs.moodle.org/en/DML_functions
/// (feel free to modify, improve and document such page, thanks!)
/// GLOBAL CONSTANTS /////////////////////////////////////////////////////////
$empty_rs_cache = array (); // Keeps copies of the recordsets used in one invocation
/// FUNCTIONS FOR DATABASE HANDLING ////////////////////////////////
/**
* Execute a given sql command string
*
* Completely general function - it just runs some SQL and reports success .
*
* @ uses $db
* @ param string $command The sql string you wish to be executed .
* @ param bool $feedback Set this argument to true if the results generated should be printed . Default is true .
* @ return string
*/
function execute_sql ( $command , $feedback = true ) {
/// Completely general function - it just runs some SQL and reports success.
global $db , $CFG ;
$olddebug = $db -> debug ;
if ( ! $feedback ) {
$db -> debug = false ;
}
$empty_rs_cache = array (); // Clear out the cache, just in case changes were made to table structures
if ( defined ( 'MDL_PERFDB' )) { global $PERF ; $PERF -> dbqueries ++ ; };
$result = $db -> Execute ( $command );
$db -> debug = $olddebug ;
if ( $result ) {
if ( $feedback ) {
notify ( get_string ( 'success' ), 'notifysuccess' );
}
return true ;
} else {
if ( $feedback ) {
notify ( '<strong>' . get_string ( 'error' ) . '</strong>' );
}
if ( ! empty ( $CFG -> dblogerror )) {
$debug = array_shift ( debug_backtrace ());
error_log ( " SQL " . $db -> ErrorMsg () . " in { $debug [ 'file' ] } on line { $debug [ 'line' ] } . STATEMENT: $command " );
}
return false ;
}
}
2006-09-22 20:05:44 +00:00
2006-08-18 22:56:08 +00:00
/**
* on DBs that support it , switch to transaction mode and begin a transaction
2006-09-22 20:05:44 +00:00
* you ' ll need to ensure you call commit_sql () or your changes * will * be lost .
*
* Now using ADOdb standard transactions . Some day , we should switch to
* Smart Transactions ( http :// phplens . com / adodb / tutorial . smart . transactions . html )
* as they autodetect errors and are nestable and easier to write
*
2006-08-18 22:56:08 +00:00
* this is _very_ useful for massive updates
*/
function begin_sql () {
2006-09-22 20:05:44 +00:00
global $db ;
$db -> BeginTrans ();
2006-08-18 22:56:08 +00:00
return true ;
}
2006-09-22 20:05:44 +00:00
2006-08-18 22:56:08 +00:00
/**
2006-09-20 21:00:45 +00:00
* on DBs that support it , commit the transaction
2006-09-22 20:05:44 +00:00
*
* Now using ADOdb standard transactions . Some day , we should switch to
* Smart Transactions ( http :// phplens . com / adodb / tutorial . smart . transactions . html )
* as they autodetect errors and are nestable and easier to write
2006-08-18 22:56:08 +00:00
*/
2006-09-22 20:05:44 +00:00
function commit_sql () {
global $db ;
$db -> CommitTrans ();
2006-08-18 22:56:08 +00:00
return true ;
}
2006-09-22 20:05:44 +00:00
/**
* on DBs that support it , rollback the transaction
*
* Now using ADOdb standard transactions . Some day , we should switch to
* Smart Transactions ( http :// phplens . com / adodb / tutorial . smart . transactions . html )
* as they autodetect errors and are nestable and easier to write
*/
function rollback_sql () {
global $db ;
2006-08-18 22:56:08 +00:00
2006-09-22 20:05:44 +00:00
$db -> RollbackTrans ();
return true ;
}
2006-08-18 22:56:08 +00:00
/**
* returns db specific uppercase function
2006-09-04 22:40:42 +00:00
* @ deprecated Moodle 1.7 because all the RDBMS use upper ()
2006-08-18 22:56:08 +00:00
*/
function db_uppercase () {
2006-09-04 22:40:42 +00:00
return " upper " ;
2006-08-18 22:56:08 +00:00
}
/**
* returns db specific lowercase function
2006-09-04 22:40:42 +00:00
* @ deprecated Moodle 1.7 because all the RDBMS use lower ()
2006-08-18 22:56:08 +00:00
*/
function db_lowercase () {
2006-09-04 22:40:42 +00:00
return " lower " ;
2006-08-18 22:56:08 +00:00
}
/**
* Run an arbitrary sequence of semicolon - delimited SQL commands
*
* Assumes that the input text ( file or string ) consists of
* a number of SQL statements ENDING WITH SEMICOLONS . The
* semicolons MUST be the last character in a line .
* Lines that are blank or that start with " # " or " -- " ( postgres ) are ignored .
* Only tested with mysql dump files ( mysqldump - p - d moodle )
*
* @ uses $CFG
* @ param string $sqlfile The path where a file with sql commands can be found on the server .
2006-09-20 21:00:45 +00:00
* @ param string $sqlstring If no path is supplied then a string with semicolon delimited sql
2006-08-18 22:56:08 +00:00
* commands can be supplied in this argument .
* @ return bool Returns true if databse was modified successfully .
*/
function modify_database ( $sqlfile = '' , $sqlstring = '' ) {
global $CFG ;
$success = true ; // Let's be optimistic
if ( ! empty ( $sqlfile )) {
if ( ! is_readable ( $sqlfile )) {
$success = false ;
echo '<p>Tried to modify database, but "' . $sqlfile . '" doesn\'t exist!</p>' ;
return $success ;
} else {
$lines = file ( $sqlfile );
}
} else {
$sqlstring = trim ( $sqlstring );
if ( $sqlstring { strlen ( $sqlstring ) - 1 } != " ; " ) {
$sqlstring .= " ; " ; // add it in if it's not there.
}
$lines [] = $sqlstring ;
}
$command = '' ;
foreach ( $lines as $line ) {
$line = rtrim ( $line );
$length = strlen ( $line );
if ( $length and $line [ 0 ] <> '#' and $line [ 0 ] . $line [ 1 ] <> '--' ) {
if ( substr ( $line , $length - 1 , 1 ) == ';' ) {
$line = substr ( $line , 0 , $length - 1 ); // strip ;
$command .= $line ;
$command = str_replace ( 'prefix_' , $CFG -> prefix , $command ); // Table prefixes
if ( ! execute_sql ( $command )) {
$success = false ;
}
$command = '' ;
} else {
$command .= $line ;
}
}
}
return $success ;
}
/// GENERIC FUNCTIONS TO CHECK AND COUNT RECORDS ////////////////////////////////////////
/**
* Test whether a record exists in a table where all the given fields match the given values .
*
* The record to test is specified by giving up to three fields that must
* equal the corresponding values .
*
* @ uses $CFG
* @ param string $table The table to check .
* @ param string $field1 the first field to check ( optional ) .
* @ param string $value1 the value field1 must have ( requred if field1 is given , else optional ) .
* @ param string $field2 the second field to check ( optional ) .
* @ param string $value2 the value field2 must have ( requred if field2 is given , else optional ) .
* @ param string $field3 the third field to check ( optional ) .
* @ param string $value3 the value field3 must have ( requred if field3 is given , else optional ) .
* @ return bool true if a matching record exists , else false .
*/
function record_exists ( $table , $field1 = '' , $value1 = '' , $field2 = '' , $value2 = '' , $field3 = '' , $value3 = '' ) {
global $CFG ;
$select = where_clause ( $field1 , $value1 , $field2 , $value2 , $field3 , $value3 );
2006-09-01 18:33:19 +00:00
return record_exists_sql ( 'SELECT * FROM ' . $CFG -> prefix . $table . ' ' . $select );
2006-08-18 22:56:08 +00:00
}
/**
* Test whether any records exists in a table which match a particular WHERE clause .
*
* @ uses $CFG
* @ param string $table The database table to be checked against .
* @ param string $select A fragment of SQL to be used in a WHERE clause in the SQL call .
* @ return bool true if a matching record exists , else false .
*/
function record_exists_select ( $table , $select = '' ) {
global $CFG ;
if ( $select ) {
$select = 'WHERE ' . $select ;
}
return record_exists_sql ( 'SELECT * FROM ' . $CFG -> prefix . $table . ' ' . $select );
}
/**
* Test whether a SQL SELECT statement returns any records .
*
* This function returns true if the SQL statement executes
* without any errors and returns at least one record .
*
* @ param string $sql The SQL statement to execute .
* @ return bool true if the SQL executes without errors and returns at least one record .
*/
function record_exists_sql ( $sql ) {
2006-09-01 18:33:19 +00:00
$limitfrom = 0 ; /// Number of records to skip
$limitnum = 1 ; /// Number of records to retrieve
$rs = get_recordset_sql ( $sql , $limitfrom , $limitnum );
2006-08-18 22:56:08 +00:00
if ( $rs && $rs -> RecordCount () > 0 ) {
return true ;
} else {
return false ;
}
}
/**
* Count the records in a table where all the given fields match the given values .
*
* @ uses $CFG
* @ param string $table The table to query .
* @ param string $field1 the first field to check ( optional ) .
* @ param string $value1 the value field1 must have ( requred if field1 is given , else optional ) .
* @ param string $field2 the second field to check ( optional ) .
* @ param string $value2 the value field2 must have ( requred if field2 is given , else optional ) .
* @ param string $field3 the third field to check ( optional ) .
* @ param string $value3 the value field3 must have ( requred if field3 is given , else optional ) .
* @ return int The count of records returned from the specified criteria .
*/
function count_records ( $table , $field1 = '' , $value1 = '' , $field2 = '' , $value2 = '' , $field3 = '' , $value3 = '' ) {
global $CFG ;
$select = where_clause ( $field1 , $value1 , $field2 , $value2 , $field3 , $value3 );
return count_records_sql ( 'SELECT COUNT(*) FROM ' . $CFG -> prefix . $table . ' ' . $select );
}
/**
* Count the records in a table which match a particular WHERE clause .
*
* @ uses $CFG
* @ param string $table The database table to be checked against .
* @ param string $select A fragment of SQL to be used in a WHERE clause in the SQL call .
* @ param string $countitem The count string to be used in the SQL call . Default is COUNT ( * ) .
* @ return int The count of records returned from the specified criteria .
*/
function count_records_select ( $table , $select = '' , $countitem = 'COUNT(*)' ) {
global $CFG ;
if ( $select ) {
$select = 'WHERE ' . $select ;
}
return count_records_sql ( 'SELECT ' . $countitem . ' FROM ' . $CFG -> prefix . $table . ' ' . $select );
}
/**
* Get the result of a SQL SELECT COUNT ( ... ) query .
*
* Given a query that counts rows , return that count . ( In fact ,
* given any query , return the first field of the first record
* returned . However , this method should only be used for the
* intended purpose . ) If an error occurrs , 0 is returned .
*
* @ uses $CFG
* @ uses $db
* @ param string $sql The SQL string you wish to be executed .
* @ return int the count . If an error occurrs , 0 is returned .
*/
function count_records_sql ( $sql ) {
$rs = get_recordset_sql ( $sql );
2006-09-20 21:00:45 +00:00
2006-08-18 22:56:08 +00:00
if ( $rs ) {
2006-09-20 11:26:47 +00:00
return reset ( $rs -> fields );
2006-08-18 22:56:08 +00:00
} else {
2006-09-20 21:00:45 +00:00
return 0 ;
2006-08-18 22:56:08 +00:00
}
}
/// GENERIC FUNCTIONS TO GET, INSERT, OR UPDATE DATA ///////////////////////////////////
/**
* Get a single record as an object
*
* @ uses $CFG
* @ param string $table The table to select from .
* @ param string $field1 the first field to check ( optional ) .
* @ param string $value1 the value field1 must have ( requred if field1 is given , else optional ) .
* @ param string $field2 the second field to check ( optional ) .
* @ param string $value2 the value field2 must have ( requred if field2 is given , else optional ) .
* @ param string $field3 the third field to check ( optional ) .
* @ param string $value3 the value field3 must have ( requred if field3 is given , else optional ) .
* @ return mixed a fieldset object containing the first mathcing record , or false if none found .
*/
function get_record ( $table , $field1 , $value1 , $field2 = '' , $value2 = '' , $field3 = '' , $value3 = '' , $fields = '*' ) {
global $CFG ;
$select = where_clause ( $field1 , $value1 , $field2 , $value2 , $field3 , $value3 );
return get_record_sql ( 'SELECT ' . $fields . ' FROM ' . $CFG -> prefix . $table . ' ' . $select );
}
/**
* Get a single record as an object using an SQL statement
*
* The SQL statement should normally only return one record . In debug mode
* you will get a warning if more record is returned ( unless you
* set $expectmultiple to true ) . In non - debug mode , it just returns
2006-09-20 21:00:45 +00:00
* the first record .
2006-08-18 22:56:08 +00:00
*
* @ uses $CFG
* @ uses $db
* @ param string $sql The SQL string you wish to be executed , should normally only return one record .
2006-09-20 21:00:45 +00:00
* @ param bool $expectmultiple If the SQL cannot be written to conviniently return just one record ,
2006-08-18 22:56:08 +00:00
* set this to true to hide the debug message .
* @ param bool $nolimit sometimes appending ' LIMIT 1' to the SQL causes an error . Set this to true
* to stop your SQL being modified . This argument should probably be deprecated .
* @ return Found record as object . False if not found or error
*/
function get_record_sql ( $sql , $expectmultiple = false , $nolimit = false ) {
global $CFG ;
2006-08-20 09:37:56 +00:00
/// Default situation
$limitfrom = 0 ; /// Number of records to skip
$limitnum = 1 ; /// Number of records to retrieve
/// Only a few uses of the 2nd and 3rd parameter have been found
/// I think that we should avoid to use them completely, one
/// record is one record, and everything else should return error.
2006-09-20 21:00:45 +00:00
/// So the proposal is to change all the uses, (4-5 inside Moodle
2006-08-20 09:37:56 +00:00
/// Core), drop them from the definition and delete the next two
/// "if" sentences. (eloy, 2006-08-19)
2006-08-18 22:56:08 +00:00
if ( $nolimit ) {
2006-08-20 09:37:56 +00:00
$limitfrom = 0 ;
$limitnum = 0 ;
2006-08-18 22:56:08 +00:00
} else if ( $expectmultiple ) {
2006-08-20 09:37:56 +00:00
$limitfrom = 0 ;
$limitnum = 1 ;
2006-09-13 09:45:07 +00:00
} else if ( debugging ()) {
2006-08-18 22:56:08 +00:00
// Debugging mode - don't use a limit of 1, but do change the SQL, because sometimes that
2006-09-20 21:00:45 +00:00
// causes errors, and in non-debug mode you don't see the error message and it is
2006-08-18 22:56:08 +00:00
// impossible to know what's wrong.
2006-08-20 09:37:56 +00:00
$limitfrom = 0 ;
$limitnum = 100 ;
2006-08-18 22:56:08 +00:00
}
2006-08-20 09:37:56 +00:00
if ( ! $rs = get_recordset_sql ( $sql , $limitfrom , $limitnum )) {
2006-09-20 21:00:45 +00:00
return false ;
2006-08-18 22:56:08 +00:00
}
2006-09-20 21:00:45 +00:00
2006-08-18 22:56:08 +00:00
$recordcount = $rs -> RecordCount ();
if ( $recordcount == 0 ) { // Found no records
2006-09-20 21:00:45 +00:00
return false ;
2006-08-18 22:56:08 +00:00
} else if ( $recordcount == 1 ) { // Found one record
2006-09-07 23:48:48 +00:00
/// DIRTY HACK to retrieve all the ' ' (1 space) fields converted back
/// to '' (empty string) for Oracle. It's the only way to work with
/// all those NOT NULL DEFAULT '' fields until we definetively delete them
if ( $CFG -> dbtype == 'oci8po' ) {
array_walk ( $rs -> fields , 'onespace2empty' );
}
/// End od DIRTY HACK
2006-08-18 22:56:08 +00:00
return ( object ) $rs -> fields ;
} else { // Error: found more than one record
notify ( 'Error: Turn off debugging to hide this error.' );
2006-08-20 09:37:56 +00:00
notify ( $sql . '(with limits ' . $limitfrom . ', ' . $limitnum . ')' );
2006-08-18 22:56:08 +00:00
if ( $records = $rs -> GetAssoc ( true )) {
notify ( 'Found more than one record in get_record_sql !' );
print_object ( $records );
} else {
notify ( 'Very strange error in get_record_sql !' );
print_object ( $rs );
}
print_continue ( " $CFG->wwwroot / $CFG->admin /config.php " );
}
}
/**
* Gets one record from a table , as an object
*
* @ uses $CFG
* @ param string $table The database table to be checked against .
* @ param string $select A fragment of SQL to be used in a where clause in the SQL call .
* @ param string $fields A comma separated list of fields to be returned from the chosen table .
* @ return object | false Returns an array of found records ( as objects ) or false if no records or error occured .
*/
function get_record_select ( $table , $select = '' , $fields = '*' ) {
global $CFG ;
if ( $select ) {
$select = 'WHERE ' . $select ;
}
return get_record_sql ( 'SELECT ' . $fields . ' FROM ' . $CFG -> prefix . $table . ' ' . $select );
}
/**
* Get a number of records as an ADODB RecordSet .
*
* Selects records from the table $table .
2006-09-20 21:00:45 +00:00
*
2006-08-18 22:56:08 +00:00
* If specified , only records where the field $field has value $value are retured .
2006-09-20 21:00:45 +00:00
*
2006-08-18 22:56:08 +00:00
* If specified , the results will be sorted as specified by $sort . This
* is added to the SQL as " ORDER BY $sort " . Example values of $sort
* mightbe " time ASC " or " time DESC " .
2006-09-20 21:00:45 +00:00
*
2006-08-18 22:56:08 +00:00
* If $fields is specified , only those fields are returned .
*
2006-09-20 21:00:45 +00:00
* This function is internal to datalib , and should NEVER should be called directly
2006-08-18 22:56:08 +00:00
* from general Moodle scripts . Use get_record , get_records etc .
2006-09-20 21:00:45 +00:00
*
2006-08-18 22:56:08 +00:00
* If you only want some of the records , specify $limitfrom and $limitnum .
* The query will skip the first $limitfrom records ( according to the sort
* order ) and then return the next $limitnum records . If either of $limitfrom
* or $limitnum is specified , both must be present .
2006-09-20 21:00:45 +00:00
*
2006-08-18 22:56:08 +00:00
* The return value is an ADODB RecordSet object
* @ link http :// phplens . com / adodb / reference . functions . adorecordset . html
* if the query succeeds . If an error occurrs , false is returned .
*
* @ param string $table the table to query .
* @ param string $field a field to check ( optional ) .
* @ param string $value the value the field must have ( requred if field1 is given , else optional ) .
* @ param string $sort an order to sort the results in ( optional , a valid SQL ORDER BY parameter ) .
* @ param string $fields a comma separated list of fields to return ( optional , by default all fields are returned ) .
* @ param int $limitfrom return a subset of records , starting at this point ( optional , required if $limitnum is set ) .
* @ param int $limitnum return a subset comprising this many records ( optional , required if $limitfrom is set ) .
* @ return mixed an ADODB RecordSet object , or false if an error occured .
*/
function get_recordset ( $table , $field = '' , $value = '' , $sort = '' , $fields = '*' , $limitfrom = '' , $limitnum = '' ) {
if ( $field ) {
$select = " $field = ' $value ' " ;
} else {
$select = '' ;
}
2006-09-20 21:00:45 +00:00
2006-08-18 22:56:08 +00:00
return get_recordset_select ( $table , $select , $sort , $fields , $limitfrom , $limitnum );
}
/**
* Get a number of records as an ADODB RecordSet .
*
* If given , $select is used as the SELECT parameter in the SQL query ,
* otherwise all records from the table are returned .
2006-09-20 21:00:45 +00:00
*
* Other arguments and the return type as for @ see function get_recordset .
2006-08-18 22:56:08 +00:00
*
* @ uses $CFG
* @ param string $table the table to query .
* @ param string $select A fragment of SQL to be used in a where clause in the SQL call .
* @ param string $sort an order to sort the results in ( optional , a valid SQL ORDER BY parameter ) .
* @ param string $fields a comma separated list of fields to return ( optional , by default all fields are returned ) .
* @ param int $limitfrom return a subset of records , starting at this point ( optional , required if $limitnum is set ) .
* @ param int $limitnum return a subset comprising this many records ( optional , required if $limitfrom is set ) .
* @ return mixed an ADODB RecordSet object , or false if an error occured .
*/
function get_recordset_select ( $table , $select = '' , $sort = '' , $fields = '*' , $limitfrom = '' , $limitnum = '' ) {
global $CFG ;
if ( $select ) {
$select = ' WHERE ' . $select ;
}
if ( $limitfrom !== '' ) {
$limit = sql_paging_limit ( $limitfrom , $limitnum );
} else {
$limit = '' ;
}
if ( $sort ) {
$sort = ' ORDER BY ' . $sort ;
}
return get_recordset_sql ( 'SELECT ' . $fields . ' FROM ' . $CFG -> prefix . $table . $select . $sort . ' ' . $limit );
}
/**
* Get a number of records as an ADODB RecordSet .
*
* Only records where $field takes one of the values $values are returned .
* $values should be a comma - separated list of values , for example " 4,5,6,10 "
* or " 'foo','bar','baz' " .
2006-09-20 21:00:45 +00:00
*
* Other arguments and the return type as for @ see function get_recordset .
2006-08-18 22:56:08 +00:00
*
* @ param string $table the table to query .
* @ param string $field a field to check ( optional ) .
* @ param string $values comma separated list of values the field must have ( requred if field is given , else optional ) .
* @ param string $sort an order to sort the results in ( optional , a valid SQL ORDER BY parameter ) .
* @ param string $fields a comma separated list of fields to return ( optional , by default all fields are returned ) .
* @ param int $limitfrom return a subset of records , starting at this point ( optional , required if $limitnum is set ) .
* @ param int $limitnum return a subset comprising this many records ( optional , required if $limitfrom is set ) .
* @ return mixed an ADODB RecordSet object , or false if an error occured .
*/
function get_recordset_list ( $table , $field = '' , $values = '' , $sort = '' , $fields = '*' , $limitfrom = '' , $limitnum = '' ) {
if ( $field ) {
$select = " $field IN ( $values ) " ;
} else {
$select = '' ;
}
return get_recordset_select ( $table , $select , $sort , $fields , $limitfrom , $limitnum );
}
/**
* Get a number of records as an ADODB RecordSet . $sql must be a complete SQL query .
2006-09-20 21:00:45 +00:00
* This function is internal to datalib , and should NEVER should be called directly
2006-08-18 22:56:08 +00:00
* from general Moodle scripts . Use get_record , get_records etc .
2006-09-20 21:00:45 +00:00
*
* The return type is as for @ see function get_recordset .
2006-08-18 22:56:08 +00:00
*
* @ uses $CFG
* @ uses $db
* @ param string $sql the SQL select query to execute .
2006-08-20 09:37:56 +00:00
* @ param int $limitfrom return a subset of records , starting at this point ( optional , required if $limitnum is set ) .
* @ param int $limitnum return a subset comprising this many records ( optional , required if $limitfrom is set ) .
2006-08-18 22:56:08 +00:00
* @ return mixed an ADODB RecordSet object , or false if an error occured .
*/
2006-08-20 09:37:56 +00:00
function get_recordset_sql ( $sql , $limitfrom = null , $limitnum = null ) {
2006-08-18 22:56:08 +00:00
global $CFG , $db ;
2006-09-13 08:09:18 +00:00
if ( empty ( $db )) {
return false ;
}
2006-10-01 09:16:49 +00:00
/// Temporary hack as part of phasing out all access to obsolete user tables XXX
if ( ! empty ( $CFG -> rolesactive )) {
if ( strpos ( $sql , $CFG -> prefix . 'user_students' ) ||
strpos ( $sql , $CFG -> prefix . 'user_teachers' ) ||
strpos ( $sql , $CFG -> prefix . 'user_coursecreators' ) ||
strpos ( $sql , $CFG -> prefix . 'user_admins' )) {
if ( debugging ()) { var_dump ( debug_backtrace ()); }
error ( 'This SQL relies on obsolete tables! Your code must be fixed by a developer.' );
}
}
2006-08-18 22:56:08 +00:00
if ( defined ( 'MDL_PERFDB' )) { global $PERF ; $PERF -> dbqueries ++ ; };
2006-08-27 09:02:41 +00:00
if ( $limitfrom || $limitnum ) {
///Special case, 0 must be -1 for ADOdb
$limitfrom = empty ( $limitfrom ) ? - 1 : $limitfrom ;
$limitnum = empty ( $limitnum ) ? - 1 : $limitnum ;
2006-08-20 09:37:56 +00:00
$rs = $db -> SelectLimit ( $sql , $limitnum , $limitfrom );
} else {
$rs = $db -> Execute ( $sql );
}
if ( ! $rs ) {
2006-09-13 09:45:07 +00:00
debugging ( $db -> ErrorMsg () . '<br /><br />' . $sql );
2006-08-18 22:56:08 +00:00
if ( ! empty ( $CFG -> dblogerror )) {
$debug = array_shift ( debug_backtrace ());
2006-08-20 09:37:56 +00:00
error_log ( " SQL " . $db -> ErrorMsg () . " in { $debug [ 'file' ] } on line { $debug [ 'line' ] } . STATEMENT: $sql with limits ( $limitfrom , $limitnum ) " );
2006-08-18 22:56:08 +00:00
}
return false ;
}
return $rs ;
}
/**
* Utility function used by the following 4 methods .
2006-09-20 21:00:45 +00:00
*
2006-08-18 22:56:08 +00:00
* @ param object an ADODB RecordSet object .
* @ return mixed mixed an array of objects , or false if an error occured or the RecordSet was empty .
*/
function recordset_to_array ( $rs ) {
2006-09-07 23:48:48 +00:00
global $CFG ;
2006-08-18 22:56:08 +00:00
if ( $rs && $rs -> RecordCount () > 0 ) {
2006-08-26 00:08:11 +00:00
/// First of all, we are going to get the name of the first column
/// to introduce it back after transforming the recordset to assoc array
/// See http://docs.moodle.org/en/XMLDB_Problems, fetch mode problem.
$firstcolumn = $rs -> FetchField ( 0 );
/// Get the whole associative array
2006-08-18 22:56:08 +00:00
if ( $records = $rs -> GetAssoc ( true )) {
foreach ( $records as $key => $record ) {
2006-09-07 23:48:48 +00:00
/// Really DIRTY HACK for Oracle, but it's the only way to make it work
/// until we got all those NOT NULL DEFAULT '' out from Moodle
if ( $CFG -> dbtype == 'oci8po' ) {
array_walk ( $record , 'onespace2empty' );
}
/// End of DIRTY HACK
2006-08-26 00:08:11 +00:00
$record [ $firstcolumn -> name ] = $key ; /// Re-add the assoc field
$objects [ $key ] = ( object ) $record ; /// To object
2006-08-18 22:56:08 +00:00
}
return $objects ;
} else {
return false ;
}
} else {
return false ;
}
}
2006-09-20 21:00:45 +00:00
/**
2006-09-07 23:48:48 +00:00
* This function is used to convert all the Oracle 1 - space defaults to the empty string
* like a really DIRTY HACK to allow it to work better until all those NOT NULL DEFAULT ''
* fields will be out from Moodle .
* @ param string the string to be converted to '' ( empty string ) if it 's ' ' ( one space )
2006-09-26 18:27:44 +00:00
* @ param mixed the key of the array in case we are using this function from array_walk ,
* defaults to null for other ( direct ) uses
* @ return boolean always true ( the converted variable is returned by reference )
2006-09-07 23:48:48 +00:00
*/
2006-09-26 18:27:44 +00:00
function onespace2empty ( & $item , $key = null ) {
2006-09-07 23:48:48 +00:00
$item = $item == ' ' ? '' : $item ;
return true ;
}
2006-09-21 18:30:50 +00:00
///End DIRTY HACK
2006-09-07 23:48:48 +00:00
2006-08-18 22:56:08 +00:00
/**
* Get a number of records as an array of objects .
*
* If the query succeeds and returns at least one record , the
* return value is an array of objects , one object for each
2006-09-20 21:00:45 +00:00
* record found . The array key is the value from the first
2006-08-18 22:56:08 +00:00
* column of the result set . The object associated with that key
* has a member variable for each column of the results .
*
* @ param string $table the table to query .
* @ param string $field a field to check ( optional ) .
* @ param string $value the value the field must have ( requred if field1 is given , else optional ) .
* @ param string $sort an order to sort the results in ( optional , a valid SQL ORDER BY parameter ) .
* @ param string $fields a comma separated list of fields to return ( optional , by default all fields are returned ) .
* @ param int $limitfrom return a subset of records , starting at this point ( optional , required if $limitnum is set ) .
* @ param int $limitnum return a subset comprising this many records ( optional , required if $limitfrom is set ) .
* @ return mixed an array of objects , or false if no records were found or an error occured .
*/
function get_records ( $table , $field = '' , $value = '' , $sort = '' , $fields = '*' , $limitfrom = '' , $limitnum = '' ) {
$rs = get_recordset ( $table , $field , $value , $sort , $fields , $limitfrom , $limitnum );
return recordset_to_array ( $rs );
}
/**
* Get a number of records as an array of objects .
*
* Return value as for @ see function get_records .
*
* @ param string $table the table to query .
* @ param string $select A fragment of SQL to be used in a where clause in the SQL call .
* @ param string $sort an order to sort the results in ( optional , a valid SQL ORDER BY parameter ) .
* @ param string $fields a comma separated list of fields to return ( optional , by default all fields are returned ) .
* @ param int $limitfrom return a subset of records , starting at this point ( optional , required if $limitnum is set ) .
* @ param int $limitnum return a subset comprising this many records ( optional , required if $limitfrom is set ) .
* @ return mixed an array of objects , or false if no records were found or an error occured .
*/
function get_records_select ( $table , $select = '' , $sort = '' , $fields = '*' , $limitfrom = '' , $limitnum = '' ) {
$rs = get_recordset_select ( $table , $select , $sort , $fields , $limitfrom , $limitnum );
return recordset_to_array ( $rs );
}
/**
* Get a number of records as an array of objects .
*
* Return value as for @ see function get_records .
*
* @ param string $table The database table to be checked against .
* @ param string $field The field to search
* @ param string $values Comma separated list of possible value
* @ param string $sort Sort order ( as valid SQL sort parameter )
* @ param string $fields A comma separated list of fields to be returned from the chosen table .
* @ return mixed an array of objects , or false if no records were found or an error occured .
*/
function get_records_list ( $table , $field = '' , $values = '' , $sort = '' , $fields = '*' , $limitfrom = '' , $limitnum = '' ) {
$rs = get_recordset_list ( $table , $field , $values , $sort , $fields , $limitfrom , $limitnum );
return recordset_to_array ( $rs );
}
/**
* Get a number of records as an array of objects .
*
* Return value as for @ see function get_records .
2006-09-20 21:00:45 +00:00
*
2006-08-18 22:56:08 +00:00
* @ param string $sql the SQL select query to execute .
2006-09-05 18:53:08 +00:00
* @ param int $limitfrom return a subset of records , starting at this point ( optional , required if $limitnum is set ) .
* @ param int $limitnum return a subset comprising this many records ( optional , required if $limitfrom is set ) .
2006-08-18 22:56:08 +00:00
* @ return mixed an array of objects , or false if no records were found or an error occured .
*/
2006-09-05 18:53:08 +00:00
function get_records_sql ( $sql , $limitfrom = '' , $limitnum = '' ) {
2006-09-15 06:03:32 +00:00
$rs = get_recordset_sql ( $sql , $limitfrom , $limitnum );
2006-08-18 22:56:08 +00:00
return recordset_to_array ( $rs );
}
/**
* Utility function used by the following 3 methods .
2006-09-20 21:00:45 +00:00
*
2006-08-18 22:56:08 +00:00
* @ param object an ADODB RecordSet object with two columns .
* @ return mixed an associative array , or false if an error occured or the RecordSet was empty .
*/
function recordset_to_menu ( $rs ) {
2006-09-26 08:49:47 +00:00
global $CFG ;
2006-08-18 22:56:08 +00:00
if ( $rs && $rs -> RecordCount () > 0 ) {
2006-09-20 11:26:47 +00:00
$keys = array_keys ( $rs -> fields );
$key0 = $keys [ 0 ];
$key1 = $keys [ 1 ];
2006-08-18 22:56:08 +00:00
while ( ! $rs -> EOF ) {
2006-09-20 11:26:47 +00:00
$menu [ $rs -> fields [ $key0 ]] = $rs -> fields [ $key1 ];
2006-08-18 22:56:08 +00:00
$rs -> MoveNext ();
}
2006-09-26 05:10:18 +00:00
/// Really DIRTY HACK for Oracle, but it's the only way to make it work
/// until we got all those NOT NULL DEFAULT '' out from Moodle
if ( $CFG -> dbtype == 'oci8po' ) {
array_walk ( $menu , 'onespace2empty' );
}
/// End of DIRTY HACK
2006-08-18 22:56:08 +00:00
return $menu ;
} else {
return false ;
}
}
/**
* Get the first two columns from a number of records as an associative array .
*
* Arguments as for @ see function get_recordset .
2006-09-20 21:00:45 +00:00
*
2006-08-18 22:56:08 +00:00
* If no errors occur , and at least one records is found , the return value
* is an associative whose keys come from the first field of each record ,
* and whose values are the corresponding second fields . If no records are found ,
* or an error occurs , false is returned .
*
* @ param string $table the table to query .
* @ param string $field a field to check ( optional ) .
* @ param string $value the value the field must have ( requred if field1 is given , else optional ) .
* @ param string $sort an order to sort the results in ( optional , a valid SQL ORDER BY parameter ) .
* @ param string $fields a comma separated list of fields to return ( optional , by default all fields are returned ) .
* @ return mixed an associative array , or false if no records were found or an error occured .
*/
function get_records_menu ( $table , $field = '' , $value = '' , $sort = '' , $fields = '*' ) {
$rs = get_recordset ( $table , $field , $value , $sort , $fields );
return recordset_to_menu ( $rs );
}
/**
* Get the first two columns from a number of records as an associative array .
*
* Arguments as for @ see function get_recordset_select .
* Return value as for @ see function get_records_menu .
*
* @ param string $table The database table to be checked against .
* @ param string $select A fragment of SQL to be used in a where clause in the SQL call .
* @ param string $sort Sort order ( optional ) - a valid SQL order parameter
* @ param string $fields A comma separated list of fields to be returned from the chosen table .
* @ return mixed an associative array , or false if no records were found or an error occured .
*/
function get_records_select_menu ( $table , $select = '' , $sort = '' , $fields = '*' ) {
$rs = get_recordset_select ( $table , $select , $sort , $fields );
return recordset_to_menu ( $rs );
}
/**
* Get the first two columns from a number of records as an associative array .
*
* Arguments as for @ see function get_recordset_sql .
* Return value as for @ see function get_records_menu .
*
* @ param string $sql The SQL string you wish to be executed .
* @ return mixed an associative array , or false if no records were found or an error occured .
*/
function get_records_sql_menu ( $sql ) {
$rs = get_recordset_sql ( $sql );
return recordset_to_menu ( $rs );
}
/**
* Get a single value from a table row where all the given fields match the given values .
*
* @ param string $table the table to query .
* @ param string $return the field to return the value of .
* @ param string $field1 the first field to check ( optional ) .
* @ param string $value1 the value field1 must have ( requred if field1 is given , else optional ) .
* @ param string $field2 the second field to check ( optional ) .
* @ param string $value2 the value field2 must have ( requred if field2 is given , else optional ) .
* @ param string $field3 the third field to check ( optional ) .
* @ param string $value3 the value field3 must have ( requred if field3 is given , else optional ) .
* @ return mixed the specified value , or false if an error occured .
*/
function get_field ( $table , $return , $field1 , $value1 , $field2 = '' , $value2 = '' , $field3 = '' , $value3 = '' ) {
global $CFG ;
$select = where_clause ( $field1 , $value1 , $field2 , $value2 , $field3 , $value3 );
return get_field_sql ( 'SELECT ' . $return . ' FROM ' . $CFG -> prefix . $table . ' ' . $select );
}
/**
* Get a single value from a table row where a particular select clause is true .
*
* @ uses $CFG
* @ param string $table the table to query .
* @ param string $return the field to return the value of .
* @ param string $select A fragment of SQL to be used in a where clause in the SQL call .
* @ return mixed the specified value , or false if an error occured .
*/
function get_field_select ( $table , $return , $select ) {
global $CFG ;
if ( $select ) {
$select = 'WHERE ' . $select ;
}
return get_field_sql ( 'SELECT ' . $return . ' FROM ' . $CFG -> prefix . $table . ' ' . $select );
}
/**
* Get a single value from a table .
*
* @ param string $sql an SQL statement expected to return a single value .
* @ return mixed the specified value , or false if an error occured .
*/
function get_field_sql ( $sql ) {
2006-09-26 06:31:18 +00:00
global $CFG ;
2006-08-18 22:56:08 +00:00
$rs = get_recordset_sql ( $sql );
if ( $rs && $rs -> RecordCount () == 1 ) {
2006-09-26 05:10:18 +00:00
/// DIRTY HACK to retrieve all the ' ' (1 space) fields converted back
/// to '' (empty string) for Oracle. It's the only way to work with
/// all those NOT NULL DEFAULT '' fields until we definetively delete them
if ( $CFG -> dbtype == 'oci8po' ) {
2006-09-26 18:27:44 +00:00
$value = reset ( $rs -> fields );
onespace2empty ( $value );
return $value ;
2006-09-26 05:10:18 +00:00
}
/// End of DIRTY HACK
2006-09-20 11:26:47 +00:00
return reset ( $rs -> fields );
2006-08-18 22:56:08 +00:00
} else {
return false ;
}
}
/**
2006-09-20 21:00:45 +00:00
* Get an array of data from one or more fields from a database
2006-08-18 22:56:08 +00:00
* use to get a column , or a series of distinct values
*
* @ uses $CFG
* @ uses $db
* @ param string $sql The SQL string you wish to be executed .
* @ return mixed | false Returns the value return from the SQL statment or false if an error occured .
* @ todo Finish documenting this function
*/
function get_fieldset_sql ( $sql ) {
global $db , $CFG ;
if ( defined ( 'MDL_PERFDB' )) { global $PERF ; $PERF -> dbqueries ++ ; };
$rs = $db -> Execute ( $sql );
if ( ! $rs ) {
2006-09-13 09:45:07 +00:00
debugging ( $db -> ErrorMsg () . '<br /><br />' . $sql );
2006-08-18 22:56:08 +00:00
if ( ! empty ( $CFG -> dblogerror )) {
$debug = array_shift ( debug_backtrace ());
error_log ( " SQL " . $db -> ErrorMsg () . " in { $debug [ 'file' ] } on line { $debug [ 'line' ] } . STATEMENT: $sql " );
}
return false ;
}
if ( $rs -> RecordCount () > 0 ) {
2006-09-20 11:26:47 +00:00
$keys = array_keys ( $rs -> fields );
$key0 = $keys [ 0 ];
2006-08-18 22:56:08 +00:00
$results = array ();
while ( ! $rs -> EOF ) {
2006-09-20 11:26:47 +00:00
array_push ( $results , $rs -> fields [ $key0 ]);
2006-08-18 22:56:08 +00:00
$rs -> MoveNext ();
}
2006-09-26 05:10:18 +00:00
/// DIRTY HACK to retrieve all the ' ' (1 space) fields converted back
/// to '' (empty string) for Oracle. It's the only way to work with
/// all those NOT NULL DEFAULT '' fields until we definetively delete them
if ( $CFG -> dbtype == 'oci8po' ) {
array_walk ( $results , 'onespace2empty' );
}
/// End of DIRTY HACK
2006-08-18 22:56:08 +00:00
return $results ;
} else {
return false ;
}
}
/**
* Set a single field in every table row where all the given fields match the given values .
*
* @ uses $CFG
* @ uses $db
* @ param string $table The database table to be checked against .
* @ param string $newfield the field to set .
* @ param string $newvalue the value to set the field to .
* @ param string $field1 the first field to check ( optional ) .
* @ param string $value1 the value field1 must have ( requred if field1 is given , else optional ) .
* @ param string $field2 the second field to check ( optional ) .
* @ param string $value2 the value field2 must have ( requred if field2 is given , else optional ) .
* @ param string $field3 the third field to check ( optional ) .
* @ param string $value3 the value field3 must have ( requred if field3 is given , else optional ) .
* @ return mixed An ADODB RecordSet object with the results from the SQL call or false .
*/
function set_field ( $table , $newfield , $newvalue , $field1 , $value1 , $field2 = '' , $value2 = '' , $field3 = '' , $value3 = '' ) {
global $db , $CFG ;
if ( defined ( 'MDL_PERFDB' )) { global $PERF ; $PERF -> dbqueries ++ ; };
$select = where_clause ( $field1 , $value1 , $field2 , $value2 , $field3 , $value3 );
2006-09-26 05:10:39 +00:00
// Oracle DIRTY HACK -
if ( $CFG -> dbtype == 'oci8po' ) {
$dataobject = new StdClass ;
$dataobject -> { $newfield } = $newvalue ;
oracle_dirty_hack ( $table , $dataobject ); // Convert object to the correct "empty" values for Oracle DB
$newvalue = $dataobject -> { $newfield };
}
/// End DIRTY HACK
2006-08-18 22:56:08 +00:00
return $db -> Execute ( 'UPDATE ' . $CFG -> prefix . $table . ' SET ' . $newfield . ' = \'' . $newvalue . '\' ' . $select );
}
/**
* Delete the records from a table where all the given fields match the given values .
*
* @ uses $CFG
* @ uses $db
* @ param string $table the table to delete from .
* @ param string $field1 the first field to check ( optional ) .
* @ param string $value1 the value field1 must have ( requred if field1 is given , else optional ) .
* @ param string $field2 the second field to check ( optional ) .
* @ param string $value2 the value field2 must have ( requred if field2 is given , else optional ) .
* @ param string $field3 the third field to check ( optional ) .
* @ param string $value3 the value field3 must have ( requred if field3 is given , else optional ) .
* @ return mixed An ADODB RecordSet object with the results from the SQL call or false .
*/
function delete_records ( $table , $field1 = '' , $value1 = '' , $field2 = '' , $value2 = '' , $field3 = '' , $value3 = '' ) {
global $db , $CFG ;
if ( defined ( 'MDL_PERFDB' )) { global $PERF ; $PERF -> dbqueries ++ ; };
$select = where_clause ( $field1 , $value1 , $field2 , $value2 , $field3 , $value3 );
return $db -> Execute ( 'DELETE FROM ' . $CFG -> prefix . $table . ' ' . $select );
}
/**
* Delete one or more records from a table
*
* @ uses $CFG
* @ uses $db
* @ param string $table The database table to be checked against .
* @ param string $select A fragment of SQL to be used in a where clause in the SQL call ( used to define the selection criteria ) .
* @ return object A PHP standard object with the results from the SQL call .
* @ todo Verify return type .
*/
function delete_records_select ( $table , $select = '' ) {
global $CFG , $db ;
if ( defined ( 'MDL_PERFDB' )) { global $PERF ; $PERF -> dbqueries ++ ; };
if ( $select ) {
$select = 'WHERE ' . $select ;
}
return $db -> Execute ( 'DELETE FROM ' . $CFG -> prefix . $table . ' ' . $select );
}
/**
* Insert a record into a table and return the " id " field if required
*
* If the return ID isn ' t required , then this just reports success as true / false .
* $dataobject is an object containing needed data
*
* @ uses $db
* @ uses $CFG
* @ param string $table The database table to be checked against .
* @ param array $dataobject A data object with values for one or more fields in the record
* @ param bool $returnid Should the id of the newly created record entry be returned ? If this option is not requested then true / false is returned .
* @ param string $primarykey The primary key of the table we are inserting into ( almost always " id " )
*/
function insert_record ( $table , $dataobject , $returnid = true , $primarykey = 'id' ) {
global $db , $CFG , $empty_rs_cache ;
2006-09-13 08:09:18 +00:00
if ( empty ( $db )) {
return false ;
}
2006-10-01 09:16:49 +00:00
/// Temporary hack as part of phasing out all access to obsolete user tables XXX
if ( ! empty ( $CFG -> rolesactive )) {
if ( in_array ( $table , array ( 'user_students' , 'user_teachers' , 'user_coursecreators' , 'user_admins' ))) {
if ( debugging ()) { var_dump ( debug_backtrace ()); }
error ( 'This SQL relies on obsolete tables (' . $table . ')! Your code must be fixed by a developer.' );
}
}
2006-08-18 22:56:08 +00:00
if ( defined ( 'MDL_PERFDB' )) { global $PERF ; $PERF -> dbqueries ++ ; };
/// In Moodle we always use auto-numbering fields for the primary key
/// so let's unset it now before it causes any trouble later
unset ( $dataobject -> { $primarykey });
/// Get an empty recordset. Cache for multiple inserts.
if ( empty ( $empty_rs_cache [ $table ])) {
/// Execute a dummy query to get an empty recordset
if ( ! $empty_rs_cache [ $table ] = $db -> Execute ( 'SELECT * FROM ' . $CFG -> prefix . $table . ' WHERE ' . $primarykey . ' = \'-1\'' )) {
return false ;
}
}
$rs = $empty_rs_cache [ $table ];
/// Postgres doesn't have the concept of primary key built in
/// and will return the OID which isn't what we want.
2006-09-20 21:00:45 +00:00
/// The efficient and transaction-safe strategy is to
2006-08-18 22:56:08 +00:00
/// move the sequence forward first, and make the insert
/// with an explicit id.
if ( $CFG -> dbtype === 'postgres7' && $returnid == true ) {
if ( $nextval = ( int ) get_field_sql ( " SELECT NEXTVAL(' { $CFG -> prefix } { $table } _ { $primarykey } _seq') " )) {
2006-09-20 21:00:45 +00:00
$dataobject -> { $primarykey } = $nextval ;
}
2006-08-18 22:56:08 +00:00
}
2006-09-20 21:00:45 +00:00
/// First basic support of insert for Oracle. As it doesn't
2006-08-27 09:02:41 +00:00
/// support autogenerated fields, we rely on the corresponding
/// sequence. It will work automatically, unless we need to
/// return the primary from the function, in this case we
/// get the next sequence value here and insert it manually.
if ( $CFG -> dbtype === 'oci8po' && $returnid == true ) {
/// We need this here (move this function to dmlib?)
2006-10-08 09:58:11 +00:00
include_once ( $CFG -> libdir . '/ddllib.php' );
$xmldb_table = new XMLDBTable ( $table );
$seqname = find_sequence_name ( $xmldb_table );
if ( ! $seqname ) {
/// Fallback, seqname not found, something is wrong. Inform and use the alternative getNameForObject() method
debugging ( 'Sequence name for table ' . $table -> getName () . ' not found' , DEBUG_DEVELOPER );
$generator = new XMLDBoci8po ();
$generator -> setPrefix ( $CFG -> prefix );
$seqname = $generator -> getNameForObject ( $table , $primarykey , 'seq' );
}
2006-08-27 09:02:41 +00:00
if ( $nextval = ( int ) get_field_sql ( " SELECT $seqname .NEXTVAL from dual " )) {
2006-09-20 21:00:45 +00:00
$dataobject -> { $primarykey } = $nextval ;
2006-08-27 09:02:41 +00:00
}
}
2006-09-21 18:30:50 +00:00
/// Begin DIRTY HACK
if ( $CFG -> dbtype == 'oci8po' ) {
oracle_dirty_hack ( $table , $dataobject ); // Convert object to the correct "empty" values for Oracle DB
}
/// End DIRTY HACK
2006-10-08 09:58:11 +00:00
/// Under Oracle we have our own insert record process
/// detect all the clob/blob fields and change their contents to @#CLOB#@ and @#BLOB#@
/// saving them into $foundclobs and $foundblobs [$fieldname]->contents
if ( $CFG -> dbtype == 'oci8po' && ! empty ( $dataobject -> { $primarykey })) {
$foundclobs = array ();
$foundblobs = array ();
///TODO
}
2006-09-21 18:30:50 +00:00
2006-08-18 22:56:08 +00:00
/// Get the correct SQL from adoDB
if ( ! $insertSQL = $db -> GetInsertSQL ( $rs , ( array ) $dataobject , true )) {
return false ;
}
2006-10-08 09:58:11 +00:00
/// Under Oracle, replace all the '@#CLOB#@' and '@#BLOB#@' ocurrences to empty_clob() and empty_blob()
/// if we know we have some of them in the query
if ( $CFG -> dbtype == 'oci8po' && ! empty ( $dataobject -> { $primarykey }) &&
( ! empty ( $foundclobs ) || ! empty ( $foundblobs ))) {
///TODO
}
2006-08-18 22:56:08 +00:00
/// Run the SQL statement
if ( ! $rs = $db -> Execute ( $insertSQL )) {
2006-09-13 09:45:07 +00:00
debugging ( $db -> ErrorMsg () . '<br /><br />' . $insertSQL );
2006-08-18 22:56:08 +00:00
if ( ! empty ( $CFG -> dblogerror )) {
$debug = array_shift ( debug_backtrace ());
error_log ( " SQL " . $db -> ErrorMsg () . " in { $debug [ 'file' ] } on line { $debug [ 'line' ] } . STATEMENT: $insertSQL " );
}
return false ;
}
2006-10-08 09:58:11 +00:00
/// Under Oracle, finally, Update all the Clobs and Blobs present in the record
/// if we know we have some of them in the query
if ( $CFG -> dbtype == 'oci8po' && ! empty ( $dataobject -> { $primarykey }) &&
( ! empty ( $foundclobs ) || ! empty ( $foundblobs ))) {
//TODO
}
2006-08-18 22:56:08 +00:00
/// If a return ID is not needed then just return true now
if ( ! $returnid ) {
return true ;
}
/// We already know the record PK if it's been passed explicitly,
2006-09-21 18:30:50 +00:00
/// or if we've retrieved it from a sequence (Postgres and Oracle).
2006-08-18 22:56:08 +00:00
if ( ! empty ( $dataobject -> { $primarykey })) {
return $dataobject -> { $primarykey };
}
/// This only gets triggered with non-Postgres databases
2006-09-20 21:00:45 +00:00
/// however we have some postgres fallback in case we failed
2006-08-18 22:56:08 +00:00
/// to find the sequence.
2006-09-20 21:00:45 +00:00
$id = $db -> Insert_ID ();
2006-08-18 22:56:08 +00:00
if ( $CFG -> dbtype === 'postgres7' ) {
// try to get the primary key based on id
if ( ( $rs = $db -> Execute ( 'SELECT ' . $primarykey . ' FROM ' . $CFG -> prefix . $table . ' WHERE oid = ' . $id ))
&& ( $rs -> RecordCount () == 1 ) ) {
trigger_error ( " Retrieved $primarykey from oid on table $table because we could not find the sequence. " );
2006-09-20 11:26:47 +00:00
return ( integer ) reset ( $rs -> fields );
2006-09-20 21:00:45 +00:00
}
2006-08-18 22:56:08 +00:00
trigger_error ( 'Failed to retrieve primary key after insert: SELECT ' . $primarykey .
' FROM ' . $CFG -> prefix . $table . ' WHERE oid = ' . $id );
return false ;
}
return ( integer ) $id ;
}
/**
* Update a record in a table
*
* $dataobject is an object containing needed data
* Relies on $dataobject having a variable " id " to
* specify the record to update
*
* @ uses $CFG
* @ uses $db
* @ param string $table The database table to be checked against .
* @ param array $dataobject An object with contents equal to fieldname => fieldvalue . Must have an entry for 'id' to map to the table specified .
* @ return bool
*/
function update_record ( $table , $dataobject ) {
global $db , $CFG ;
if ( ! isset ( $dataobject -> id ) ) {
return false ;
}
2006-10-01 09:16:49 +00:00
/// Temporary hack as part of phasing out all access to obsolete user tables XXX
if ( ! empty ( $CFG -> rolesactive )) {
if ( in_array ( $table , array ( 'user_students' , 'user_teachers' , 'user_coursecreators' , 'user_admins' ))) {
if ( debugging ()) { var_dump ( debug_backtrace ()); }
error ( 'This SQL relies on obsolete tables (' . $table . ')! Your code must be fixed by a developer.' );
}
}
2006-09-21 18:30:50 +00:00
/// Begin DIRTY HACK
if ( $CFG -> dbtype == 'oci8po' ) {
oracle_dirty_hack ( $table , $dataobject ); // Convert object to the correct "empty" values for Oracle DB
}
/// End DIRTY HACK
2006-08-18 22:56:08 +00:00
// Determine all the fields in the table
if ( ! $columns = $db -> MetaColumns ( $CFG -> prefix . $table )) {
return false ;
}
$data = ( array ) $dataobject ;
if ( defined ( 'MDL_PERFDB' )) { global $PERF ; $PERF -> dbqueries ++ ; };
// Pull out data matching these fields
foreach ( $columns as $column ) {
if ( $column -> name <> 'id' and isset ( $data [ $column -> name ]) ) {
$ddd [ $column -> name ] = $data [ $column -> name ];
// PostgreSQL bytea support
if ( $CFG -> dbtype == 'postgres7' && $column -> type == 'bytea' ) {
$ddd [ $column -> name ] = $db -> BlobEncode ( $ddd [ $column -> name ]);
}
}
}
// Construct SQL queries
$numddd = count ( $ddd );
$count = 0 ;
$update = '' ;
foreach ( $ddd as $key => $value ) {
$count ++ ;
$update .= $key . ' = \'' . $value . '\'' ; // All incoming data is already quoted
if ( $count < $numddd ) {
$update .= ', ' ;
}
}
if ( $rs = $db -> Execute ( 'UPDATE ' . $CFG -> prefix . $table . ' SET ' . $update . ' WHERE id = \'' . $dataobject -> id . '\'' )) {
return true ;
} else {
2006-09-13 09:45:07 +00:00
debugging ( $db -> ErrorMsg () . '<br /><br />UPDATE ' . $CFG -> prefix . $table . ' SET ' . $update . ' WHERE id = \'' . $dataobject -> id . '\'' );
2006-08-18 22:56:08 +00:00
if ( ! empty ( $CFG -> dblogerror )) {
$debug = array_shift ( debug_backtrace ());
error_log ( " SQL " . $db -> ErrorMsg () . " in { $debug [ 'file' ] } on line { $debug [ 'line' ] } . STATEMENT: UPDATE $CFG->prefix $table SET $update WHERE id = ' $dataobject->id ' " );
}
return false ;
}
}
/**
* Returns the proper SQL to do paging
*
* @ uses $CFG
* @ param string $page Offset page number
* @ param string $recordsperpage Number of records per page
2006-09-05 21:42:20 +00:00
* @ deprecated Moodle 1.7 use the new $limitfrom , $limitnum available in all
* the get_recordXXX () funcions .
2006-08-18 22:56:08 +00:00
* @ return string
*/
function sql_paging_limit ( $page , $recordsperpage ) {
global $CFG ;
switch ( $CFG -> dbtype ) {
case 'postgres7' :
return 'LIMIT ' . $recordsperpage . ' OFFSET ' . $page ;
default :
return 'LIMIT ' . $page . ',' . $recordsperpage ;
}
}
/**
* Returns the proper SQL to do LIKE in a case - insensitive way
*
2006-09-26 05:07:28 +00:00
* Note the LIKE are case sensitive for Oracle . Oracle 10 g is required to use
* the caseinsensitive search using regexp_like () or NLS_COMP = LINGUISTIC :- (
* See http :// docs . moodle . org / en / XMLDB_Problems #Case-insensitive_searches
*
2006-08-18 22:56:08 +00:00
* @ uses $CFG
* @ return string
*/
function sql_ilike () {
global $CFG ;
switch ( $CFG -> dbtype ) {
2006-09-26 05:07:28 +00:00
case 'postgres7' :
2006-08-18 22:56:08 +00:00
return 'ILIKE' ;
2006-09-26 05:07:28 +00:00
default :
return 'LIKE' ;
2006-08-18 22:56:08 +00:00
}
}
/**
2006-09-20 18:40:22 +00:00
* Returns the proper SQL ( for the dbms in use ) to concatenate $firstname and $lastname
2006-09-20 21:00:45 +00:00
*
2006-08-18 22:56:08 +00:00
* @ uses $CFG
* @ param string $firstname User ' s first name
* @ param string $lastname User ' s last name
* @ return string
*/
function sql_fullname ( $firstname = 'firstname' , $lastname = 'lastname' ) {
2006-09-26 05:02:59 +00:00
return sql_concat ( $firstname , " ' ' " , $lastname );
}
2006-08-18 22:56:08 +00:00
2006-09-26 05:02:59 +00:00
/**
* Returns the proper SQL to do CONCAT between the elements passed
* Can take many parameters - just a passthrough to $db -> Concat ()
*
* @ uses $db
* @ param string $element
* @ return string
*/
function sql_concat () {
global $db ;
2006-09-26 05:09:48 +00:00
2006-09-26 05:02:59 +00:00
$args = func_get_args ();
2006-09-26 05:09:48 +00:00
return call_user_func_array ( array ( $db , 'Concat' ), $args );
2006-08-18 22:56:08 +00:00
}
2006-09-26 05:05:54 +00:00
/**
* Returns the proper SQL to do CONCAT between the elements passed
* with a given separator
*
* @ uses $db
* @ param string $separator
* @ param array $elements
* @ return string
*/
function sql_concat_join ( $separator = " ' ' " , $elements = array ()) {
global $db ;
// copy to ensure pass by value
$elem = $elements ;
// Intersperse $elements in the array.
// Add items to the array on the fly, walking it
// _backwards_ splicing the elements in. The loop definition
// should skip first and last positions.
for ( $n = count ( $elem ) - 1 ; $n > 0 ; $n -- ) {
array_splice ( $elem , $n , 0 , $separator );
}
2006-09-26 05:09:48 +00:00
return call_user_func_array ( array ( $db , 'Concat' ), $elem );
2006-09-26 05:05:54 +00:00
}
2006-08-18 22:56:08 +00:00
/**
* Returns the proper SQL to do IS NULL
* @ uses $CFG
* @ param string $fieldname The field to add IS NULL to
* @ return string
*/
function sql_isnull ( $fieldname ) {
global $CFG ;
switch ( $CFG -> dbtype ) {
case 'mysql' :
return $fieldname . ' IS NULL' ;
default :
return $fieldname . ' IS NULL' ;
}
}
2006-09-20 21:00:45 +00:00
/**
2006-09-01 17:45:02 +00:00
* Returns the proper AS keyword to be used to aliase columns
* SQL defines the keyword as optional and nobody but PG
* seems to require it . This function should be used inside all
* the statements using column aliases .
* Note than the use of table aliases doesn ' t require the
* AS keyword at all , only columns for postgres .
* @ uses $CFG
* @ return string the keyword
*/
function sql_as () {
2006-09-04 22:30:56 +00:00
global $CFG , $db ;
2006-09-01 17:45:02 +00:00
switch ( $CFG -> dbtype ) {
case 'postgres7' :
return 'AS' ;
default :
return '' ;
}
}
2006-09-09 13:51:40 +00:00
/**
* Returns the SQL text to be used to order by one TEXT ( clob ) column , because
2006-09-20 21:00:45 +00:00
* some RDBMS doesn ' t support direct ordering of such fields .
2006-09-09 13:51:40 +00:00
* Note that the use or queries being ordered by TEXT columns must be minimised ,
* because it ' s really slooooooow .
* @ param string fieldname the name of the TEXT field we need to order by
* @ param string number of chars to use for the ordering ( defaults to 32 )
* @ return string the piece of SQL code to be used in your statement .
*/
function sql_order_by_text ( $fieldname , $numchars = 32 ) {
global $CFG ;
switch ( $CFG -> dbtype ) {
case 'mssql' :
return 'CONVERT(varchar, ' . $fieldname . ', ' . $numchars . ')' ;
break ;
case 'oci8po' :
return 'dbms_lob.substr(' . $fieldname . ', ' . $numchars . ',1)' ;
break ;
default :
return $fieldname ;
}
}
2006-09-26 04:33:59 +00:00
/**
* Returns SQL to be used as a subselect to find the primary role of users .
2006-09-26 04:41:45 +00:00
* Geoff Cant < geoff @ catalyst . net . nz > ( the author ) is very keen for this to
* be implemented as a view in future versions .
2006-09-26 04:33:59 +00:00
*
* eg if this function returns a string called $primaryroles , then you could :
* $sql = 'SELECT COUNT(DISTINCT prs.userid) FROM (' . $primary_roles . ' ) prs
* WHERE prs . primary_roleid = '.$role->id.' AND prs . courseid = ' . $course -> id .
* ' AND prs.contextlevel = ' . CONTEXT_COURSE ;
*
* @ return string the piece of SQL code to be used in your FROM ( ) statement .
*/
function sql_primary_role_subselect () {
global $CFG ;
return ' SELECT ra . userid ,
ra . roleid AS primary_roleid ,
ra . contextid ,
r . sortorder ,
r . name ,
r . description ,
r . shortname ,
c . instanceid AS courseid ,
c . contextlevel
FROM '.$CFG->prefix.' role_assignments ra
INNER JOIN '.$CFG->prefix.' role r ON ra . roleid = r . id
INNER JOIN '.$CFG->prefix.' context c ON ra . contextid = c . id
WHERE NOT EXISTS (
SELECT 1
FROM '.$CFG->prefix.' role_assignments i_ra
INNER JOIN '.$CFG->prefix.' role i_r ON i_ra . roleid = i_r . id
WHERE ra . userid = i_ra . userid AND
ra . contextid = i_ra . contextid AND
i_r . sortorder < r . sortorder
) ' ;
}
2006-09-20 21:00:45 +00:00
/**
2006-08-18 22:56:08 +00:00
* Prepare a SQL WHERE clause to select records where the given fields match the given values .
2006-09-20 21:00:45 +00:00
*
2006-08-18 22:56:08 +00:00
* Prepares a where clause of the form
* WHERE field1 = value1 AND field2 = value2 AND field3 = value3
* except that you need only specify as many arguments ( zero to three ) as you need .
2006-09-20 21:00:45 +00:00
*
2006-08-18 22:56:08 +00:00
* @ param string $field1 the first field to check ( optional ) .
* @ param string $value1 the value field1 must have ( requred if field1 is given , else optional ) .
* @ param string $field2 the second field to check ( optional ) .
* @ param string $value2 the value field2 must have ( requred if field2 is given , else optional ) .
* @ param string $field3 the third field to check ( optional ) .
* @ param string $value3 the value field3 must have ( requred if field3 is given , else optional ) .
*/
function where_clause ( $field1 = '' , $value1 = '' , $field2 = '' , $value2 = '' , $field3 = '' , $value3 = '' ) {
if ( $field1 ) {
$select = " WHERE $field1 = ' $value1 ' " ;
if ( $field2 ) {
$select .= " AND $field2 = ' $value2 ' " ;
if ( $field3 ) {
$select .= " AND $field3 = ' $value3 ' " ;
}
}
} else {
$select = '' ;
}
return $select ;
}
/**
* Get the data type of a table column , using an ADOdb MetaType () call .
*
* @ uses $CFG
* @ uses $db
* @ param string $table The name of the database table
* @ param string $column The name of the field in the table
* @ return string Field type or false if error
*/
function column_type ( $table , $column ) {
global $CFG , $db ;
if ( defined ( 'MDL_PERFDB' )) { global $PERF ; $PERF -> dbqueries ++ ; };
2006-09-24 09:59:25 +00:00
if ( ! $rs = $db -> Execute ( 'SELECT ' . $column . ' FROM ' . $CFG -> prefix . $table . ' WHERE 1=2' )) {
2006-08-18 22:56:08 +00:00
return false ;
}
$field = $rs -> FetchField ( 0 );
return $rs -> MetaType ( $field -> type );
}
2006-09-02 23:46:43 +00:00
/**
* This function will execute an array of SQL commands , returning
* true / false if any error is found and stopping / continue as desired .
* It ' s widely used by all the ddllib . php functions
*
* @ param array sqlarr array of sql statements to execute
2006-09-10 18:37:57 +00:00
* @ param boolean continue to specify if must continue on error ( true ) or stop ( false )
* @ param boolean feedback to specify to show status info ( true ) or not ( false )
2006-09-02 23:46:43 +00:00
* @ param boolean true if everything was ok , false if some error was found
*/
2006-09-03 17:56:34 +00:00
function execute_sql_arr ( $sqlarr , $continue = true , $feedback = true ) {
2006-09-02 23:46:43 +00:00
if ( ! is_array ( $sqlarr )) {
return false ;
}
$status = true ;
foreach ( $sqlarr as $sql ) {
2006-09-03 17:56:34 +00:00
if ( ! execute_sql ( $sql , $feedback )) {
2006-09-02 23:46:43 +00:00
$status = false ;
if ( ! $continue ) {
break ;
}
}
}
return $status ;
}
2006-09-01 17:45:02 +00:00
2006-08-20 18:21:33 +00:00
/**
* This function , called from setup . php includes all the configuration
* needed to properly work agains any DB . It setups connection encoding
* and some other variables .
*/
function configure_dbconnection () {
global $CFG , $db ;
switch ( $CFG -> dbtype ) {
case 'mysql' :
/// Set names if needed
if ( $CFG -> unicodedb ) {
$db -> Execute ( " SET NAMES 'utf8' " );
}
2006-08-27 21:57:33 +00:00
break ;
2006-08-20 18:21:33 +00:00
case 'postgres7' :
/// Set names if needed
if ( $CFG -> unicodedb ) {
$db -> Execute ( " SET NAMES 'utf8' " );
}
2006-08-27 21:57:33 +00:00
break ;
2006-08-20 18:21:33 +00:00
case 'mssql' :
/// No need to set charset. It must be specified in the driver conf
/// Allow quoted identifiers
$db -> Execute ( 'SET QUOTED_IDENTIFIER ON' );
2006-09-21 07:44:56 +00:00
/// Force ANSI nulls so the NULL check was done by IS NULL and NOT IS NULL
/// instead of equal(=) and distinct(<>) simbols
$db -> Execute ( 'SET ANSI_NULLS ON' );
2006-08-27 21:57:33 +00:00
/// Enable sybase quotes, so addslashes and stripslashes will use "'"
ini_set ( 'magic_quotes_sybase' , '1' );
/// NOTE: Not 100% useful because GPC has been addslashed with the setting off
/// so IT'S MANDATORY TO CHANGE THIS UNDER php.ini or .htaccess for this DB
break ;
case 'oci8po' :
2006-08-20 18:21:33 +00:00
/// No need to set charset. It must be specified by the NLS_LANG env. variable
2006-08-27 21:57:33 +00:00
/// Enable sybase quotes, so addslashes and stripslashes will use "'"
ini_set ( 'magic_quotes_sybase' , '1' );
/// NOTE: Not 100% useful because GPC has been addslashed with the setting off
/// so IT'S MANDATORY TO ENABLE THIS UNDER php.ini or .htaccess for this DB
break ;
2006-08-20 18:21:33 +00:00
}
}
2006-09-21 18:30:50 +00:00
/**
* This function will handle all the records before being inserted / updated to DB for Oracle
* installations . This is because the " special feature " of Oracle where the empty string is
* equal to NULL and this presents a problem with all our currently NOT NULL default '' fields .
*
* Once Moodle DB will be free of this sort of false NOT NULLS , this hack could be removed safely
*
* Note that this function is 100 % private and should be used , exclusively by DML functions
* in this file . Also , this is considered a DIRTY HACK to be removed when possible . ( stronk7 )
*
* @ param $table string the table where the record is going to be inserted / updated ( without prefix )
* @ param $dataobject object the object to be inserted / updated
* @ param $usecache boolean flag to determinate if we must use the per request cache of metadata
* true to use it , false to ignore and delete it
*/
function oracle_dirty_hack ( $table , & $dataobject , $usecache = true ) {
global $CFG , $db ;
/// Init and delete metadata cache
if ( ! isset ( $metadatacache ) || ! $usecache ) {
static $metadatacache = array ();
}
/// For Oracle DB, empty strings are converted to NULLs in DB
/// and this breaks a lot of NOT NULL columns currenty Moodle. In the future it's
/// planned to move some of them to NULL, if they must accept empty values and this
/// piece of code will become less and less used. But, for now, we need it.
/// What we are going to do is to examine all the data being inserted and if it's
/// an empty string (NULL for Oracle) and the field is defined as NOT NULL, we'll modify
/// such data in the best form possible ("0" for booleans and numbers and " " for the
/// rest of strings. It isn't optimal, but the only way to do so.
/// In the oppsite, when retrieving records from Oracle, we'll decode " " back to
/// empty strings to allow everything to work properly. DIRTY HACK.
/// If the db isn't Oracle, return without modif
if ( $CFG -> dbtype != 'oci8po' ) {
return ;
}
/// Get Meta info to know what to change, using the cached meta if exists
if ( ! isset ( $metadatacache [ $table ])) {
$metadatacache [ $table ] = array_change_key_case ( $db -> MetaColumns ( $CFG -> prefix . $table ), CASE_LOWER );
}
$columns = $metadatacache [ $table ];
/// Iterate over all the fields in the insert, transforming values
/// in the best possible form
foreach ( $dataobject as $fieldname => $fieldvalue ) {
/// If the field doesn't exist in metadata, skip
if ( ! isset ( $columns [ strtolower ( $fieldname )])) {
continue ;
}
/// If the field ins't VARCHAR or CLOB, skip
if ( $columns [ strtolower ( $fieldname )] -> type != 'VARCHAR2' && $columns [ strtolower ( $fieldname )] -> type != 'CLOB' ) {
continue ;
}
/// If the field isn't NOT NULL, skip (it's nullable, so accept empty values)
if ( ! $columns [ strtolower ( $fieldname )] -> not_null ) {
continue ;
}
/// If the value isn't empty, skip
if ( ! empty ( $fieldvalue )) {
continue ;
}
/// Now, we have one empty value, going to be inserted to one NOT NULL, VARCHAR2 or CLOB field
/// Try to get the best value to be inserted
if ( gettype ( $fieldvalue ) == 'boolean' ) {
$dataobject -> $fieldname = '0' ; /// Transform false to '0' that evaluates the same for PHP
} else if ( gettype ( $fieldvalue ) == 'integer' ) {
$dataobject -> $fieldname = '0' ; /// Transform 0 to '0' that evaluates the same for PHP
} else if ( gettype ( $fieldvalue ) == 'NULL' ) {
$dataobject -> $fieldname = '0' ; /// Transform NULL to '0' that evaluates the same for PHP
} else {
$dataobject -> $fieldname = ' ' ; /// Transform '' to ' ' that DONT'T EVALUATE THE SAME
/// (we'll transform back again on get_records_XXX functions and others)!!
}
}
}
/// End of DIRTY HACK
2006-08-20 18:21:33 +00:00
2006-08-18 22:56:08 +00:00
?>