Moodle  2.2.1
http://www.collinsharper.com
C:/xampp/htdocs/moodle/lib/dml/mssql_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 3 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 
00018 
00028 defined('MOODLE_INTERNAL') || die();
00029 
00030 require_once($CFG->libdir.'/dml/moodle_database.php');
00031 require_once($CFG->libdir.'/dml/mssql_native_moodle_recordset.php');
00032 require_once($CFG->libdir.'/dml/mssql_native_moodle_temptables.php');
00033 
00037 class mssql_native_moodle_database extends moodle_database {
00038 
00039     protected $mssql     = null;
00040     protected $last_error_reporting; // To handle mssql driver default verbosity
00041     protected $collation;  // current DB collation cache
00042 
00048     public function driver_installed() {
00049         if (!function_exists('mssql_connect')) {
00050             return get_string('mssqlextensionisnotpresentinphp', 'install');
00051         }
00052         return true;
00053     }
00054 
00060     public function get_dbfamily() {
00061         return 'mssql';
00062     }
00063 
00069     protected function get_dbtype() {
00070         return 'mssql';
00071     }
00072 
00078     protected function get_dblibrary() {
00079         return 'native';
00080     }
00081 
00087     public function get_name() {
00088         return get_string('nativemssql', 'install');
00089     }
00090 
00096     public function get_configuration_help() {
00097         return get_string('nativemssqlhelp', 'install');
00098     }
00099 
00105     public function get_configuration_hints() {
00106         $str = get_string('databasesettingssub_mssql', 'install');
00107         $str .= "<p style='text-align:right'><a href=\"javascript:void(0)\" ";
00108         $str .= "onclick=\"return window.open('http://docs.moodle.org/en/Installing_MSSQL_for_PHP')\"";
00109         $str .= ">";
00110         $str .= '<img src="pix/docs.gif' . '" alt="Docs" class="iconhelp" />';
00111         $str .= get_string('moodledocslink', 'install') . '</a></p>';
00112         return $str;
00113     }
00114 
00127     public function connect($dbhost, $dbuser, $dbpass, $dbname, $prefix, array $dboptions=null) {
00128         if ($prefix == '' and !$this->external) {
00129             //Enforce prefixes for everybody but mysql
00130             throw new dml_exception('prefixcannotbeempty', $this->get_dbfamily());
00131         }
00132 
00133         $driverstatus = $this->driver_installed();
00134 
00135         if ($driverstatus !== true) {
00136             throw new dml_exception('dbdriverproblem', $driverstatus);
00137         }
00138 
00139         $this->store_settings($dbhost, $dbuser, $dbpass, $dbname, $prefix, $dboptions);
00140 
00141         $dbhost = $this->dbhost;
00142         if (isset($dboptions['dbport'])) {
00143             if (stristr(PHP_OS, 'win') && !stristr(PHP_OS, 'darwin')) {
00144                 $dbhost .= ','.$dboptions['dbport'];
00145             } else {
00146                 $dbhost .= ':'.$dboptions['dbport'];
00147             }
00148         }
00149         ob_start();
00150         if (!empty($this->dboptions['dbpersist'])) { // persistent connection
00151             $this->mssql = mssql_pconnect($dbhost, $this->dbuser, $this->dbpass, true);
00152         } else {
00153             $this->mssql = mssql_connect($dbhost, $this->dbuser, $this->dbpass, true);
00154         }
00155         $dberr = ob_get_contents();
00156         ob_end_clean();
00157 
00158         if ($this->mssql === false) {
00159             $this->mssql = null;
00160             throw new dml_connection_exception($dberr);
00161         }
00162 
00163         // already connected, select database and set some env. variables
00164         $this->query_start("--mssql_select_db", null, SQL_QUERY_AUX);
00165         $result = mssql_select_db($this->dbname, $this->mssql);
00166         $this->query_end($result);
00167 
00168         // No need to set charset. It's UTF8, with transparent conversions
00169         // back and forth performed both by FreeTDS or ODBTP
00170 
00171         // Allow quoted identifiers
00172         $sql = "SET QUOTED_IDENTIFIER ON";
00173         $this->query_start($sql, null, SQL_QUERY_AUX);
00174         $result = mssql_query($sql, $this->mssql);
00175         $this->query_end($result);
00176 
00177         $this->free_result($result);
00178 
00179         // Force ANSI nulls so the NULL check was done by IS NULL and NOT IS NULL
00180         // instead of equal(=) and distinct(<>) symbols
00181         $sql = "SET ANSI_NULLS ON";
00182         $this->query_start($sql, null, SQL_QUERY_AUX);
00183         $result = mssql_query($sql, $this->mssql);
00184         $this->query_end($result);
00185 
00186         $this->free_result($result);
00187 
00188         // Force ANSI warnings so arithmetic/string overflows will be
00189         // returning error instead of transparently truncating data
00190         $sql = "SET ANSI_WARNINGS ON";
00191         $this->query_start($sql, null, SQL_QUERY_AUX);
00192         $result = mssql_query($sql, $this->mssql);
00193         $this->query_end($result);
00194 
00195         // Concatenating null with anything MUST return NULL
00196         $sql = "SET CONCAT_NULL_YIELDS_NULL  ON";
00197         $this->query_start($sql, null, SQL_QUERY_AUX);
00198         $result = mssql_query($sql, $this->mssql);
00199         $this->query_end($result);
00200 
00201         $this->free_result($result);
00202 
00203         // Set transactions isolation level to READ_COMMITTED
00204         // prevents dirty reads when using transactions +
00205         // is the default isolation level of MSSQL
00206         // Requires database to run with READ_COMMITTED_SNAPSHOT ON
00207         $sql = "SET TRANSACTION ISOLATION LEVEL READ COMMITTED";
00208         $this->query_start($sql, NULL, SQL_QUERY_AUX);
00209         $result = mssql_query($sql, $this->mssql);
00210         $this->query_end($result);
00211 
00212         $this->free_result($result);
00213 
00214         // Connection stabilised and configured, going to instantiate the temptables controller
00215         $this->temptables = new mssql_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         if ($this->mssql) {
00228             mssql_close($this->mssql);
00229             $this->mssql = null;
00230         }
00231     }
00232 
00241     protected function query_start($sql, array $params=null, $type, $extrainfo=null) {
00242         parent::query_start($sql, $params, $type, $extrainfo);
00243         // mssql driver tends to send debug to output, we do not need that ;-)
00244         $this->last_error_reporting = error_reporting(0);
00245     }
00246 
00252     protected function query_end($result) {
00253         // reset original debug level
00254         error_reporting($this->last_error_reporting);
00255         parent::query_end($result);
00256     }
00257 
00262     public function get_server_info() {
00263         static $info;
00264         if (!$info) {
00265             $info = array();
00266             $sql = 'sp_server_info 2';
00267             $this->query_start($sql, null, SQL_QUERY_AUX);
00268             $result = mssql_query($sql, $this->mssql);
00269             $this->query_end($result);
00270             $row = mssql_fetch_row($result);
00271             $info['description'] = $row[2];
00272             $this->free_result($result);
00273 
00274             $sql = 'sp_server_info 500';
00275             $this->query_start($sql, null, SQL_QUERY_AUX);
00276             $result = mssql_query($sql, $this->mssql);
00277             $this->query_end($result);
00278             $row = mssql_fetch_row($result);
00279             $info['version'] = $row[2];
00280             $this->free_result($result);
00281         }
00282         return $info;
00283     }
00284 
00285     protected function is_min_version($version) {
00286         $server = $this->get_server_info();
00287         $server = $server['version'];
00288         return version_compare($server, $version, '>=');
00289     }
00290 
00298     protected function fix_table_names($sql) {
00299         if (preg_match_all('/\{([a-z][a-z0-9_]*)\}/', $sql, $matches)) {
00300             foreach($matches[0] as $key=>$match) {
00301                 $name = $matches[1][$key];
00302                 if ($this->temptables->is_temptable($name)) {
00303                     $sql = str_replace($match, $this->temptables->get_correct_name($name), $sql);
00304                 } else {
00305                     $sql = str_replace($match, $this->prefix.$name, $sql);
00306                 }
00307             }
00308         }
00309         return $sql;
00310     }
00311 
00316     protected function allowed_param_types() {
00317         return SQL_PARAMS_QM; // Not really, but emulated, see emulate_bound_params()
00318     }
00319 
00324     public function get_last_error() {
00325         return mssql_get_last_message();
00326     }
00327 
00332     public function get_tables($usecache=true) {
00333         if ($usecache and $this->tables !== null) {
00334             return $this->tables;
00335         }
00336         $this->tables = array();
00337         $sql = "SELECT table_name
00338                   FROM information_schema.tables
00339                  WHERE table_name LIKE '$this->prefix%'
00340                    AND table_type = 'BASE TABLE'";
00341         $this->query_start($sql, null, SQL_QUERY_AUX);
00342         $result = mssql_query($sql, $this->mssql);
00343         $this->query_end($result);
00344 
00345         if ($result) {
00346             while ($row = mssql_fetch_row($result)) {
00347                 $tablename = reset($row);
00348                 if (strpos($tablename, $this->prefix) !== 0) {
00349                     continue;
00350                 }
00351                 $tablename = substr($tablename, strlen($this->prefix));
00352                 $this->tables[$tablename] = $tablename;
00353             }
00354             $this->free_result($result);
00355         }
00356 
00357         // Add the currently available temptables
00358         $this->tables = array_merge($this->tables, $this->temptables->get_temptables());
00359         return $this->tables;
00360     }
00361 
00366     public function get_indexes($table) {
00367         $indexes = array();
00368         $tablename = $this->prefix.$table;
00369 
00370         // Indexes aren't covered by information_schema metatables, so we need to
00371         // go to sys ones. Skipping primary key indexes on purpose.
00372         $sql = "SELECT i.name AS index_name, i.is_unique, ic.index_column_id, c.name AS column_name
00373                   FROM sys.indexes i
00374                   JOIN sys.index_columns ic ON i.object_id = ic.object_id AND i.index_id = ic.index_id
00375                   JOIN sys.columns c ON ic.object_id = c.object_id AND ic.column_id = c.column_id
00376                   JOIN sys.tables t ON i.object_id = t.object_id
00377                  WHERE t.name = '$tablename'
00378                    AND i.is_primary_key = 0
00379               ORDER BY i.name, i.index_id, ic.index_column_id";
00380 
00381         $this->query_start($sql, null, SQL_QUERY_AUX);
00382         $result = mssql_query($sql, $this->mssql);
00383         $this->query_end($result);
00384 
00385         if ($result) {
00386             $lastindex = '';
00387             $unique = false;
00388             $columns = array();
00389             while ($row = mssql_fetch_assoc($result)) {
00390                 if ($lastindex and $lastindex != $row['index_name']) { // Save lastindex to $indexes and reset info
00391                     $indexes[$lastindex] = array('unique' => $unique, 'columns' => $columns);
00392                     $unique = false;
00393                     $columns = array();
00394                 }
00395                 $lastindex = $row['index_name'];
00396                 $unique = empty($row['is_unique']) ? false : true;
00397                 $columns[] = $row['column_name'];
00398             }
00399             if ($lastindex ) { // Add the last one if exists
00400                 $indexes[$lastindex] = array('unique' => $unique, 'columns' => $columns);
00401             }
00402             $this->free_result($result);
00403         }
00404         return $indexes;
00405     }
00406 
00413     public function get_columns($table, $usecache=true) {
00414         if ($usecache and isset($this->columns[$table])) {
00415             return $this->columns[$table];
00416         }
00417 
00418         $this->columns[$table] = array();
00419 
00420         if (!$this->temptables->is_temptable($table)) { // normal table, get metadata from own schema
00421             $sql = "SELECT column_name AS name,
00422                            data_type AS type,
00423                            numeric_precision AS max_length,
00424                            character_maximum_length AS char_max_length,
00425                            numeric_scale AS scale,
00426                            is_nullable AS is_nullable,
00427                            columnproperty(object_id(quotename(table_schema) + '.' +
00428                                quotename(table_name)), column_name, 'IsIdentity') AS auto_increment,
00429                            column_default AS default_value
00430                       FROM information_schema.columns
00431                      WHERE table_name = '{" . $table . "}'
00432                   ORDER BY ordinal_position";
00433         } else { // temp table, get metadata from tempdb schema
00434             $sql = "SELECT column_name AS name,
00435                            data_type AS type,
00436                            numeric_precision AS max_length,
00437                            character_maximum_length AS char_max_length,
00438                            numeric_scale AS scale,
00439                            is_nullable AS is_nullable,
00440                            columnproperty(object_id(quotename(table_schema) + '.' +
00441                                quotename(table_name)), column_name, 'IsIdentity') AS auto_increment,
00442                            column_default AS default_value
00443                       FROM tempdb.information_schema.columns
00444                       JOIN tempdb..sysobjects ON name = table_name
00445                      WHERE id = object_id('tempdb..{" . $table . "}')
00446                   ORDER BY ordinal_position";
00447         }
00448 
00449         list($sql, $params, $type) = $this->fix_sql_params($sql, null);
00450 
00451         $this->query_start($sql, null, SQL_QUERY_AUX);
00452         $result = mssql_query($sql, $this->mssql);
00453         $this->query_end($result);
00454 
00455         if (!$result) {
00456             return array();
00457         }
00458 
00459         while ($rawcolumn = mssql_fetch_assoc($result)) {
00460 
00461             $rawcolumn = (object)$rawcolumn;
00462 
00463             $info = new stdClass();
00464             $info->name = $rawcolumn->name;
00465             $info->type = $rawcolumn->type;
00466             $info->meta_type = $this->mssqltype2moodletype($info->type);
00467 
00468             // Prepare auto_increment info
00469             $info->auto_increment = $rawcolumn->auto_increment ? true : false;
00470 
00471             // Define type for auto_increment columns
00472             $info->meta_type = ($info->auto_increment && $info->meta_type == 'I') ? 'R' : $info->meta_type;
00473 
00474             // id columns being auto_incremnt are PK by definition
00475             $info->primary_key = ($info->name == 'id' && $info->meta_type == 'R' && $info->auto_increment);
00476 
00477             // Put correct length for character and LOB types
00478             $info->max_length = $info->meta_type == 'C' ? $rawcolumn->char_max_length : $rawcolumn->max_length;
00479             $info->max_length = ($info->meta_type == 'X' || $info->meta_type == 'B') ? -1 : $info->max_length;
00480 
00481             // Scale
00482             $info->scale = $rawcolumn->scale ? $rawcolumn->scale : false;
00483 
00484             // Prepare not_null info
00485             $info->not_null = $rawcolumn->is_nullable == 'NO'  ? true : false;
00486 
00487             // Process defaults
00488             $info->has_default = !empty($rawcolumn->default_value);
00489             if ($rawcolumn->default_value === NULL) {
00490                 $info->default_value = NULL;
00491             } else {
00492                 $info->default_value = preg_replace("/^[\(N]+[']?(.*?)[']?[\)]+$/", '\\1', $rawcolumn->default_value);
00493             }
00494 
00495             // Process binary
00496             $info->binary = $info->meta_type == 'B' ? true : false;
00497 
00498             $this->columns[$table][$info->name] = new database_column_info($info);
00499         }
00500         $this->free_result($result);
00501 
00502         return $this->columns[$table];
00503     }
00504 
00512     protected function normalise_value($column, $value) {
00513         if (is_bool($value)) { 
00514             $value = (int)$value;
00515         } // And continue processing because text columns with numeric info need special handling below
00516 
00517         if ($column->meta_type == 'B') {   // BLOBs need to be properly "packed", but can be inserted directly if so.
00518             if (!is_null($value)) {               // If value not null, unpack it to unquoted hexadecimal byte-string format
00519                 $value = unpack('H*hex', $value); // we leave it as array, so emulate_bound_params() can detect it
00520             }                                     // easily and "bind" the param ok.
00521 
00522         } else if ($column->meta_type == 'X') {             // MSSQL doesn't cast from int to text, so if text column
00523             if (is_numeric($value)) {                       // and is numeric value then cast to string
00524                 $value = array('numstr' => (string)$value); // and put into array, so emulate_bound_params() will know how
00525             }                                               // to "bind" the param ok, avoiding reverse conversion to number
00526 
00527         } else if ($value === '') {
00528             if ($column->meta_type == 'I' or $column->meta_type == 'F' or $column->meta_type == 'N') {
00529                 $value = 0; // prevent '' problems in numeric fields
00530             }
00531         }
00532         return $value;
00533     }
00534 
00540     private function free_result($resource) {
00541         if (!is_bool($resource)) { // true/false resources cannot be freed
00542             mssql_free_result($resource);
00543         }
00544     }
00545 
00552     private function mssqltype2moodletype($mssql_type) {
00553         $type = null;
00554         switch (strtoupper($mssql_type)) {
00555             case 'BIT':
00556                 $type = 'L';
00557                 break;
00558             case 'INT':
00559             case 'SMALLINT':
00560             case 'INTEGER':
00561             case 'BIGINT':
00562                 $type = 'I';
00563                 break;
00564             case 'DECIMAL':
00565             case 'REAL':
00566             case 'FLOAT':
00567                 $type = 'N';
00568                 break;
00569             case 'VARCHAR':
00570             case 'NVARCHAR':
00571                 $type = 'C';
00572                 break;
00573             case 'TEXT':
00574             case 'NTEXT':
00575             case 'VARCHAR(MAX)':
00576             case 'NVARCHAR(MAX)':
00577                 $type = 'X';
00578                 break;
00579             case 'IMAGE':
00580             case 'VARBINARY(MAX)':
00581                 $type = 'B';
00582                 break;
00583             case 'DATETIME':
00584                 $type = 'D';
00585                 break;
00586         }
00587         if (!$type) {
00588             throw new dml_exception('invalidmssqlnativetype', $mssql_type);
00589         }
00590         return $type;
00591     }
00592 
00599     public function change_database_structure($sql) {
00600         $this->reset_caches();
00601 
00602         $this->query_start($sql, null, SQL_QUERY_STRUCTURE);
00603         $result = mssql_query($sql, $this->mssql);
00604         $this->query_end($result);
00605 
00606         return true;
00607     }
00608 
00613     protected function emulate_bound_params($sql, array $params=null) {
00614         if (empty($params)) {
00615             return $sql;
00616         }
00618         $parts = explode('?', $sql);
00619         $return = array_shift($parts);
00620         foreach ($params as $param) {
00621             if (is_bool($param)) {
00622                 $return .= (int)$param;
00623 
00624             } else if (is_array($param) && isset($param['hex'])) { // detect hex binary, bind it specially
00625                 $return .= '0x' . $param['hex'];
00626 
00627             } else if (is_array($param) && isset($param['numstr'])) { // detect numerical strings that *must not*
00628                 $return .= "N'{$param['numstr']}'";                   // be converted back to number params, but bound as strings
00629 
00630             } else if (is_null($param)) {
00631                 $return .= 'NULL';
00632 
00633             } else if (is_number($param)) { // we can not use is_numeric() because it eats leading zeros from strings like 0045646
00634                 $return .= "'".$param."'"; //fix for MDL-24863 to prevent auto-cast to int.
00635 
00636             } else if (is_float($param)) {
00637                 $return .= $param;
00638 
00639             } else {
00640                 $param = str_replace("'", "''", $param);
00641                 $return .= "N'$param'";
00642             }
00643 
00644             $return .= array_shift($parts);
00645         }
00646         return $return;
00647     }
00648 
00657     public function execute($sql, array $params=null) {
00658 
00659         list($sql, $params, $type) = $this->fix_sql_params($sql, $params);
00660         $rawsql = $this->emulate_bound_params($sql, $params);
00661 
00662         if (strpos($sql, ';') !== false) {
00663             throw new coding_exception('moodle_database::execute() Multiple sql statements found or bound parameters not used properly in query!');
00664         }
00665 
00666         $this->query_start($sql, $params, SQL_QUERY_UPDATE);
00667         $result = mssql_query($rawsql, $this->mssql);
00668         $this->query_end($result);
00669         $this->free_result($result);
00670 
00671         return true;
00672     }
00673 
00690     public function get_recordset_sql($sql, array $params=null, $limitfrom=0, $limitnum=0) {
00691         $limitfrom = (int)$limitfrom;
00692         $limitnum  = (int)$limitnum;
00693         $limitfrom = ($limitfrom < 0) ? 0 : $limitfrom;
00694         $limitnum  = ($limitnum < 0)  ? 0 : $limitnum;
00695         if ($limitfrom or $limitnum) {
00696             if ($limitnum >= 1) { // Only apply TOP clause if we have any limitnum (limitfrom offset is handled later)
00697                 $fetch = $limitfrom + $limitnum;
00698                 if (PHP_INT_MAX - $limitnum < $limitfrom) { // Check PHP_INT_MAX overflow
00699                     $fetch = PHP_INT_MAX;
00700                 }
00701                 $sql = preg_replace('/^([\s(])*SELECT([\s]+(DISTINCT|ALL))?(?!\s*TOP\s*\()/i',
00702                                     "\\1SELECT\\2 TOP $fetch", $sql);
00703             }
00704         }
00705 
00706         list($sql, $params, $type) = $this->fix_sql_params($sql, $params);
00707         $rawsql = $this->emulate_bound_params($sql, $params);
00708 
00709         $this->query_start($sql, $params, SQL_QUERY_SELECT);
00710         $result = mssql_query($rawsql, $this->mssql);
00711         $this->query_end($result);
00712 
00713         if ($limitfrom) { // Skip $limitfrom records
00714             mssql_data_seek($result, $limitfrom);
00715         }
00716 
00717         return $this->create_recordset($result);
00718     }
00719 
00720     protected function create_recordset($result) {
00721         return new mssql_native_moodle_recordset($result);
00722     }
00723 
00738     public function get_records_sql($sql, array $params=null, $limitfrom=0, $limitnum=0) {
00739 
00740         $rs = $this->get_recordset_sql($sql, $params, $limitfrom, $limitnum);
00741 
00742         $results = array();
00743 
00744         foreach ($rs as $row) {
00745             $id = reset($row);
00746             if (isset($results[$id])) {
00747                 $colname = key($row);
00748                 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);
00749             }
00750             $results[$id] = $row;
00751         }
00752         $rs->close();
00753 
00754         return $results;
00755     }
00756 
00765     public function get_fieldset_sql($sql, array $params=null) {
00766 
00767         $rs = $this->get_recordset_sql($sql, $params);
00768 
00769         $results = array();
00770 
00771         foreach ($rs as $row) {
00772             $results[] = reset($row);
00773         }
00774         $rs->close();
00775 
00776         return $results;
00777     }
00778 
00789     public function insert_record_raw($table, $params, $returnid=true, $bulk=false, $customsequence=false) {
00790         if (!is_array($params)) {
00791             $params = (array)$params;
00792         }
00793 
00794         $returning = "";
00795 
00796         if ($customsequence) {
00797             if (!isset($params['id'])) {
00798                 throw new coding_exception('moodle_database::insert_record_raw() id field must be specified if custom sequences used.');
00799             }
00800             $returnid = false;
00801 
00802             // Disable IDENTITY column before inserting record with id
00803             $sql = 'SET IDENTITY_INSERT {' . $table . '} ON'; // Yes, it' ON!!
00804             list($sql, $xparams, $xtype) = $this->fix_sql_params($sql, null);
00805             $this->query_start($sql, null, SQL_QUERY_AUX);
00806             $result = mssql_query($sql, $this->mssql);
00807             $this->query_end($result);
00808             $this->free_result($result);
00809 
00810         } else {
00811             unset($params['id']);
00812             if ($returnid) {
00813                 $returning = "; SELECT SCOPE_IDENTITY()";
00814             }
00815         }
00816 
00817         if (empty($params)) {
00818             throw new coding_exception('moodle_database::insert_record_raw() no fields found.');
00819         }
00820 
00821         $fields = implode(',', array_keys($params));
00822         $qms    = array_fill(0, count($params), '?');
00823         $qms    = implode(',', $qms);
00824 
00825         $sql = "INSERT INTO {" . $table . "} ($fields) VALUES($qms) $returning";
00826 
00827         list($sql, $params, $type) = $this->fix_sql_params($sql, $params);
00828         $rawsql = $this->emulate_bound_params($sql, $params);
00829 
00830         $this->query_start($sql, $params, SQL_QUERY_INSERT);
00831         $result = mssql_query($rawsql, $this->mssql);
00832         $this->query_end($result);
00833 
00834         if ($returning !== "") {
00835             $row = mssql_fetch_assoc($result);
00836             $params['id'] = reset($row);
00837         }
00838         $this->free_result($result);
00839 
00840         if ($customsequence) {
00841             // Enable IDENTITY column after inserting record with id
00842             $sql = 'SET IDENTITY_INSERT {' . $table . '} OFF'; // Yes, it' OFF!!
00843             list($sql, $xparams, $xtype) = $this->fix_sql_params($sql, null);
00844             $this->query_start($sql, null, SQL_QUERY_AUX);
00845             $result = mssql_query($sql, $this->mssql);
00846             $this->query_end($result);
00847             $this->free_result($result);
00848         }
00849 
00850         if (!$returnid) {
00851             return true;
00852         }
00853 
00854         return (int)$params['id'];
00855     }
00856 
00869     public function insert_record($table, $dataobject, $returnid=true, $bulk=false) {
00870         $dataobject = (array)$dataobject;
00871 
00872         $columns = $this->get_columns($table);
00873         $cleaned = array();
00874 
00875         foreach ($dataobject as $field => $value) {
00876             if ($field === 'id') {
00877                 continue;
00878             }
00879             if (!isset($columns[$field])) {
00880                 continue;
00881             }
00882             $column = $columns[$field];
00883             $cleaned[$field] = $this->normalise_value($column, $value);
00884         }
00885 
00886         return $this->insert_record_raw($table, $cleaned, $returnid, $bulk);
00887     }
00888 
00898     public function import_record($table, $dataobject) {
00899         $dataobject = (array)$dataobject;
00900 
00901         $columns = $this->get_columns($table);
00902         $cleaned = array();
00903 
00904         foreach ($dataobject as $field => $value) {
00905             if (!isset($columns[$field])) {
00906                 continue;
00907             }
00908             $column = $columns[$field];
00909             $cleaned[$field] = $this->normalise_value($column, $value);
00910         }
00911 
00912         $this->insert_record_raw($table, $cleaned, false, false, true);
00913 
00914         return true;
00915     }
00916 
00925     public function update_record_raw($table, $params, $bulk=false) {
00926         $params = (array)$params;
00927 
00928         if (!isset($params['id'])) {
00929             throw new coding_exception('moodle_database::update_record_raw() id field must be specified.');
00930         }
00931         $id = $params['id'];
00932         unset($params['id']);
00933 
00934         if (empty($params)) {
00935             throw new coding_exception('moodle_database::update_record_raw() no fields found.');
00936         }
00937 
00938         $sets = array();
00939         foreach ($params as $field=>$value) {
00940             $sets[] = "$field = ?";
00941         }
00942 
00943         $params[] = $id; // last ? in WHERE condition
00944 
00945         $sets = implode(',', $sets);
00946         $sql = "UPDATE {" . $table . "} SET $sets WHERE id = ?";
00947 
00948         list($sql, $params, $type) = $this->fix_sql_params($sql, $params);
00949         $rawsql = $this->emulate_bound_params($sql, $params);
00950 
00951         $this->query_start($sql, $params, SQL_QUERY_UPDATE);
00952         $result = mssql_query($rawsql, $this->mssql);
00953         $this->query_end($result);
00954 
00955         $this->free_result($result);
00956         return true;
00957     }
00958 
00972     public function update_record($table, $dataobject, $bulk=false) {
00973         $dataobject = (array)$dataobject;
00974 
00975         $columns = $this->get_columns($table);
00976         $cleaned = array();
00977 
00978         foreach ($dataobject as $field => $value) {
00979             if (!isset($columns[$field])) {
00980                 continue;
00981             }
00982             $column = $columns[$field];
00983             $cleaned[$field] = $this->normalise_value($column, $value);
00984         }
00985 
00986         return $this->update_record_raw($table, $cleaned, $bulk);
00987     }
00988 
01000     public function set_field_select($table, $newfield, $newvalue, $select, array $params=null) {
01001 
01002         if ($select) {
01003             $select = "WHERE $select";
01004         }
01005         if (is_null($params)) {
01006             $params = array();
01007         }
01008 
01009         // convert params to ? types
01010         list($select, $params, $type) = $this->fix_sql_params($select, $params);
01011 
01013         $columns = $this->get_columns($table);
01014         $column = $columns[$newfield];
01015 
01016         $newvalue = $this->normalise_value($column, $newvalue);
01017 
01018         if (is_null($newvalue)) {
01019             $newfield = "$newfield = NULL";
01020         } else {
01021             $newfield = "$newfield = ?";
01022             array_unshift($params, $newvalue);
01023         }
01024         $sql = "UPDATE {" . $table . "} SET $newfield $select";
01025 
01026         list($sql, $params, $type) = $this->fix_sql_params($sql, $params);
01027         $rawsql = $this->emulate_bound_params($sql, $params);
01028 
01029         $this->query_start($sql, $params, SQL_QUERY_UPDATE);
01030         $result = mssql_query($rawsql, $this->mssql);
01031         $this->query_end($result);
01032 
01033         $this->free_result($result);
01034 
01035         return true;
01036     }
01037 
01047     public function delete_records_select($table, $select, array $params=null) {
01048 
01049         if ($select) {
01050             $select = "WHERE $select";
01051         }
01052 
01053         $sql = "DELETE FROM {" . $table . "} $select";
01054 
01055         list($sql, $params, $type) = $this->fix_sql_params($sql, $params);
01056         $rawsql = $this->emulate_bound_params($sql, $params);
01057 
01058         $this->query_start($sql, $params, SQL_QUERY_UPDATE);
01059         $result = mssql_query($rawsql, $this->mssql);
01060         $this->query_end($result);
01061 
01062         $this->free_result($result);
01063 
01064         return true;
01065     }
01066 
01068 
01069     public function sql_cast_char2int($fieldname, $text=false) {
01070         if (!$text) {
01071             return ' CAST(' . $fieldname . ' AS INT) ';
01072         } else {
01073             return ' CAST(' . $this->sql_compare_text($fieldname) . ' AS INT) ';
01074         }
01075     }
01076 
01077     public function sql_cast_char2real($fieldname, $text=false) {
01078         if (!$text) {
01079             return ' CAST(' . $fieldname . ' AS REAL) ';
01080         } else {
01081             return ' CAST(' . $this->sql_compare_text($fieldname) . ' AS REAL) ';
01082         }
01083     }
01084 
01085     public function sql_ceil($fieldname) {
01086         return ' CEILING(' . $fieldname . ')';
01087     }
01088 
01089 
01090     protected function get_collation() {
01091         if (isset($this->collation)) {
01092             return $this->collation;
01093         }
01094         if (!empty($this->dboptions['dbcollation'])) {
01095             // perf speedup
01096             $this->collation = $this->dboptions['dbcollation'];
01097             return $this->collation;
01098         }
01099 
01100         // make some default
01101         $this->collation = 'Latin1_General_CI_AI';
01102 
01103         $sql = "SELECT CAST(DATABASEPROPERTYEX('$this->dbname', 'Collation') AS varchar(255)) AS SQLCollation";
01104         $this->query_start($sql, null, SQL_QUERY_AUX);
01105         $result = mssql_query($sql, $this->mssql);
01106         $this->query_end($result);
01107 
01108         if ($result) {
01109             if ($rawcolumn = mssql_fetch_assoc($result)) {
01110                 $this->collation = reset($rawcolumn);
01111             }
01112             $this->free_result($result);
01113         }
01114 
01115         return $this->collation;
01116     }
01117 
01129     public function sql_like($fieldname, $param, $casesensitive = true, $accentsensitive = true, $notlike = false, $escapechar = '\\') {
01130         if (strpos($param, '%') !== false) {
01131             debugging('Potential SQL injection detected, sql_ilike() expects bound parameters (? or :named)');
01132         }
01133 
01134         $collation = $this->get_collation();
01135 
01136         if ($casesensitive) {
01137             $collation = str_replace('_CI', '_CS', $collation);
01138         } else {
01139             $collation = str_replace('_CS', '_CI', $collation);
01140         }
01141         if ($accentsensitive) {
01142             $collation = str_replace('_AI', '_AS', $collation);
01143         } else {
01144             $collation = str_replace('_AS', '_AI', $collation);
01145         }
01146 
01147         $LIKE = $notlike ? 'NOT LIKE' : 'LIKE';
01148 
01149         return "$fieldname COLLATE $collation $LIKE $param ESCAPE '$escapechar'";
01150     }
01151 
01152     public function sql_concat() {
01153         $arr = func_get_args();
01154         foreach ($arr as $key => $ele) {
01155             $arr[$key] = ' CAST(' . $ele . ' AS VARCHAR(255)) ';
01156         }
01157         $s = implode(' + ', $arr);
01158         if ($s === '') {
01159             return " '' ";
01160         }
01161         return " $s ";
01162     }
01163 
01164     public function sql_concat_join($separator="' '", $elements=array()) {
01165         for ($n=count($elements)-1; $n > 0 ; $n--) {
01166             array_splice($elements, $n, 0, $separator);
01167         }
01168         $s = implode(' + ', $elements);
01169         if ($s === '') {
01170             return " '' ";
01171         }
01172         return " $s ";
01173     }
01174 
01175    public function sql_isempty($tablename, $fieldname, $nullablefield, $textfield) {
01176         if ($textfield) {
01177             return ' (' . $this->sql_compare_text($fieldname) . " = '') ";
01178         } else {
01179             return " ($fieldname = '') ";
01180         }
01181     }
01182 
01188     public function sql_length($fieldname) {
01189         return ' LEN(' . $fieldname . ')';
01190     }
01191 
01192     public function sql_order_by_text($fieldname, $numchars=32) {
01193         return ' CONVERT(varchar, ' . $fieldname . ', ' . $numchars . ')';
01194     }
01195 
01199     public function sql_position($needle, $haystack) {
01200         return "CHARINDEX(($needle), ($haystack))";
01201     }
01202 
01212     public function sql_substr($expr, $start, $length=false) {
01213         if (count(func_get_args()) < 2) {
01214             throw new coding_exception('moodle_database::sql_substr() requires at least two parameters', 'Originaly this function wa
01215 s only returning name of SQL substring function, it now requires all parameters.');
01216         }
01217         if ($length === false) {
01218             return "SUBSTRING($expr, $start, (LEN($expr) - $start + 1))";
01219         } else {
01220             return "SUBSTRING($expr, $start, $length)";
01221         }
01222     }
01223 
01225 
01226     public function session_lock_supported() {
01227         return true;
01228     }
01229 
01236     public function get_session_lock($rowid, $timeout) {
01237         if (!$this->session_lock_supported()) {
01238             return;
01239         }
01240         parent::get_session_lock($rowid, $timeout);
01241 
01242         $timeoutmilli = $timeout * 1000;
01243 
01244         $fullname = $this->dbname.'-'.$this->prefix.'-session-'.$rowid;
01245         // There is one bug in PHP/freetds (both reproducible with mssql_query()
01246         // and its mssql_init()/mssql_bind()/mssql_execute() alternative) for
01247         // stored procedures, causing scalar results of the execution
01248         // to be cast to boolean (true/fals). Here there is one
01249         // workaround that forces the return of one recordset resource.
01250         // $sql = "sp_getapplock '$fullname', 'Exclusive', 'Session',  $timeoutmilli";
01251         $sql = "BEGIN
01252                     DECLARE @result INT
01253                     EXECUTE @result = sp_getapplock @Resource='$fullname',
01254                                                     @LockMode='Exclusive',
01255                                                     @LockOwner='Session',
01256                                                     @LockTimeout='$timeoutmilli'
01257                     SELECT @result
01258                 END";
01259         $this->query_start($sql, null, SQL_QUERY_AUX);
01260         $result = mssql_query($sql, $this->mssql);
01261         $this->query_end($result);
01262 
01263         if ($result) {
01264             $row = mssql_fetch_row($result);
01265             if ($row[0] < 0) {
01266                 throw new dml_sessionwait_exception();
01267             }
01268         }
01269 
01270         $this->free_result($result);
01271     }
01272 
01273     public function release_session_lock($rowid) {
01274         if (!$this->session_lock_supported()) {
01275             return;
01276         }
01277         parent::release_session_lock($rowid);
01278 
01279         $fullname = $this->dbname.'-'.$this->prefix.'-session-'.$rowid;
01280         $sql = "sp_releaseapplock '$fullname', 'Session'";
01281         $this->query_start($sql, null, SQL_QUERY_AUX);
01282         $result = mssql_query($sql, $this->mssql);
01283         $this->query_end($result);
01284 
01285         $this->free_result($result);
01286     }
01287 
01289 
01295     protected function begin_transaction() {
01296         // requires database to run with READ_COMMITTED_SNAPSHOT ON
01297         $sql = "BEGIN TRANSACTION"; // Will be using READ COMMITTED isolation
01298         $this->query_start($sql, NULL, SQL_QUERY_AUX);
01299         $result = mssql_query($sql, $this->mssql);
01300         $this->query_end($result);
01301 
01302         $this->free_result($result);
01303     }
01304 
01310     protected function commit_transaction() {
01311         $sql = "COMMIT TRANSACTION";
01312         $this->query_start($sql, NULL, SQL_QUERY_AUX);
01313         $result = mssql_query($sql, $this->mssql);
01314         $this->query_end($result);
01315 
01316         $this->free_result($result);
01317     }
01318 
01324     protected function rollback_transaction() {
01325         $sql = "ROLLBACK TRANSACTION";
01326         $this->query_start($sql, NULL, SQL_QUERY_AUX);
01327         $result = mssql_query($sql, $this->mssql);
01328         $this->query_end($result);
01329 
01330         $this->free_result($result);
01331     }
01332 }
 All Data Structures Namespaces Files Functions Variables Enumerations