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
2006-10-15 16:41:55 +00:00
$metadata_cache = array (); // Keeps copies of the MetaColumns() for each table used in one invocations
2006-12-27 22:39:32 +00:00
$rcache = new StdClass ; // Cache simple get_record results
$rcache -> data = array ();
$rcache -> hits = 0 ;
$rcache -> misses = 0 ;
2006-08-18 22:56:08 +00:00
/// 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 ;
}
2007-01-26 21:27:44 +00:00
if ( $CFG -> version >= 2006101007 ) { //Look for trailing ; from Moodle 1.7.0
$command = trim ( $command );
/// If the trailing ; is there, fix and warn!
if ( substr ( $command , strlen ( $command ) - 1 , 1 ) == ';' ) {
2007-01-27 23:37:22 +00:00
/// One noticeable exception, Oracle PL/SQL blocks require ending in ";"
2007-01-27 23:39:57 +00:00
if ( $CFG -> dbfamily == 'oracle' && substr ( $command , - 4 ) == 'END;' ) {
2007-01-27 23:37:22 +00:00
/// Nothing to fix/warn. The command is one PL/SQL block, so it's ok.
} else {
$command = trim ( $command , ';' );
debugging ( 'Warning. Avoid to end your SQL commands with a trailing ";".' , DEBUG_DEVELOPER );
}
2007-01-26 21:27:44 +00:00
}
}
2006-08-18 22:56:08 +00:00
$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
2006-11-04 16:22:49 +00:00
*
* @ deprecated Moodle 1.7 use the new XMLDB stuff in lib / ddllib . php
*
2006-08-18 22:56:08 +00:00
* @ 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 ;
2006-11-05 09:45:19 +00:00
if ( $CFG -> version > 2006101007 ) {
2006-11-04 16:22:49 +00:00
debugging ( 'Function modify_database() is deprecated. Replace it with the new XMLDB stuff.' , DEBUG_DEVELOPER );
}
2006-08-18 22:56:08 +00:00
$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 ///////////////////////////////////
2006-10-26 07:57:07 +00:00
2006-08-18 22:56:08 +00:00
/**
* 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 = '*' ) {
2006-10-26 07:57:07 +00:00
2006-12-27 22:39:32 +00:00
global $CFG ;
2006-10-26 07:57:07 +00:00
// Check to see whether this record is eligible for caching (fields=*, only condition is id)
$docache = false ;
2007-01-30 09:11:19 +00:00
if ( ! empty ( $CFG -> rcache ) && $CFG -> rcache === true && $field1 == 'id' && ! $field2 && ! $field3 && $fields == '*' ) {
2006-10-26 07:57:07 +00:00
$docache = true ;
// If it's in the cache, return it
2006-12-27 22:40:38 +00:00
$cached = rcache_getforfill ( $table , $value1 );
2006-12-27 22:39:32 +00:00
if ( ! empty ( $cached )) {
return $cached ;
2006-10-26 07:57:07 +00:00
}
}
2006-08-18 22:56:08 +00:00
$select = where_clause ( $field1 , $value1 , $field2 , $value2 , $field3 , $value3 );
2006-10-26 07:57:07 +00:00
$record = get_record_sql ( 'SELECT ' . $fields . ' FROM ' . $CFG -> prefix . $table . ' ' . $select );
2006-12-27 22:39:32 +00:00
// If we're caching records, store this one
// (supposing we got something - we don't cache failures)
2006-12-27 22:40:38 +00:00
if ( $docache ) {
if ( isset ( $record )) {
rcache_set ( $table , $value1 , $record );
} else {
rcache_releaseforfill ( $table , $value1 );
}
2006-10-26 07:57:07 +00:00
}
return $record ;
2006-08-18 22:56:08 +00:00
}
/**
* 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
2007-01-12 23:44:00 +00:00
if ( $CFG -> dbfamily == 'oracle' ) {
2006-09-07 23:48:48 +00:00
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 ( $sort ) {
$sort = ' ORDER BY ' . $sort ;
}
2006-10-22 22:37:30 +00:00
return get_recordset_sql ( 'SELECT ' . $fields . ' FROM ' . $CFG -> prefix . $table . $select . $sort , $limitfrom , $limitnum );
2006-08-18 22:56:08 +00:00
}
/**
* 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 )) {
2006-11-18 20:28:00 +00:00
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 ' )) {
2006-10-01 09:16:49 +00:00
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 ;
}
/**
2006-12-18 13:54:31 +00:00
* Utility function used by the following 4 methods . Note that for this to work , the first column
* in the recordset must contain unique values , as it is used as the key to the associative array .
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
2007-01-12 23:44:00 +00:00
if ( $CFG -> dbfamily == 'oracle' ) {
2006-09-07 23:48:48 +00:00
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 ;
2006-11-04 17:01:34 +00:00
/// Fallback in case we only have 1 field in the recordset. MDL-5877
} else if ( $rs -> _numOfFields == 1 && $records = $rs -> GetRows ()) {
foreach ( $records as $key => $record ) {
/// 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
2007-01-12 23:44:00 +00:00
if ( $CFG -> dbfamily == 'oracle' ) {
2006-11-04 17:01:34 +00:00
array_walk ( $record , 'onespace2empty' );
}
/// End of DIRTY HACK
$objects [ $record [ $firstcolumn -> name ]] = ( object ) $record ; /// The key is the first column value (like Assoc)
}
return $objects ;
2006-08-18 22:56:08 +00:00
} else {
return false ;
}
} else {
return false ;
}
}
2007-01-28 20:14:09 +00:00
/**
* This function is used to get the current record from the recordset . It
* doesn 't advance the recordset position. You' ll need to do that by
* using the rs_next_record ( $recordset ) function .
* @ param ADORecordSet the recordset to fetch current record from
* @ return ADOFetchObj the object containing the fetched information
*/
function rs_fetch_record ( & $rs ) {
2007-01-28 23:15:16 +00:00
global $CFG ;
2007-01-28 20:14:09 +00:00
$rec = $rs -> FetchObj (); //Retrieve record as object without advance the pointer
2007-01-28 23:15:16 +00:00
if ( $rs -> EOF ) { //FetchObj requires manual checking of EOF to detect if it's the last record
$rec = false ;
} else {
/// 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 -> dbfamily == 'oracle' ) {
$recarr = ( array ) $rec ; /// Cast to array
array_walk ( $recarr , 'onespace2empty' );
$rec = ( object ) $recarr ; /// Cast back to object
}
/// End DIRTY HACK
}
2007-01-28 20:14:09 +00:00
return $rec ;
}
/**
* This function is used to advance the pointer of the recordset
* to its next position / record .
* @ param ADORecordSet the recordset to be moved to the next record
* @ return boolean true if the movement was successful and false if not ( end of recordset )
*/
function rs_next_record ( & $rs ) {
return $rs -> MoveNext (); //Move the pointer to the next record
}
/**
* This function is used to get the current record from the recordset . It
2007-01-30 00:22:21 +00:00
* does advance the recordset position .
2007-01-28 20:14:09 +00:00
* This is the prefered way to iterate over recordsets with code blocks like this :
*
* $rs = get_recordset ( 'SELECT .....' );
* while ( $rec = rs_fetch_next_record ( $rs )) {
* /// Perform actions with the $rec record here
* }
* rs_close ( $rs ); /// Close the recordset if not used anymore. Saves memory (optional but recommended).
*
* @ param ADORecordSet the recordset to fetch current record from
* @ return mixed ADOFetchObj the object containing the fetched information or boolean false if no record ( end of recordset )
*/
function rs_fetch_next_record ( & $rs ) {
2007-01-28 23:15:16 +00:00
global $CFG ;
$rec = false ;
$recarr = $rs -> FetchRow (); //Retrieve record as object without advance the pointer. It's quicker that FetchNextObj()
if ( $recarr ) {
/// 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 -> dbfamily == 'oracle' ) {
array_walk ( $recarr , 'onespace2empty' );
}
/// End DIRTY HACK
/// Cast array to object
$rec = ( object ) $recarr ;
}
2007-01-28 20:14:09 +00:00
return $rec ;
}
/**
* This function closes the recordset , freeing all the memory and associated resources .
* Note that , once closed , the recordset must not be used anymore along the request .
* Saves memory ( optional but recommended ) .
* @ param ADORecordSet the recordset to be closed
*/
function rs_close ( & $rs ) {
$rs -> Close ();
}
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 ) .
2006-12-18 13:54:31 +00:00
* @ param string $fields a comma separated list of fields to return ( optional , by default
* all fields are returned ) . The first field will be used as key for the
* array so must be a unique field such as 'id' .
2006-08-18 22:56: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 ) .
* @ 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 ) .
2006-12-18 13:54:31 +00:00
* @ param string $fields a comma separated list of fields to return
* ( optional , by default all fields are returned ) . The first field will be used as key for the
* array so must be a unique field such as 'id' .
2006-08-18 22:56: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 ) .
* @ 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 )
2006-12-18 13:54:31 +00:00
* @ param string $fields A comma separated list of fields to be returned from the chosen table . If specified ,
* the first field should be a unique one such as 'id' since it will be used as a key in the associative
* array .
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 .
*/
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-12-18 13:54:31 +00:00
* @ param string $sql the SQL select query to execute . The first column of this SELECT statement
* must be a unique value ( usually the 'id' field ), as it will be used as the key of the
* returned array .
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
2007-01-12 23:44:00 +00:00
if ( $CFG -> dbfamily == 'oracle' ) {
2006-09-26 05:10:18 +00:00
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 ) .
2006-10-22 22:37:30 +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 associative array , or false if no records were found or an error occured .
*/
2006-10-22 22:37:30 +00:00
function get_records_menu ( $table , $field = '' , $value = '' , $sort = '' , $fields = '*' , $limitfrom = '' , $limitnum = '' ) {
$rs = get_recordset ( $table , $field , $value , $sort , $fields , $limitfrom , $limitnum );
2006-08-18 22:56:08 +00:00
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 .
2006-10-22 22:37:30 +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 associative array , or false if no records were found or an error occured .
*/
2006-10-22 22:37:30 +00:00
function get_records_select_menu ( $table , $select = '' , $sort = '' , $fields = '*' , $limitfrom = '' , $limitnum = '' ) {
$rs = get_recordset_select ( $table , $select , $sort , $fields , $limitfrom , $limitnum );
2006-08-18 22:56:08 +00:00
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 .
2006-10-22 22:37:30 +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 associative array , or false if no records were found or an error occured .
*/
2006-10-22 22:37:30 +00:00
function get_records_sql_menu ( $sql , $limitfrom = '' , $limitnum = '' ) {
$rs = get_recordset_sql ( $sql , $limitfrom , $limitnum );
2006-08-18 22:56:08 +00:00
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
2006-10-23 07:46:10 +00:00
/// Strip potential LIMIT uses arriving here, debugging them (MDL-7173)
$newsql = preg_replace ( '/ LIMIT [0-9, ]+$/is' , '' , $sql );
if ( $newsql != $sql ) {
debugging ( 'Incorrect use of LIMIT clause (not cross-db) in call to get_field_sql(): ' . $sql , DEBUG_DEVELOPER );
$sql = $newsql ;
}
2006-10-22 23:21:59 +00:00
$rs = get_recordset_sql ( $sql , 0 , 1 );
2006-08-18 22:56:08 +00:00
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
2007-01-12 23:44:00 +00:00
if ( $CFG -> dbfamily == 'oracle' ) {
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
2007-01-12 23:44:00 +00:00
if ( $CFG -> dbfamily == 'oracle' ) {
2006-09-26 05:10:18 +00:00
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 = '' ) {
2006-12-27 22:39:32 +00:00
global $CFG ;
2006-08-18 22:56:08 +00:00
2006-12-27 22:39:32 +00:00
// Clear record_cache based on the parameters passed
// (individual record or whole table)
2007-01-14 23:02:13 +00:00
if ( $CFG -> rcache === true ) {
2006-10-28 19:56:34 +00:00
if ( $field1 == 'id' ) {
2006-12-27 22:39:32 +00:00
rcache_unset ( $table , $value1 );
2006-10-28 19:56:34 +00:00
} else if ( $field2 == 'id' ) {
2006-12-27 22:39:32 +00:00
rcache_unset ( $table , $value1 );
2006-10-28 19:56:34 +00:00
} else if ( $field3 == 'id' ) {
2006-12-27 22:39:32 +00:00
rcache_unset ( $table , $value1 );
2006-10-28 19:56:34 +00:00
} else {
2006-12-27 22:39:32 +00:00
rcache_unset_table ( $table );
2006-10-28 17:53:00 +00:00
}
}
2006-08-18 22:56:08 +00:00
$select = where_clause ( $field1 , $value1 , $field2 , $value2 , $field3 , $value3 );
2006-12-15 18:23:31 +00:00
return set_field_select ( $table , $newfield , $newvalue , $select , true );
}
/**
* Set a single field in every table row where the select statement evaluates to true .
*
* @ 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 $select a fragment of SQL to be used in a where clause in the SQL call .
* @ param boolean $localcall Leave this set to false . ( Should only be set to true by set_field . )
* @ return mixed An ADODB RecordSet object with the results from the SQL call or false .
*/
function set_field_select ( $table , $newfield , $newvalue , $select , $localcall = false ) {
2006-12-27 22:39:32 +00:00
global $db , $CFG ;
2006-12-15 18:23:31 +00:00
if ( defined ( 'MDL_PERFDB' )) { global $PERF ; $PERF -> dbqueries ++ ; };
if ( ! $localcall ) {
if ( $select ) {
$select = 'WHERE ' . $select ;
}
2006-12-27 22:39:32 +00:00
// Clear record_cache based on the parameters passed
// (individual record or whole table)
2007-01-14 23:02:13 +00:00
if ( $CFG -> rcache === true ) {
2006-12-27 22:39:32 +00:00
rcache_unset_table ( $table );
2006-12-15 18:23:31 +00:00
}
}
2006-10-17 23:37:58 +00:00
$dataobject = new StdClass ;
$dataobject -> { $newfield } = $newvalue ;
2006-09-26 05:10:39 +00:00
// Oracle DIRTY HACK -
2007-01-12 23:44:00 +00:00
if ( $CFG -> dbfamily == 'oracle' ) {
2006-09-26 05:10:39 +00:00
oracle_dirty_hack ( $table , $dataobject ); // Convert object to the correct "empty" values for Oracle DB
$newvalue = $dataobject -> { $newfield };
}
2006-10-15 20:21:27 +00:00
// End DIRTY HACK
2006-09-26 05:10:39 +00:00
2006-10-17 23:37:58 +00:00
/// Under Oracle and MSSQL we have our own set field process
2006-10-15 20:21:27 +00:00
/// If the field being updated is clob/blob, we use our alternate update here
/// They will be updated later
2007-01-12 23:44:00 +00:00
if (( $CFG -> dbfamily == 'oracle' || $CFG -> dbfamily == 'mssql' ) && ! empty ( $select )) {
2006-10-15 20:21:27 +00:00
/// Detect lobs
$foundclobs = array ();
$foundblobs = array ();
2006-10-17 23:37:58 +00:00
db_detect_lobs ( $table , $dataobject , $foundclobs , $foundblobs );
2006-10-15 20:21:27 +00:00
}
2006-10-17 23:37:58 +00:00
/// Under Oracle and MSSQL, finally, Update all the Clobs and Blobs present in the record
2006-10-15 20:21:27 +00:00
/// if we know we have some of them in the query
2007-01-12 23:44:00 +00:00
if (( $CFG -> dbfamily == 'oracle' || $CFG -> dbfamily == 'mssql' ) && ! empty ( $select ) &&
2006-10-17 23:37:58 +00:00
( ! empty ( $foundclobs ) || ! empty ( $foundblobs ))) {
if ( ! db_update_lobs ( $table , $select , $foundclobs , $foundblobs )) {
2006-10-15 20:21:27 +00:00
return false ; //Some error happened while updating LOBs
} else {
return true ; //Everrything was ok
}
}
/// Arriving here, standard update
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 = '' ) {
2006-12-27 22:39:32 +00:00
global $db , $CFG ;
2006-08-18 22:56:08 +00:00
2006-12-27 22:39:32 +00:00
// Clear record_cache based on the parameters passed
// (individual record or whole table)
2007-01-14 23:02:13 +00:00
if ( $CFG -> rcache === true ) {
2006-10-28 19:56:34 +00:00
if ( $field1 == 'id' ) {
2006-12-27 22:39:32 +00:00
rcache_unset ( $table , $value1 );
2006-10-28 19:56:34 +00:00
} else if ( $field2 == 'id' ) {
2006-12-27 22:39:32 +00:00
rcache_unset ( $table , $value2 );
2006-10-28 19:56:34 +00:00
} else if ( $field3 == 'id' ) {
2006-12-27 22:39:32 +00:00
rcache_unset ( $table , $value3 );
2006-10-28 19:56:34 +00:00
} else {
2006-12-27 22:39:32 +00:00
rcache_unset_table ( $table );
2006-10-27 16:56:34 +00:00
}
}
2006-08-18 22:56:08 +00:00
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 = '' ) {
2006-12-27 22:39:32 +00:00
global $CFG , $db ;
2006-08-18 22:56:08 +00:00
2006-10-28 17:53:00 +00:00
// Clear record_cache (whole table)
2007-01-14 23:02:13 +00:00
if ( $CFG -> rcache === true ) {
2006-12-27 22:39:32 +00:00
rcache_unset_table ( $table );
2006-10-28 17:53:00 +00:00
}
2006-08-18 22:56:08 +00:00
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.
2007-01-12 23:44:00 +00:00
if ( $CFG -> dbfamily === 'postgres' && $returnid == true ) {
2006-08-18 22:56:08 +00:00
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-10-30 22:59:49 +00:00
/// Begin DIRTY HACK
2007-01-12 23:44:00 +00:00
if ( $CFG -> dbfamily == 'oracle' ) {
2006-10-30 22:59:49 +00:00
oracle_dirty_hack ( $table , $dataobject ); // Convert object to the correct "empty" values for Oracle DB
}
/// End DIRTY HACK
2006-10-30 23:13:48 +00:00
/// Under Oracle and MSSQL we have our own insert record process
2006-10-30 22:59:49 +00:00
/// detect all the clob/blob fields and change their contents to @#CLOB#@ and @#BLOB#@
/// saving them into $foundclobs and $foundblobs [$fieldname]->contents
/// Same for mssql (only processing blobs - image fields)
2007-01-12 23:44:00 +00:00
if ( $CFG -> dbfamily == 'oracle' || $CFG -> dbfamily == 'mssql' ) {
2006-10-30 22:59:49 +00:00
$foundclobs = array ();
$foundblobs = array ();
db_detect_lobs ( $table , $dataobject , $foundclobs , $foundblobs );
}
/// Under Oracle, if the primary key inserted has been requested OR
/// if there are LOBs to insert, we calculate the next value via
/// explicit query to the sequence.
/// Else, the pre-insert trigger will do the job, because the primary
/// key isn't needed at all by the rest of PHP code
2007-01-12 23:44:00 +00:00
if ( $CFG -> dbfamily === 'oracle' && ( $returnid == true || ! empty ( $foundclobs ) || ! empty ( $foundblobs ))) {
2006-08-27 09:02:41 +00:00
/// 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-10-23 17:46:08 +00:00
if ( $nextval = ( int ) $db -> GenID ( $seqname )) {
2006-09-20 21:00:45 +00:00
$dataobject -> { $primarykey } = $nextval ;
2007-01-28 01:07:52 +00:00
} else {
debugging ( 'Not able to get value from sequence ' . $seqname , DEBUG_DEVELOPER );
2006-08-27 09:02:41 +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-17 23:37:58 +00:00
/// Under Oracle and MSSQL, replace all the '@#CLOB#@' and '@#BLOB#@' ocurrences to proper default values
2006-10-08 09:58:11 +00:00
/// if we know we have some of them in the query
2007-01-12 23:44:00 +00:00
if (( $CFG -> dbfamily == 'oracle' || $CFG -> dbfamily == 'mssql' ) &&
2006-10-17 23:37:58 +00:00
( ! empty ( $foundclobs ) || ! empty ( $foundblobs ))) {
/// Initial configuration, based on DB
2007-01-12 23:44:00 +00:00
switch ( $CFG -> dbfamily ) {
case 'oracle' :
2006-10-17 23:37:58 +00:00
$clobdefault = 'empty_clob()' ; //Value of empty default clobs for this DB
$blobdefault = 'empty_blob()' ; //Value of empty default blobs for this DB
break ;
case 'mssql' :
$clobdefault = 'null' ; //Value of empty default clobs for this DB (under mssql this won't be executed
$blobdefault = 'null' ; //Value of empty default blobs for this DB
break ;
}
$insertSQL = str_replace ( " '@#CLOB#@' " , $clobdefault , $insertSQL );
$insertSQL = str_replace ( " '@#BLOB#@' " , $blobdefault , $insertSQL );
2006-10-08 09:58:11 +00:00
}
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
2007-01-12 23:44:00 +00:00
if ( $CFG -> dbfamily == 'oracle' &&
2006-10-17 23:37:58 +00:00
! empty ( $dataobject -> { $primarykey }) &&
( ! empty ( $foundclobs ) || ! empty ( $foundblobs ))) {
if ( ! db_update_lobs ( $table , $dataobject -> { $primarykey }, $foundclobs , $foundblobs )) {
2006-10-15 20:21:27 +00:00
return false ; //Some error happened while updating LOBs
}
2006-10-08 09:58:11 +00:00
}
2006-10-23 00:01:57 +00:00
/// If a return ID is not needed then just return true now (but not in MSSQL DBs, where we may have some pending tasks)
2007-01-12 23:44:00 +00:00
if ( ! $returnid && $CFG -> dbfamily != 'mssql' ) {
2006-08-18 22:56:08 +00:00
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 };
}
2006-10-23 00:01:57 +00:00
/// This only gets triggered with MySQL and MSQL 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
2007-01-12 23:44:00 +00:00
/// Under MSSQL all the Clobs and Blobs (IMAGE) present in the record
2006-10-17 23:37:58 +00:00
/// if we know we have some of them in the query
2007-01-12 23:44:00 +00:00
if (( $CFG -> dbfamily == 'mssql' ) &&
2006-10-17 23:37:58 +00:00
! empty ( $id ) &&
( ! empty ( $foundclobs ) || ! empty ( $foundblobs ))) {
if ( ! db_update_lobs ( $table , $id , $foundclobs , $foundblobs )) {
return false ; //Some error happened while updating LOBs
}
}
2007-01-12 23:44:00 +00:00
if ( $CFG -> dbfamily === 'postgres' ) {
2006-08-18 22:56:08 +00:00
// 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 ) {
2006-12-27 22:39:32 +00:00
global $db , $CFG ;
2006-08-18 22:56:08 +00:00
if ( ! isset ( $dataobject -> id ) ) {
return false ;
}
2006-10-26 07:57:07 +00:00
// Remove this record from record cache since it will change
2007-01-14 23:02:13 +00:00
if ( $CFG -> rcache === true ) {
2006-12-27 22:39:32 +00:00
rcache_unset ( $table , $dataobject -> id );
2006-10-27 02:15:02 +00:00
}
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
2007-01-12 23:44:00 +00:00
if ( $CFG -> dbfamily == 'oracle' ) {
2006-09-21 18:30:50 +00:00
oracle_dirty_hack ( $table , $dataobject ); // Convert object to the correct "empty" values for Oracle DB
}
/// End DIRTY HACK
2006-10-17 23:37:58 +00:00
/// Under Oracle and MSSQL we have our own update record process
2006-10-15 19:40:33 +00:00
/// detect all the clob/blob fields and delete them from the record being updated
/// saving them into $foundclobs and $foundblobs [$fieldname]->contents
/// They will be updated later
2007-01-12 23:44:00 +00:00
if (( $CFG -> dbfamily == 'oracle' || $CFG -> dbfamily == 'mssql' )
2006-10-17 23:37:58 +00:00
&& ! empty ( $dataobject -> id )) {
2006-10-15 19:40:33 +00:00
/// Detect lobs
$foundclobs = array ();
$foundblobs = array ();
2006-10-17 23:37:58 +00:00
db_detect_lobs ( $table , $dataobject , $foundclobs , $foundblobs , true );
2006-10-15 19:40:33 +00:00
}
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
2006-10-15 19:40:33 +00:00
$ddd = array ();
2006-08-18 22:56:08 +00:00
foreach ( $columns as $column ) {
if ( $column -> name <> 'id' and isset ( $data [ $column -> name ]) ) {
$ddd [ $column -> name ] = $data [ $column -> name ];
// PostgreSQL bytea support
2007-01-12 23:44:00 +00:00
if ( $CFG -> dbfamily == 'postgres' && $column -> type == 'bytea' ) {
2006-08-18 22:56:08 +00:00
$ddd [ $column -> name ] = $db -> BlobEncode ( $ddd [ $column -> name ]);
}
}
}
// Construct SQL queries
$numddd = count ( $ddd );
$count = 0 ;
$update = '' ;
2006-10-15 19:40:33 +00:00
/// Only if we have fields to be updated (this will prevent both wrong updates +
/// updates of only LOBs in Oracle
if ( $numddd ) {
foreach ( $ddd as $key => $value ) {
$count ++ ;
$update .= $key . ' = \'' . $value . '\'' ; // All incoming data is already quoted
if ( $count < $numddd ) {
$update .= ', ' ;
}
2006-08-18 22:56:08 +00:00
}
2006-10-15 19:40:33 +00:00
if ( ! $rs = $db -> Execute ( 'UPDATE ' . $CFG -> prefix . $table . ' SET ' . $update . ' WHERE id = \'' . $dataobject -> id . '\'' )) {
debugging ( $db -> ErrorMsg () . '<br /><br />UPDATE ' . $CFG -> prefix . $table . ' SET ' . $update . ' WHERE id = \'' . $dataobject -> id . '\'' );
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 ;
2006-08-18 22:56:08 +00:00
}
}
2006-10-15 19:40:33 +00:00
2006-10-17 23:37:58 +00:00
/// Under Oracle AND MSSQL, finally, Update all the Clobs and Blobs present in the record
2006-10-15 19:40:33 +00:00
/// if we know we have some of them in the query
2007-01-12 23:44:00 +00:00
if (( $CFG -> dbfamily == 'oracle' || $CFG -> dbfamily == 'mssql' ) &&
! empty ( $dataobject -> id ) &&
( ! empty ( $foundclobs ) || ! empty ( $foundblobs ))) {
2006-10-17 23:37:58 +00:00
if ( ! db_update_lobs ( $table , $dataobject -> id , $foundclobs , $foundblobs )) {
2006-10-15 20:21:27 +00:00
return false ; //Some error happened while updating LOBs
}
2006-10-15 19:40:33 +00:00
}
return true ;
2006-08-18 22:56:08 +00:00
}
/**
* 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 ;
2006-10-22 23:21:59 +00:00
debugging ( 'Function sql_paging_limit() is deprecated. Replace it with the correct use of limitfrom, limitnum parameters' , DEBUG_DEVELOPER );
2007-01-12 23:44:00 +00:00
switch ( $CFG -> dbfamily ) {
case 'postgres' :
2006-08-18 22:56:08 +00:00
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 ;
2007-01-12 23:44:00 +00:00
switch ( $CFG -> dbfamily ) {
case 'postgres' :
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 ;
2007-01-12 23:44:00 +00:00
switch ( $CFG -> dbfamily ) {
2006-08-18 22:56:08 +00:00
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
2006-10-30 17:41:55 +00:00
* @ deprecated Moodle 1.7 because coding guidelines now enforce to use AS in column aliases
2006-09-01 17:45:02 +00:00
*/
function sql_as () {
2006-09-04 22:30:56 +00:00
global $CFG , $db ;
2006-09-01 17:45:02 +00:00
2007-01-12 23:44:00 +00:00
switch ( $CFG -> dbfamily ) {
case 'postgres' :
2006-09-01 17:45:02 +00:00
return 'AS' ;
default :
return '' ;
}
}
2007-01-26 21:27:44 +00:00
/**
* Returns the empty string char used by every supported DB . To be used when
* we are searching for that values in our queries . Only Oracle uses this
* for now ( will be out , once we migrate to proper NULLs if that days arrives )
*/
function sql_empty () {
global $CFG ;
switch ( $CFG -> dbfamily ) {
case 'oracle' :
return ' ' ; //Only Oracle uses 1 white-space
default :
return '' ;
}
}
2006-10-30 18:31:08 +00:00
/**
* Returns the proper substr () function for each DB
* Relies on ADOdb $db -> substr property
*/
function sql_substr () {
global $db ;
return $db -> substr ;
}
2007-01-26 21:27:44 +00:00
/**
* Returns the SQL text to be used to compare one TEXT ( clob ) column with
* one varchar column , because some RDBMS doesn ' t support such direct
* comparisons .
* @ 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_compare_text ( $fieldname , $numchars = 32 ) {
return sql_order_by_text ( $fieldname , $numchars );
}
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 ;
2007-01-12 23:44:00 +00:00
switch ( $CFG -> dbfamily ) {
2006-09-09 13:51:40 +00:00
case 'mssql' :
return 'CONVERT(varchar, ' . $fieldname . ', ' . $numchars . ')' ;
break ;
2007-01-12 23:44:00 +00:00
case 'oracle' :
2006-09-09 13:51:40 +00:00
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
/**
2007-01-10 16:31:40 +00:00
* This internal function , called from setup . php , sets all the configuration
* needed to work properly against any DB . It setups connection encoding
* and some other variables . Also , ir defines the $CFG -> dbfamily variable
* to handle conditional code better than using $CFG -> dbtype directly .
2007-01-12 23:44:00 +00:00
*
* This function must contain the init code needed for each dbtype supported .
2006-08-20 18:21:33 +00:00
*/
function configure_dbconnection () {
global $CFG , $db ;
switch ( $CFG -> dbtype ) {
case 'mysql' :
2007-01-10 16:31:40 +00:00
case 'mysqli' :
2006-11-11 17:23:20 +00:00
$db -> Execute ( " SET NAMES 'utf8' " );
2006-08-27 21:57:33 +00:00
break ;
2006-08-20 18:21:33 +00:00
case 'postgres7' :
2006-11-11 17:23:20 +00:00
$db -> Execute ( " SET NAMES 'utf8' " );
2006-08-27 21:57:33 +00:00
break ;
2006-08-20 18:21:33 +00:00
case 'mssql' :
2006-10-15 16:41:55 +00:00
case 'mssql_n' :
2006-10-09 22:55:55 +00:00
case 'odbc_mssql' :
2006-08-20 18:21:33 +00:00
/// 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
2006-10-15 16:41:55 +00:00
/// or to turn off magic_quotes to allow Moodle to do it properly
2006-08-27 21:57:33 +00:00
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
2006-10-15 16:41:55 +00:00
/// or to turn off magic_quotes to allow Moodle to do it properly
2007-01-12 23:44:00 +00:00
break ;
}
/// Finally define dbfamily
set_dbfamily ();
}
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 )
*
2006-10-15 16:41:55 +00:00
* This function is private and must not be used outside dmllib at all
*
2006-09-21 18:30:50 +00:00
* @ 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 ) {
2006-10-15 16:41:55 +00:00
global $CFG , $db , $metadata_cache ;
2006-09-21 18:30:50 +00:00
/// Init and delete metadata cache
2006-10-15 16:41:55 +00:00
if ( ! isset ( $metadata_cache ) || ! $usecache ) {
$metadata_cache = array ();
2006-09-21 18:30:50 +00:00
}
/// 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
2007-01-12 23:44:00 +00:00
if ( $CFG -> dbfamily != 'oracle' ) {
2006-09-21 18:30:50 +00:00
return ;
}
/// Get Meta info to know what to change, using the cached meta if exists
2006-10-15 16:41:55 +00:00
if ( ! isset ( $metadata_cache [ $table ])) {
$metadata_cache [ $table ] = array_change_key_case ( $db -> MetaColumns ( $CFG -> prefix . $table ), CASE_LOWER );
2006-09-21 18:30:50 +00:00
}
2006-10-15 16:41:55 +00:00
$columns = $metadata_cache [ $table ];
2006-09-21 18:30:50 +00:00
/// 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-10-15 16:41:55 +00:00
/**
* This function will search for all the CLOBs and BLOBs fields passed in the dataobject , replacing
2006-10-17 18:31:56 +00:00
* their contents by the fixed strings '@#CLOB#@' and '@#BLOB#@' and returning one array for all the
2006-10-15 16:41:55 +00:00
* found CLOBS and another for all the found BLOBS
2006-10-17 23:37:58 +00:00
* Used by Oracle drivers to perform the two - step insertion / update of LOBs and
* by MSSQL to perform the same exclusively for BLOBs ( IMAGE fields )
2006-10-15 16:41:55 +00:00
*
* This function is private and must not be used outside dmllib at all
*
* @ 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 $clobs array of clobs detected
* @ param $dataobject array of blobs detected
2006-10-15 19:40:33 +00:00
* @ param $unset boolean to specify if we must unset found LOBs from the original object ( true ) or
* just return them modified to @ #CLOB#@ and @#BLOB#@ (false)
2006-10-15 16:41:55 +00:00
* @ 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
*/
2006-10-17 23:37:58 +00:00
function db_detect_lobs ( $table , & $dataobject , & $clobs , & $blobs , $unset = false , $usecache = true ) {
2006-10-15 16:41:55 +00:00
global $CFG , $db , $metadata_cache ;
2006-10-17 23:37:58 +00:00
$dataarray = ( array ) $dataobject ; //Convert to array. It's supposed that PHP 4.3 doesn't iterate over objects
/// Initial configuration, based on DB
2007-01-12 23:44:00 +00:00
switch ( $CFG -> dbfamily ) {
case 'oracle' :
2006-10-17 23:37:58 +00:00
$clobdbtype = 'CLOB' ; //Name of clobs for this DB
$blobdbtype = 'BLOB' ; //Name of blobs for this DB
break ;
case 'mssql' :
$clobdbtype = 'NOTPROCESSES' ; //Name of clobs for this DB (under mssql flavours we don't process CLOBS)
$blobdbtype = 'IMAGE' ; //Name of blobs for this DB
break ;
default :
return ; //Other DB doesn't need this two step to happen, prevent continue
}
2006-10-15 16:41:55 +00:00
/// Init and delete metadata cache
if ( ! isset ( $metadata_cache ) || ! $usecache ) {
$metadata_cache = array ();
}
/// Get Meta info to know what to change, using the cached meta if exists
if ( ! isset ( $metadata_cache [ $table ])) {
$metadata_cache [ $table ] = array_change_key_case ( $db -> MetaColumns ( $CFG -> prefix . $table ), CASE_LOWER );
}
$columns = $metadata_cache [ $table ];
2006-10-17 23:37:58 +00:00
foreach ( $dataarray as $fieldname => $fieldvalue ) {
2006-10-15 16:41:55 +00:00
/// If the field doesn't exist in metadata, skip
if ( ! isset ( $columns [ strtolower ( $fieldname )])) {
continue ;
}
/// If the field is CLOB, update its value to '@#CLOB#@' and store it in the $clobs array
2006-10-30 23:13:48 +00:00
if ( strtoupper ( $columns [ strtolower ( $fieldname )] -> type ) == $clobdbtype ) {
/// Oracle optimization. CLOBs under 4000cc can be directly inserted (no need to apply 2-phases to them)
2007-01-28 01:07:52 +00:00
if ( $CFG -> dbfamily == 'oracle' && strlen ( $dataobject -> $fieldname ) < 4000 ) {
2006-10-30 23:13:48 +00:00
continue ;
}
2006-10-15 16:41:55 +00:00
$clobs [ $fieldname ] = $dataobject -> $fieldname ;
2006-10-15 19:40:33 +00:00
if ( $unset ) {
unset ( $dataobject -> $fieldname );
} else {
$dataobject -> $fieldname = '@#CLOB#@' ;
}
2006-10-15 16:41:55 +00:00
continue ;
}
2006-10-17 23:37:58 +00:00
/// If the field is BLOB OR IMAGE, update its value to '@#BLOB#@' and store it in the $blobs array
2006-10-30 23:13:48 +00:00
if ( strtoupper ( $columns [ strtolower ( $fieldname )] -> type ) == $blobdbtype ) {
2006-10-17 18:31:56 +00:00
$blobs [ $fieldname ] = $dataobject -> $fieldname ;
2006-10-15 19:40:33 +00:00
if ( $unset ) {
unset ( $dataobject -> $fieldname );
} else {
$dataobject -> $fieldname = '@#BLOB#@' ;
}
2006-10-15 16:41:55 +00:00
continue ;
}
}
}
/**
* This function will iterate over $clobs and $blobs array , executing the needed
* UpdateClob () and UpdateBlob () ADOdb function calls to store LOBs contents properly
* Records to be updated are always searched by PK ( id always ! )
*
2006-10-17 23:37:58 +00:00
* Used by Orace CLOBS and BLOBS and MSSQL IMAGES
*
2006-10-15 16:41:55 +00:00
* This function is private and must not be used outside dmllib at all
*
* @ param $table string the table where the record is going to be inserted / updated ( without prefix )
2006-10-15 20:21:27 +00:00
* @ param $sqlcondition mixed value defining the records to be LOB - updated . It it ' s a number , must point
* to the PK og the table ( id field ), else it ' s processed as one harcoded SQL condition ( WHERE clause )
2006-10-15 16:41:55 +00:00
* @ param $clobs array of clobs to be updated
2006-10-15 18:37:01 +00:00
* @ param $blobs array of blobs to be updated
2006-10-15 16:41:55 +00:00
*/
2006-10-17 23:37:58 +00:00
function db_update_lobs ( $table , $sqlcondition , & $clobs , & $blobs ) {
2006-10-15 16:41:55 +00:00
global $CFG , $db ;
2006-10-15 20:21:27 +00:00
$status = true ;
2006-10-17 23:37:58 +00:00
/// Initial configuration, based on DB
2007-01-12 23:44:00 +00:00
switch ( $CFG -> dbfamily ) {
case 'oracle' :
2006-10-17 23:37:58 +00:00
$clobdbtype = 'CLOB' ; //Name of clobs for this DB
$blobdbtype = 'BLOB' ; //Name of blobs for this DB
break ;
case 'mssql' :
$clobdbtype = 'NOTPROCESSES' ; //Name of clobs for this DB (under mssql flavours we don't process CLOBS)
$blobdbtype = 'IMAGE' ; //Name of blobs for this DB
break ;
default :
return ; //Other DB doesn't need this two step to happen, prevent continue
2006-10-15 20:21:27 +00:00
}
/// Calculate the update sql condition
if ( is_numeric ( $sqlcondition )) { /// If passing a number, it's the PK of the table (id)
$sqlcondition = 'id=' . $sqlcondition ;
} else { /// Else, it's a formal standard SQL condition, we try to delete the WHERE in case it exists
$sqlcondition = trim ( preg_replace ( '/^WHERE/is' , '' , trim ( $sqlcondition )));
2006-10-15 16:41:55 +00:00
}
/// Update all the clobs
if ( $clobs ) {
foreach ( $clobs as $key => $value ) {
2006-10-27 17:49:25 +00:00
if ( defined ( 'MDL_PERFDB' )) { global $PERF ; $PERF -> dbqueries ++ ; }; /// Count the extra updates in PERF
2006-10-15 20:21:27 +00:00
if ( ! $db -> UpdateClob ( $CFG -> prefix . $table , $key , $value , $sqlcondition )) {
$status = false ;
$statement = " UpdateClob(' $CFG->prefix $table ', ' $key ', ' " . substr ( $value , 0 , 100 ) . " ...', ' $sqlcondition ') " ;
2006-10-15 18:37:01 +00:00
debugging ( $db -> ErrorMsg () . " <br /><br /> $statement " );
if ( ! empty ( $CFG -> dblogerror )) {
$debug = array_shift ( debug_backtrace ());
error_log ( " SQL " . $db -> ErrorMsg () . " in { $debug [ 'file' ] } on line { $debug [ 'line' ] } . STATEMENT: $statement " );
}
}
2006-10-15 16:41:55 +00:00
}
}
/// Update all the blobs
if ( $blobs ) {
foreach ( $blobs as $key => $value ) {
2006-10-27 17:49:25 +00:00
if ( defined ( 'MDL_PERFDB' )) { global $PERF ; $PERF -> dbqueries ++ ; }; /// Count the extra updates in PERF
2006-10-15 20:21:27 +00:00
if ( ! $db -> UpdateBlob ( $CFG -> prefix . $table , $key , $value , $sqlcondition )) {
$status = false ;
$statement = " UpdateBlob(' $CFG->prefix $table ', ' $key ', ' " . substr ( $value , 0 , 100 ) . " ...', ' $sqlcondition ') " ;
2006-10-15 18:37:01 +00:00
debugging ( $db -> ErrorMsg () . " <br /><br /> $statement " );
if ( ! empty ( $CFG -> dblogerror )) {
$debug = array_shift ( debug_backtrace ());
error_log ( " SQL " . $db -> ErrorMsg () . " in { $debug [ 'file' ] } on line { $debug [ 'line' ] } . STATEMENT: $statement " );
}
}
2006-10-15 16:41:55 +00:00
}
}
2006-10-15 20:21:27 +00:00
return $status ;
2006-10-15 16:41:55 +00:00
}
2006-12-27 22:40:38 +00:00
/**
* Set cached record .
*
* If you have called rcache_getforfill () before , it will also
* release the lock .
*
* This function is private and must not be used outside dmllib at all
*
* @ param $table string
* @ param $id integer
* @ param $rec obj
* @ return bool
*/
2006-12-27 22:39:32 +00:00
function rcache_set ( $table , $id , $rec ) {
2006-12-27 22:40:38 +00:00
global $CFG , $MCACHE , $rcache ;
2006-12-27 22:39:32 +00:00
2007-01-14 23:02:13 +00:00
if ( $CFG -> cachetype === 'internal' ) {
2006-12-27 22:40:38 +00:00
$rcache -> data [ $table ][ $id ] = $rec ;
} else {
2006-12-27 22:46:31 +00:00
$key = $table . '|' . $id ;
2006-12-27 22:40:38 +00:00
if ( isset ( $MCACHE )) {
2006-12-27 22:46:31 +00:00
// $table is a flag used to mark
2006-12-27 22:40:38 +00:00
// a table as dirty & uncacheable
// when an UPDATE or DELETE not bound by ID
// is taking place
2006-12-27 22:46:31 +00:00
if ( ! $MCACHE -> get ( $table )) {
// this will also release the _forfill lock
$MCACHE -> set ( $key , $rec , $CFG -> rcachettl );
2006-12-27 22:40:38 +00:00
}
}
}
2006-12-27 22:39:32 +00:00
return true ;
2006-12-27 22:40:38 +00:00
2006-12-27 22:39:32 +00:00
}
2006-12-27 22:40:38 +00:00
/**
* Unset cached record if it exists .
*
* This function is private and must not be used outside dmllib at all
*
* @ param $table string
* @ param $id integer
* @ return bool
*/
2006-12-27 22:39:32 +00:00
function rcache_unset ( $table , $id ) {
2006-12-27 22:40:38 +00:00
global $CFG , $MCACHE , $rcache ;
2006-12-27 22:39:32 +00:00
2007-01-14 23:02:13 +00:00
if ( $CFG -> cachetype === 'internal' ) {
2006-12-27 22:40:38 +00:00
if ( isset ( $rcache -> data [ $table ][ $id ])) {
unset ( $rcache -> data [ $table ][ $id ]);
}
} else {
2006-12-27 22:46:31 +00:00
$key = $table . '|' . $id ;
2006-12-27 22:40:38 +00:00
if ( isset ( $MCACHE )) {
$MCACHE -> delete ( $key );
}
2006-12-27 22:39:32 +00:00
}
return true ;
}
/**
* Get cached record if available . ONLY use if you
* are trying to get the cached record and will NOT
* fetch it yourself if not cached .
*
* Use rcache_getforfill () if you are going to fetch
* the record if not cached ...
*
* This function is private and must not be used outside dmllib at all
*
* @ param $table string
* @ param $id integer
* @ return mixed object - like record on cache hit , false otherwise
*/
function rcache_get ( $table , $id ) {
2006-12-27 22:40:38 +00:00
global $CFG , $MCACHE , $rcache ;
2006-12-27 22:39:32 +00:00
2007-01-14 23:02:13 +00:00
if ( $CFG -> cachetype === 'internal' ) {
2006-12-27 22:40:38 +00:00
if ( isset ( $rcache -> data [ $table ][ $id ])) {
$rcache -> hits ++ ;
return $rcache -> data [ $table ][ $id ];
} else {
$rcache -> misses ++ ;
return false ;
}
}
if ( isset ( $MCACHE )) {
2006-12-27 22:46:31 +00:00
$key = $table . '|' . $id ;
// we set $table as a flag used to mark
2006-12-27 22:40:38 +00:00
// a table as dirty & uncacheable
// when an UPDATE or DELETE not bound by ID
// is taking place
2006-12-27 22:46:31 +00:00
if ( $MCACHE -> get ( $table )) {
2006-12-27 22:40:38 +00:00
$rcache -> misses ++ ;
return false ;
} else {
2006-12-27 22:46:31 +00:00
$rec = $MCACHE -> get ( $key );
2006-12-27 22:40:38 +00:00
if ( ! empty ( $rec )) {
$rcache -> hits ++ ;
return $rec ;
} else {
$rcache -> misses ++ ;
return false ;
}
}
2006-12-27 22:39:32 +00:00
}
2006-12-27 22:40:38 +00:00
return false ;
2006-12-27 22:39:32 +00:00
}
/**
2006-12-27 22:55:12 +00:00
* Get cached record if available . In most cases you want
* to use this function -- namely if you are trying to get
* the cached record and will fetch it yourself if not cached .
* ( and set the cache ; - )
*
* Uses the getforfill caching mechanism . See lib / eaccelerator . class . php
* for a detailed description of the technique .
*
* Note : if you call rcache_getforfill () you are making an implicit promise
* that if the cache is empty , you will later populate it , or cancel the promise
* calling rcache_releaseforfill ();
2006-12-27 22:39:32 +00:00
*
* This function is private and must not be used outside dmllib at all
*
* @ param $table string
* @ param $id integer
* @ return mixed object - like record on cache hit , false otherwise
*/
function rcache_getforfill ( $table , $id ) {
2006-12-27 22:40:38 +00:00
global $CFG , $MCACHE , $rcache ;
2007-01-14 23:02:13 +00:00
if ( $CFG -> cachetype === 'internal' ) {
2006-12-27 22:40:38 +00:00
return rcache_get ( $table , $id );
}
if ( isset ( $MCACHE )) {
2006-12-27 22:46:31 +00:00
$key = $table . '|' . $id ;
// if $table is set - we won't take the
2006-12-27 22:40:38 +00:00
// lock either
2006-12-27 22:46:31 +00:00
if ( $MCACHE -> get ( $table )) {
2006-12-27 22:40:38 +00:00
$rcache -> misses ++ ;
return false ;
}
2006-12-27 22:46:31 +00:00
$rec = $MCACHE -> getforfill ( $key );
if ( ! empty ( $rec )) {
$rcache -> hits ++ ;
return $rec ;
}
$rcache -> misses ++ ;
return false ;
2006-12-27 22:40:38 +00:00
}
return false ;
2006-12-27 22:39:32 +00:00
}
/**
* Release the exclusive lock obtained by
* rcache_getforfill () . See rcache_getforfill ()
* for more details .
*
* This function is private and must not be used outside dmllib at all
*
* @ param $table string
* @ param $id integer
* @ return bool
*/
function rcache_releaseforfill ( $table , $id ) {
2006-12-27 22:46:31 +00:00
global $CFG , $MCACHE ;
2006-12-27 22:40:38 +00:00
if ( isset ( $MCACHE )) {
2006-12-27 22:46:31 +00:00
$key = $table . '|' . $id ;
return $MCACHE -> releaseforfill ( $key );
2006-12-27 22:40:38 +00:00
}
2006-12-27 22:39:32 +00:00
return true ;
}
/**
* Remove or invalidate all rcache entries related to
* a table . Not all caching mechanisms cluster entries
* by table so in those cases we use alternative strategies .
*
* This function is private and must not be used outside dmllib at all
*
* @ param $table string the table to invalidate records for
* @ return bool
*/
function rcache_unset_table ( $table ) {
2006-12-27 22:40:38 +00:00
global $CFG , $MCACHE , $rcache ;
2007-01-14 23:02:13 +00:00
if ( $CFG -> cachetype === 'internal' ) {
2006-12-27 22:40:38 +00:00
if ( isset ( $rcache -> data [ $table ])) {
unset ( $rcache -> data [ $table ]);
}
return true ;
}
if ( isset ( $MCACHE )) {
// at least as long as content keys to ensure they expire
// before the dirty flag
2006-12-27 22:46:31 +00:00
$MCACHE -> set ( $tablekey , true , $CFG -> rcachettl );
2006-12-27 22:39:32 +00:00
}
return true ;
}
2007-01-10 16:31:40 +00:00
?>