Moodle  2.2.1
http://www.collinsharper.com
C:/xampp/htdocs/moodle/lib/dml/sqlsrv_native_moodle_database.php
Go to the documentation of this file.
00001 <?php
00002 
00003 // This file is part of Moodle - http://moodle.org/
00004 //
00005 // Moodle is free software: you can redistribute it and/or modify
00006 // it under the terms of the GNU General Public License as published by
00007 // the Free Software Foundation, either version 2 of the License, or
00008 // (at your option) any later version.
00009 //
00010 // Moodle is distributed in the hope that it will be useful,
00011 // but WITHOUT ANY WARRANTY; without even the implied warranty of
00012 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00013 // GNU General Public License for more details.
00014 //
00015 // You should have received a copy of the GNU General Public License
00016 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
00017 
00027 defined('MOODLE_INTERNAL') || die();
00028 
00029 require_once($CFG->libdir.'/dml/moodle_database.php');
00030 require_once($CFG->libdir.'/dml/sqlsrv_native_moodle_recordset.php');
00031 require_once($CFG->libdir.'/dml/sqlsrv_native_moodle_temptables.php');
00032 
00036 class sqlsrv_native_moodle_database extends moodle_database {
00037 
00038     protected $sqlsrv = null;
00039     protected $last_error_reporting; // To handle SQL*Server-Native driver default verbosity
00040     protected $temptables; // Control existing temptables (sqlsrv_moodle_temptables object)
00041     protected $collation;  // current DB collation cache
00042 
00048     public function __construct($external=false) {
00049         parent::__construct($external);
00050     }
00051 
00057     public function driver_installed() {
00058         // use 'function_exists()' rather than 'extension_loaded()' because
00059         // the name used by 'extension_loaded()' is case specific! The extension
00060         // therefore *could be* mixed case and hence not found.
00061         if (!function_exists('sqlsrv_num_rows')) {
00062             return get_string('sqlsrvextensionisnotpresentinphp', 'install');
00063         }
00064         return true;
00065     }
00066 
00072     public function get_dbfamily() {
00073         return 'mssql';
00074     }
00075 
00081     protected function get_dbtype() {
00082         return 'sqlsrv';
00083     }
00084 
00090     protected function get_dblibrary() {
00091         return 'native';
00092     }
00093 
00099     public function get_name() {
00100         return get_string('nativesqlsrv', 'install');
00101     }
00102 
00108     public function get_configuration_help() {
00109         return get_string('nativesqlsrvhelp', 'install');
00110     }
00111 
00117     public function get_configuration_hints() {
00118         $str = get_string('databasesettingssub_sqlsrv', 'install');
00119         $str .= "<p style='text-align:right'><a href=\"javascript:void(0)\" ";
00120         $str .= "onclick=\"return window.open('http://docs.moodle.org/en/Using_the_Microsoft_SQL_Server_Driver_for_PHP')\"";
00121         $str .= ">";
00122         $str .= '<img src="pix/docs.gif'.'" alt="Docs" class="iconhelp" />';
00123         $str .= get_string('moodledocslink', 'install').'</a></p>';
00124         return $str;
00125     }
00126 
00139     public function connect($dbhost, $dbuser, $dbpass, $dbname, $prefix, array $dboptions=null) {
00140         $driverstatus = $this->driver_installed();
00141 
00142         if ($driverstatus !== true) {
00143             throw new dml_exception('dbdriverproblem', $driverstatus);
00144         }
00145 
00146         /*
00147          * Log all Errors.
00148          */
00149         sqlsrv_configure("WarningsReturnAsErrors", FALSE);
00150         sqlsrv_configure("LogSubsystems", SQLSRV_LOG_SYSTEM_ALL);
00151         sqlsrv_configure("LogSeverity", SQLSRV_LOG_SEVERITY_ERROR);
00152 
00153         $this->store_settings($dbhost, $dbuser, $dbpass, $dbname, $prefix, $dboptions);
00154         $this->sqlsrv = sqlsrv_connect($this->dbhost, array
00155          (
00156           'UID' => $this->dbuser,
00157           'PWD' => $this->dbpass,
00158           'Database' => $this->dbname,
00159           'CharacterSet' => 'UTF-8',
00160           'MultipleActiveResultSets' => true,
00161           'ConnectionPooling' => !empty($this->dboptions['dbpersist']),
00162           'ReturnDatesAsStrings' => true,
00163          ));
00164 
00165         if ($this->sqlsrv === false) {
00166             $this->sqlsrv = null;
00167             $dberr = $this->get_last_error();
00168 
00169             throw new dml_connection_exception($dberr);
00170         }
00171 
00172         // Allow quoted identifiers
00173         $sql = "SET QUOTED_IDENTIFIER ON";
00174         $this->query_start($sql, null, SQL_QUERY_AUX);
00175         $result = sqlsrv_query($this->sqlsrv, $sql);
00176         $this->query_end($result);
00177 
00178         $this->free_result($result);
00179 
00180         // Force ANSI nulls so the NULL check was done by IS NULL and NOT IS NULL
00181         // instead of equal(=) and distinct(<>) symbols
00182         $sql = "SET ANSI_NULLS ON";
00183         $this->query_start($sql, null, SQL_QUERY_AUX);
00184         $result = sqlsrv_query($this->sqlsrv, $sql);
00185         $this->query_end($result);
00186 
00187         $this->free_result($result);
00188 
00189         // Force ANSI warnings so arithmetic/string overflows will be
00190         // returning error instead of transparently truncating data
00191         $sql = "SET ANSI_WARNINGS ON";
00192         $this->query_start($sql, null, SQL_QUERY_AUX);
00193         $result = sqlsrv_query($this->sqlsrv, $sql);
00194         $this->query_end($result);
00195 
00196         // Concatenating null with anything MUST return NULL
00197         $sql = "SET CONCAT_NULL_YIELDS_NULL  ON";
00198         $this->query_start($sql, null, SQL_QUERY_AUX);
00199         $result = sqlsrv_query($this->sqlsrv, $sql);
00200         $this->query_end($result);
00201 
00202         $this->free_result($result);
00203 
00204         // Set transactions isolation level to READ_COMMITTED
00205         // prevents dirty reads when using transactions +
00206         // is the default isolation level of sqlsrv
00207         $sql = "SET TRANSACTION ISOLATION LEVEL READ COMMITTED";
00208         $this->query_start($sql, NULL, SQL_QUERY_AUX);
00209         $result = sqlsrv_query($this->sqlsrv, $sql);
00210         $this->query_end($result);
00211 
00212         $this->free_result($result);
00213 
00214         // Connection established and configured, going to instantiate the temptables controller
00215         $this->temptables = new sqlsrv_native_moodle_temptables($this);
00216 
00217         return true;
00218     }
00219 
00225     public function dispose() {
00226         parent::dispose(); // Call parent dispose to write/close session and other common stuff before closing connection
00227 
00228         if ($this->sqlsrv) {
00229             sqlsrv_close($this->sqlsrv);
00230             $this->sqlsrv = null;
00231         }
00232     }
00233 
00242     protected function query_start($sql, array $params = null, $type, $extrainfo = null) {
00243         parent::query_start($sql, $params, $type, $extrainfo);
00244     }
00245 
00251     protected function query_end($result) {
00252         parent::query_end($result);
00253     }
00254 
00259     public function get_server_info() {
00260         static $info;
00261 
00262         if (!$info) {
00263             $server_info = sqlsrv_server_info($this->sqlsrv);
00264 
00265             if ($server_info) {
00266                 $info['description'] = $server_info['SQLServerName'];
00267                 $info['version'] = $server_info['SQLServerVersion'];
00268                 $info['database'] = $server_info['CurrentDatabase'];
00269             }
00270         }
00271         return $info;
00272     }
00273 
00280     protected function is_min_version($version) {
00281         $server = $this->get_server_info();
00282         $server = $server['version'];
00283         return version_compare($server, $version, '>=');
00284     }
00285 
00293     protected function fix_table_names($sql) {
00294         if (preg_match_all('/\{([a-z][a-z0-9_]*)\}/i', $sql, $matches)) {
00295             foreach ($matches[0] as $key => $match) {
00296                 $name = $matches[1][$key];
00297 
00298                 if ($this->temptables->is_temptable($name)) {
00299                     $sql = str_replace($match, $this->temptables->get_correct_name($name), $sql);
00300                 } else {
00301                     $sql = str_replace($match, $this->prefix.$name, $sql);
00302                 }
00303             }
00304         }
00305         return $sql;
00306     }
00307 
00312     protected function allowed_param_types() {
00313         return SQL_PARAMS_QM;  // sqlsrv 1.1 can bind
00314     }
00315 
00320     public function get_last_error() {
00321         $retErrors = sqlsrv_errors(SQLSRV_ERR_ALL);
00322         $errorMessage = 'No errors found';
00323 
00324         if ($retErrors != null) {
00325             $errorMessage = '';
00326 
00327             foreach ($retErrors as $arrError) {
00328                 $errorMessage .= "SQLState: ".$arrError['SQLSTATE']."<br>\n";
00329                 $errorMessage .= "Error Code: ".$arrError['code']."<br>\n";
00330                 $errorMessage .= "Message: ".$arrError['message']."<br>\n";
00331             }
00332         }
00333 
00334         return $errorMessage;
00335     }
00336 
00337     /***
00338      * Bound variables *are* supported. Until I can get it to work, emulate the bindings
00339      * The challenge/problem/bug is that although they work, doing a SELECT SCOPE_IDENTITY()
00340      * doesn't return a value (no result set)
00341      */
00342 
00352     private function do_query($sql, $params, $sql_query_type, $free_result = true, $scrollable = false) {
00353         list($sql, $params, $type) = $this->fix_sql_params($sql, $params);
00354 
00355         $sql = $this->emulate_bound_params($sql, $params);
00356         $this->query_start($sql, $params, $sql_query_type);
00357         if (!$scrollable) { // Only supporting next row
00358             $result = sqlsrv_query($this->sqlsrv, $sql);
00359         } else { // Suporting absolute/relative rows
00360             $result = sqlsrv_query($this->sqlsrv, $sql, array(), array('Scrollable' => SQLSRV_CURSOR_STATIC));
00361         }
00362 
00363         if ($result === false) {
00364             // TODO do something with error or just use if DEV or DEBUG?
00365             $dberr = $this->get_last_error();
00366         }
00367 
00368         $this->query_end($result);
00369 
00370         if ($free_result) {
00371             $this->free_result($result);
00372             return true;
00373         }
00374         return $result;
00375     }
00376 
00381     public function get_tables($usecache = true) {
00382         if ($usecache and count($this->tables) > 0) {
00383             return $this->tables;
00384         }
00385         $this->tables = array ();
00386         $prefix = str_replace('_', '\\_', $this->prefix);
00387         $sql = "SELECT table_name
00388                   FROM information_schema.tables
00389                  WHERE table_name LIKE '$prefix%' ESCAPE '\\' AND table_type = 'BASE TABLE'";
00390 
00391         $this->query_start($sql, null, SQL_QUERY_AUX);
00392         $result = sqlsrv_query($this->sqlsrv, $sql);
00393         $this->query_end($result);
00394 
00395         if ($result) {
00396             while ($row = sqlsrv_fetch_array($result)) {
00397                 $tablename = reset($row);
00398                 if (strpos($tablename, $this->prefix) !== 0) {
00399                     continue;
00400                 }
00401                 $tablename = substr($tablename, strlen($this->prefix));
00402                 $this->tables[$tablename] = $tablename;
00403             }
00404             $this->free_result($result);
00405         }
00406 
00407         // Add the currently available temptables
00408         $this->tables = array_merge($this->tables, $this->temptables->get_temptables());
00409         return $this->tables;
00410     }
00411 
00416     public function get_indexes($table) {
00417         $indexes = array ();
00418         $tablename = $this->prefix.$table;
00419 
00420         // Indexes aren't covered by information_schema metatables, so we need to
00421         // go to sys ones. Skipping primary key indexes on purpose.
00422         $sql = "SELECT i.name AS index_name, i.is_unique, ic.index_column_id, c.name AS column_name
00423                   FROM sys.indexes i
00424                   JOIN sys.index_columns ic ON i.object_id = ic.object_id AND i.index_id = ic.index_id
00425                   JOIN sys.columns c ON ic.object_id = c.object_id AND ic.column_id = c.column_id
00426                   JOIN sys.tables t ON i.object_id = t.object_id
00427                  WHERE t.name = '$tablename' AND i.is_primary_key = 0
00428               ORDER BY i.name, i.index_id, ic.index_column_id";
00429 
00430         $this->query_start($sql, null, SQL_QUERY_AUX);
00431         $result = sqlsrv_query($this->sqlsrv, $sql);
00432         $this->query_end($result);
00433 
00434         if ($result) {
00435             $lastindex = '';
00436             $unique = false;
00437             $columns = array ();
00438 
00439             while ($row = sqlsrv_fetch_array($result, SQLSRV_FETCH_ASSOC)) {
00440                 if ($lastindex and $lastindex != $row['index_name'])
00441                     { // Save lastindex to $indexes and reset info
00442                     $indexes[$lastindex] = array
00443                      (
00444                       'unique' => $unique,
00445                       'columns' => $columns
00446                      );
00447 
00448                     $unique = false;
00449                     $columns = array ();
00450                 }
00451                 $lastindex = $row['index_name'];
00452                 $unique = empty($row['is_unique']) ? false : true;
00453                 $columns[] = $row['column_name'];
00454             }
00455 
00456             if ($lastindex) { // Add the last one if exists
00457                 $indexes[$lastindex] = array
00458                  (
00459                   'unique' => $unique,
00460                   'columns' => $columns
00461                  );
00462             }
00463 
00464             $this->free_result($result);
00465         }
00466         return $indexes;
00467     }
00468 
00475     public function get_columns($table, $usecache = true) {
00476         if ($usecache and isset($this->columns[$table])) {
00477             return $this->columns[$table];
00478         }
00479 
00480         $this->columns[$table] = array ();
00481 
00482         if (!$this->temptables->is_temptable($table)) { // normal table, get metadata from own schema
00483             $sql = "SELECT column_name AS name,
00484                            data_type AS type,
00485                            numeric_precision AS max_length,
00486                            character_maximum_length AS char_max_length,
00487                            numeric_scale AS scale,
00488                            is_nullable AS is_nullable,
00489                            columnproperty(object_id(quotename(table_schema) + '.' + quotename(table_name)), column_name, 'IsIdentity') AS auto_increment,
00490                            column_default AS default_value
00491                       FROM information_schema.columns
00492                      WHERE table_name = '{".$table."}'
00493                   ORDER BY ordinal_position";
00494         } else { // temp table, get metadata from tempdb schema
00495             $sql = "SELECT column_name AS name,
00496                            data_type AS type,
00497                            numeric_precision AS max_length,
00498                            character_maximum_length AS char_max_length,
00499                            numeric_scale AS scale,
00500                            is_nullable AS is_nullable,
00501                            columnproperty(object_id(quotename(table_schema) + '.' + quotename(table_name)), column_name, 'IsIdentity') AS auto_increment,
00502                            column_default AS default_value
00503                       FROM tempdb.information_schema.columns ".
00504             // check this statement
00505             // JOIN tempdb..sysobjects ON name = table_name
00506             // WHERE id = object_id('tempdb..{".$table."}')
00507                     "WHERE table_name LIKE '{".$table."}__________%'
00508                   ORDER BY ordinal_position";
00509         }
00510 
00511         list($sql, $params, $type) = $this->fix_sql_params($sql, null);
00512 
00513         $this->query_start($sql, null, SQL_QUERY_AUX);
00514         $result = sqlsrv_query($this->sqlsrv, $sql);
00515         $this->query_end($result);
00516 
00517         if (!$result) {
00518             return array ();
00519         }
00520 
00521         while ($rawcolumn = sqlsrv_fetch_array($result, SQLSRV_FETCH_ASSOC)) {
00522 
00523             $rawcolumn = (object)$rawcolumn;
00524 
00525             $info = new stdClass();
00526             $info->name = $rawcolumn->name;
00527             $info->type = $rawcolumn->type;
00528             $info->meta_type = $this->sqlsrvtype2moodletype($info->type);
00529 
00530             // Prepare auto_increment info
00531             $info->auto_increment = $rawcolumn->auto_increment ? true : false;
00532 
00533             // Define type for auto_increment columns
00534             $info->meta_type = ($info->auto_increment && $info->meta_type == 'I') ? 'R' : $info->meta_type;
00535 
00536             // id columns being auto_incremnt are PK by definition
00537             $info->primary_key = ($info->name == 'id' && $info->meta_type == 'R' && $info->auto_increment);
00538 
00539             // Put correct length for character and LOB types
00540             $info->max_length = $info->meta_type == 'C' ? $rawcolumn->char_max_length : $rawcolumn->max_length;
00541             $info->max_length = ($info->meta_type == 'X' || $info->meta_type == 'B') ? -1 : $info->max_length;
00542 
00543             // Scale
00544             $info->scale = $rawcolumn->scale ? $rawcolumn->scale : false;
00545 
00546             // Prepare not_null info
00547             $info->not_null = $rawcolumn->is_nullable == 'NO' ? true : false;
00548 
00549             // Process defaults
00550             $info->has_default = !empty($rawcolumn->default_value);
00551             if ($rawcolumn->default_value === NULL) {
00552                 $info->default_value = NULL;
00553             } else {
00554                 $info->default_value = preg_replace("/^[\(N]+[']?(.*?)[']?[\)]+$/", '\\1', $rawcolumn->default_value);
00555             }
00556 
00557             // Process binary
00558             $info->binary = $info->meta_type == 'B' ? true : false;
00559 
00560             $this->columns[$table][$info->name] = new database_column_info($info);
00561         }
00562         $this->free_result($result);
00563 
00564         return $this->columns[$table];
00565     }
00566 
00574     protected function normalise_value($column, $value) {
00575         if (is_bool($value)) {                               
00576             $value = (int)$value;
00577         }                                                    // And continue processing because text columns with numeric info need special handling below
00578 
00579         if ($column->meta_type == 'B')
00580             { // BLOBs need to be properly "packed", but can be inserted directly if so.
00581             if (!is_null($value)) {               // If value not null, unpack it to unquoted hexadecimal byte-string format
00582                 $value = unpack('H*hex', $value); // we leave it as array, so emulate_bound_params() can detect it
00583             }                                                // easily and "bind" the param ok.
00584 
00585         } else if ($column->meta_type == 'X') {             // sqlsrv doesn't cast from int to text, so if text column
00586             if (is_numeric($value)) { // and is numeric value then cast to string
00587                 $value = array('numstr' => (string)$value); // and put into array, so emulate_bound_params() will know how
00588             }                                                // to "bind" the param ok, avoiding reverse conversion to number
00589         } else if ($value === '') {
00590 
00591             if ($column->meta_type == 'I' or $column->meta_type == 'F' or $column->meta_type == 'N') {
00592                 $value = 0; // prevent '' problems in numeric fields
00593             }
00594         }
00595         return $value;
00596     }
00597 
00603     private function free_result($resource) {
00604         if (!is_bool($resource)) { // true/false resources cannot be freed
00605             return sqlsrv_free_stmt($resource);
00606         }
00607     }
00608 
00615     private function sqlsrvtype2moodletype($sqlsrv_type) {
00616         $type = null;
00617 
00618         switch (strtoupper($sqlsrv_type)) {
00619           case 'BIT':
00620            $type = 'L';
00621            break;
00622 
00623           case 'INT':
00624           case 'SMALLINT':
00625           case 'INTEGER':
00626           case 'BIGINT':
00627            $type = 'I';
00628            break;
00629 
00630           case 'DECIMAL':
00631           case 'REAL':
00632           case 'FLOAT':
00633            $type = 'N';
00634            break;
00635 
00636           case 'VARCHAR':
00637           case 'NVARCHAR':
00638            $type = 'C';
00639            break;
00640 
00641           case 'TEXT':
00642           case 'NTEXT':
00643           case 'VARCHAR(MAX)':
00644           case 'NVARCHAR(MAX)':
00645            $type = 'X';
00646            break;
00647 
00648           case 'IMAGE':
00649           case 'VARBINARY(MAX)':
00650            $type = 'B';
00651            break;
00652 
00653           case 'DATETIME':
00654            $type = 'D';
00655            break;
00656          }
00657 
00658         if (!$type) {
00659             throw new dml_exception('invalidsqlsrvnativetype', $sqlsrv_type);
00660         }
00661         return $type;
00662     }
00663 
00670     public function change_database_structure($sql) {
00671         $this->reset_caches();
00672 
00673         $this->query_start($sql, null, SQL_QUERY_STRUCTURE);
00674         $result = sqlsrv_query($this->sqlsrv, $sql);
00675         $this->query_end($result);
00676 
00677         return true;
00678     }
00679 
00683     protected function build_native_bound_params(array $params = null) {
00684 
00685         return null;
00686     }
00687 
00688 
00693     protected function emulate_bound_params($sql, array $params = null) {
00694 
00695         if (empty($params)) {
00696             return $sql;
00697         }
00699         $parts = explode('?', $sql);
00700         $return = array_shift($parts);
00701         foreach ($params as $param) {
00702             if (is_bool($param)) {
00703                 $return .= (int)$param;
00704             } else if (is_array($param) && isset($param['hex'])) { // detect hex binary, bind it specially
00705                 $return .= '0x'.$param['hex'];
00706             } else if (is_array($param) && isset($param['numstr'])) { // detect numerical strings that *must not*
00707                 $return .= "N'{$param['numstr']}'";                   // be converted back to number params, but bound as strings
00708             } else if (is_null($param)) {
00709                 $return .= 'NULL';
00710 
00711             } else if (is_number($param)) { // we can not use is_numeric() because it eats leading zeros from strings like 0045646
00712                 $return .= "'$param'"; // this is a hack for MDL-23997, we intentionally use string because it is compatible with both nvarchar and int types
00713             } else if (is_float($param)) {
00714                 $return .= $param;
00715             } else {
00716                 $param = str_replace("'", "''", $param);
00717                 $return .= "N'$param'";
00718             }
00719 
00720             $return .= array_shift($parts);
00721         }
00722         return $return;
00723     }
00724 
00733     public function execute($sql, array $params = null) {
00734         if (strpos($sql, ';') !== false) {
00735             throw new coding_exception('moodle_database::execute() Multiple sql statements found or bound parameters not used properly in query!');
00736         }
00737         $this->do_query($sql, $params, SQL_QUERY_UPDATE);
00738         return true;
00739     }
00740 
00757     public function get_recordset_sql($sql, array $params = null, $limitfrom = 0, $limitnum = 0) {
00758         $limitfrom = (int)$limitfrom;
00759         $limitnum = (int)$limitnum;
00760         $limitfrom = max(0, $limitfrom);
00761         $limitnum = max(0, $limitnum);
00762 
00763         if ($limitfrom or $limitnum) {
00764             if ($limitnum >= 1) { // Only apply TOP clause if we have any limitnum (limitfrom offset is handled later)
00765                 $fetch = $limitfrom + $limitnum;
00766                 if (PHP_INT_MAX - $limitnum < $limitfrom) { // Check PHP_INT_MAX overflow
00767                     $fetch = PHP_INT_MAX;
00768                 }
00769                 $sql = preg_replace('/^([\s(])*SELECT([\s]+(DISTINCT|ALL))?(?!\s*TOP\s*\()/i',
00770                                     "\\1SELECT\\2 TOP $fetch", $sql);
00771             }
00772         }
00773         $result = $this->do_query($sql, $params, SQL_QUERY_SELECT, false, (bool)$limitfrom);
00774 
00775         if ($limitfrom) { // Skip $limitfrom records
00776             sqlsrv_fetch($result, SQLSRV_SCROLL_ABSOLUTE, $limitfrom - 1);
00777         }
00778         return $this->create_recordset($result);
00779     }
00780 
00787     protected function create_recordset($result) {
00788         return new sqlsrv_native_moodle_recordset($result);
00789     }
00790 
00805     public function get_records_sql($sql, array $params = null, $limitfrom = 0, $limitnum = 0) {
00806 
00807         $rs = $this->get_recordset_sql($sql, $params, $limitfrom, $limitnum);
00808 
00809         $results = array();
00810 
00811         foreach ($rs as $row) {
00812             $id = reset($row);
00813 
00814             if (isset($results[$id])) {
00815                 $colname = key($row);
00816                 debugging("Did you remember to make the first column something unique in your call to get_records? Duplicate value '$id' found in column '$colname'.", DEBUG_DEVELOPER);
00817             }
00818             $results[$id] = (object)$row;
00819         }
00820         $rs->close();
00821 
00822         return $results;
00823     }
00824 
00833     public function get_fieldset_sql($sql, array $params = null) {
00834 
00835         $rs = $this->get_recordset_sql($sql, $params);
00836 
00837         $results = array ();
00838 
00839         foreach ($rs as $row) {
00840             $results[] = reset($row);
00841         }
00842         $rs->close();
00843 
00844         return $results;
00845     }
00846 
00857     public function insert_record_raw($table, $params, $returnid=true, $bulk=false, $customsequence=false) {
00858         if (!is_array($params)) {
00859             $params = (array)$params;
00860         }
00861         if ($customsequence) {
00862             if (!isset($params['id'])) {
00863                 throw new coding_exception('moodle_database::insert_record_raw() id field must be specified if custom sequences used.');
00864             }
00865             $returnid = false;
00866             // Disable IDENTITY column before inserting record with id
00867             $sql = 'SET IDENTITY_INSERT {'.$table.'} ON'; // Yes, it' ON!!
00868             $this->do_query($sql, null, SQL_QUERY_AUX);
00869 
00870         } else {
00871             unset($params['id']);
00872         }
00873 
00874         if (empty($params)) {
00875             throw new coding_exception('moodle_database::insert_record_raw() no fields found.');
00876         }
00877         $fields = implode(',', array_keys($params));
00878         $qms = array_fill(0, count($params), '?');
00879         $qms = implode(',', $qms);
00880         $sql = "INSERT INTO {" . $table . "} ($fields) VALUES($qms)";
00881         $query_id = $this->do_query($sql, $params, SQL_QUERY_INSERT);
00882 
00883         if ($customsequence) {
00884             // Enable IDENTITY column after inserting record with id
00885             $sql = 'SET IDENTITY_INSERT {'.$table.'} OFF'; // Yes, it' OFF!!
00886             $this->do_query($sql, null, SQL_QUERY_AUX);
00887         }
00888 
00889         if ($returnid) {
00890             $id = $this->sqlsrv_fetch_id();
00891             return $id;
00892         } else {
00893             return true;
00894         }
00895     }
00896 
00902     private function sqlsrv_fetch_id() {
00903         $query_id = sqlsrv_query($this->sqlsrv, 'SELECT SCOPE_IDENTITY()');
00904         if ($query_id === false) {
00905             $dberr = $this->get_last_error();
00906             return false;
00907         }
00908         $row = $this->sqlsrv_fetchrow($query_id);
00909         return (int)$row[0];
00910     }
00911 
00917     private function sqlsrv_fetchrow($query_id) {
00918         $row = sqlsrv_fetch_array($query_id, SQLSRV_FETCH_NUMERIC);
00919         if ($row === false) {
00920             $dberr = $this->get_last_error();
00921             return false;
00922         }
00923 
00924         foreach ($row as $key => $value) {
00925             $row[$key] = ($value === ' ' || $value === NULL) ? '' : $value;
00926         }
00927         return $row;
00928     }
00929 
00942     public function insert_record($table, $dataobject, $returnid = true, $bulk = false) {
00943         $dataobject = (array)$dataobject;
00944 
00945         $columns = $this->get_columns($table);
00946         $cleaned = array ();
00947 
00948         foreach ($dataobject as $field => $value) {
00949             if ($field === 'id') {
00950                 continue;
00951             }
00952             if (!isset($columns[$field])) {
00953                 continue;
00954             }
00955             $column = $columns[$field];
00956             $cleaned[$field] = $this->normalise_value($column, $value);
00957         }
00958 
00959         return $this->insert_record_raw($table, $cleaned, $returnid, $bulk);
00960     }
00961 
00971     public function import_record($table, $dataobject) {
00972         if (!is_object($dataobject)) {
00973             $dataobject = (object)$dataobject;
00974         }
00975 
00976         $columns = $this->get_columns($table);
00977         $cleaned = array ();
00978 
00979         foreach ($dataobject as $field => $value) {
00980             if (!isset($columns[$field])) {
00981                 continue;
00982             }
00983             $column = $columns[$field];
00984             $cleaned[$field] = $this->normalise_value($column, $value);
00985         }
00986 
00987         $this->insert_record_raw($table, $cleaned, false, false, true);
00988 
00989         return true;
00990     }
00991 
01000     public function update_record_raw($table, $params, $bulk = false) {
01001         $params = (array)$params;
01002 
01003         if (!isset($params['id'])) {
01004             throw new coding_exception('moodle_database::update_record_raw() id field must be specified.');
01005         }
01006         $id = $params['id'];
01007         unset($params['id']);
01008 
01009         if (empty($params)) {
01010             throw new coding_exception('moodle_database::update_record_raw() no fields found.');
01011         }
01012 
01013         $sets = array ();
01014 
01015         foreach ($params as $field => $value) {
01016             $sets[] = "$field = ?";
01017         }
01018 
01019         $params[] = $id; // last ? in WHERE condition
01020 
01021         $sets = implode(',', $sets);
01022         $sql = "UPDATE {".$table."} SET $sets WHERE id = ?";
01023 
01024         $this->do_query($sql, $params, SQL_QUERY_UPDATE);
01025 
01026         return true;
01027     }
01028 
01042     public function update_record($table, $dataobject, $bulk = false) {
01043         $dataobject = (array)$dataobject;
01044 
01045         $columns = $this->get_columns($table);
01046         $cleaned = array ();
01047 
01048         foreach ($dataobject as $field => $value) {
01049             if (!isset($columns[$field])) {
01050                 continue;
01051             }
01052             $column = $columns[$field];
01053             $cleaned[$field] = $this->normalise_value($column, $value);
01054         }
01055 
01056         return $this->update_record_raw($table, $cleaned, $bulk);
01057     }
01058 
01070     public function set_field_select($table, $newfield, $newvalue, $select, array $params = null) {
01071         if ($select) {
01072             $select = "WHERE $select";
01073         }
01074 
01075         if (is_null($params)) {
01076             $params = array ();
01077         }
01078 
01079         // convert params to ? types
01080         list($select, $params, $type) = $this->fix_sql_params($select, $params);
01081 
01083         $columns = $this->get_columns($table);
01084         $column = $columns[$newfield];
01085 
01086         $newvalue = $this->normalise_value($column, $newvalue);
01087 
01088         if (is_null($newvalue)) {
01089             $newfield = "$newfield = NULL";
01090         } else {
01091             $newfield = "$newfield = ?";
01092             array_unshift($params, $newvalue);
01093         }
01094         $sql = "UPDATE {".$table."} SET $newfield $select";
01095 
01096         $this->do_query($sql, $params, SQL_QUERY_UPDATE);
01097 
01098         return true;
01099     }
01100 
01110     public function delete_records_select($table, $select, array $params = null) {
01111         if ($select) {
01112             $select = "WHERE $select";
01113         }
01114 
01115         $sql = "DELETE FROM {".$table."} $select";
01116 
01117         // we use SQL_QUERY_UPDATE because we do not know what is in general SQL, delete constant would not be accurate
01118         $this->do_query($sql, $params, SQL_QUERY_UPDATE);
01119 
01120         return true;
01121     }
01122 
01123 
01125 
01126     public function sql_cast_char2int($fieldname, $text = false) {
01127         if (!$text) {
01128             return ' CAST(' . $fieldname . ' AS INT) ';
01129         } else {
01130             return ' CAST(' . $this->sql_compare_text($fieldname) . ' AS INT) ';
01131         }
01132     }
01133 
01134     public function sql_cast_char2real($fieldname, $text=false) {
01135         if (!$text) {
01136             return ' CAST(' . $fieldname . ' AS REAL) ';
01137         } else {
01138             return ' CAST(' . $this->sql_compare_text($fieldname) . ' AS REAL) ';
01139         }
01140     }
01141 
01142     public function sql_ceil($fieldname) {
01143         return ' CEILING('.$fieldname.')';
01144     }
01145 
01146     protected function get_collation() {
01147         if (isset($this->collation)) {
01148             return $this->collation;
01149         }
01150         if (!empty($this->dboptions['dbcollation'])) {
01151             // perf speedup
01152             $this->collation = $this->dboptions['dbcollation'];
01153             return $this->collation;
01154         }
01155 
01156         // make some default
01157         $this->collation = 'Latin1_General_CI_AI';
01158 
01159         $sql = "SELECT CAST(DATABASEPROPERTYEX('$this->dbname', 'Collation') AS varchar(255)) AS SQLCollation";
01160         $this->query_start($sql, null, SQL_QUERY_AUX);
01161         $result = sqlsrv_query($this->sqlsrv, $sql);
01162         $this->query_end($result);
01163 
01164         if ($result) {
01165             if ($rawcolumn = sqlsrv_fetch_array($result, SQLSRV_FETCH_ASSOC)) {
01166                 $this->collation = reset($rawcolumn);
01167             }
01168             $this->free_result($result);
01169         }
01170 
01171         return $this->collation;
01172     }
01173 
01185     public function sql_like($fieldname, $param, $casesensitive = true, $accentsensitive = true, $notlike = false, $escapechar = '\\') {
01186         if (strpos($param, '%') !== false) {
01187             debugging('Potential SQL injection detected, sql_ilike() expects bound parameters (? or :named)');
01188         }
01189 
01190         $collation = $this->get_collation();
01191         $LIKE = $notlike ? 'NOT LIKE' : 'LIKE';
01192 
01193         if ($casesensitive) {
01194             $collation = str_replace('_CI', '_CS', $collation);
01195         } else {
01196             $collation = str_replace('_CS', '_CI', $collation);
01197         }
01198         if ($accentsensitive) {
01199             $collation = str_replace('_AI', '_AS', $collation);
01200         } else {
01201             $collation = str_replace('_AS', '_AI', $collation);
01202         }
01203 
01204         return "$fieldname COLLATE $collation $LIKE $param ESCAPE '$escapechar'";
01205     }
01206 
01207     public function sql_concat() {
01208         $arr = func_get_args();
01209 
01210         foreach ($arr as $key => $ele) {
01211             $arr[$key] = ' CAST('.$ele.' AS VARCHAR(255)) ';
01212         }
01213         $s = implode(' + ', $arr);
01214 
01215         if ($s === '') {
01216             return " '' ";
01217         }
01218         return " $s ";
01219     }
01220 
01221     public function sql_concat_join($separator = "' '", $elements = array ()) {
01222         for ($n = count($elements) - 1; $n > 0; $n--) {
01223             array_splice($elements, $n, 0, $separator);
01224         }
01225         $s = implode(' + ', $elements);
01226 
01227         if ($s === '') {
01228             return " '' ";
01229         }
01230         return " $s ";
01231     }
01232 
01233     public function sql_isempty($tablename, $fieldname, $nullablefield, $textfield) {
01234         if ($textfield) {
01235             return ' ('.$this->sql_compare_text($fieldname)." = '') ";
01236         } else {
01237             return " ($fieldname = '') ";
01238         }
01239     }
01240 
01246     public function sql_length($fieldname) {
01247         return ' LEN('.$fieldname.')';
01248     }
01249 
01250     public function sql_order_by_text($fieldname, $numchars = 32) {
01251         return ' CONVERT(varchar, '.$fieldname.', '.$numchars.')';
01252     }
01253 
01257     public function sql_position($needle, $haystack) {
01258         return "CHARINDEX(($needle), ($haystack))";
01259     }
01260 
01270     public function sql_substr($expr, $start, $length = false) {
01271         if (count(func_get_args()) < 2) {
01272             throw new coding_exception('moodle_database::sql_substr() requires at least two parameters',
01273                 'Originally this function was only returning name of SQL substring function, it now requires all parameters.');
01274         }
01275 
01276         if ($length === false) {
01277             return "SUBSTRING($expr, $start, (LEN($expr) - $start + 1))";
01278         } else {
01279             return "SUBSTRING($expr, $start, $length)";
01280         }
01281     }
01282 
01284 
01285     public function session_lock_supported() {
01286         return true;
01287     }
01288 
01295     public function get_session_lock($rowid, $timeout) {
01296         if (!$this->session_lock_supported()) {
01297             return;
01298         }
01299         parent::get_session_lock($rowid, $timeout);
01300 
01301         $timeoutmilli = $timeout * 1000;
01302 
01303         $fullname = $this->dbname.'-'.$this->prefix.'-session-'.$rowid;
01304         // While this may work using proper {call sp_...} calls + binding +
01305         // executing + consuming recordsets, the solution used for the mssql
01306         // driver is working perfectly, so 100% mimic-ing that code.
01307         // $sql = "sp_getapplock '$fullname', 'Exclusive', 'Session',  $timeoutmilli";
01308         $sql = "BEGIN
01309                     DECLARE @result INT
01310                     EXECUTE @result = sp_getapplock @Resource='$fullname',
01311                                                     @LockMode='Exclusive',
01312                                                     @LockOwner='Session',
01313                                                     @LockTimeout='$timeoutmilli'
01314                     SELECT @result
01315                 END";
01316         $this->query_start($sql, null, SQL_QUERY_AUX);
01317         $result = sqlsrv_query($this->sqlsrv, $sql);
01318         $this->query_end($result);
01319 
01320         if ($result) {
01321             $row = sqlsrv_fetch_array($result);
01322             if ($row[0] < 0) {
01323                 throw new dml_sessionwait_exception();
01324             }
01325         }
01326 
01327         $this->free_result($result);
01328     }
01329 
01330     public function release_session_lock($rowid) {
01331         if (!$this->session_lock_supported()) {
01332             return;
01333         }
01334         parent::release_session_lock($rowid);
01335 
01336         $fullname = $this->dbname.'-'.$this->prefix.'-session-'.$rowid;
01337         $sql = "sp_releaseapplock '$fullname', 'Session'";
01338         $this->query_start($sql, null, SQL_QUERY_AUX);
01339         $result = sqlsrv_query($this->sqlsrv, $sql);
01340         $this->query_end($result);
01341         $this->free_result($result);
01342     }
01343 
01344 
01346 
01347     // NOTE:
01348     // TODO -- should these be wrapped in query start/end? They arn't a query
01349     // but information and error capture is nice. msk
01350 
01351 
01357     protected function begin_transaction() {
01358         $this->query_start('native sqlsrv_begin_transaction', NULL, SQL_QUERY_AUX);
01359         $result = sqlsrv_begin_transaction($this->sqlsrv);
01360         $this->query_end($result);
01361     }
01362 
01368     protected function commit_transaction() {
01369         $this->query_start('native sqlsrv_commit', NULL, SQL_QUERY_AUX);
01370         $result = sqlsrv_commit($this->sqlsrv);
01371         $this->query_end($result);
01372     }
01373 
01379     protected function rollback_transaction() {
01380         $this->query_start('native sqlsrv_rollback', NULL, SQL_QUERY_AUX);
01381         $result = sqlsrv_rollback($this->sqlsrv);
01382         $this->query_end($result);
01383     }
01384 }
 All Data Structures Namespaces Files Functions Variables Enumerations