[ Index ] |
PHP Cross Reference of Unnamed Project |
[Summary view] [Print] [Text view]
1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 16 17 /** 18 * Native mssql class representing moodle database interface. 19 * 20 * @package core_dml 21 * @copyright 2009 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com} 22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 */ 24 25 defined('MOODLE_INTERNAL') || die(); 26 27 require_once (__DIR__.'/moodle_database.php'); 28 require_once (__DIR__.'/mssql_native_moodle_recordset.php'); 29 require_once (__DIR__.'/mssql_native_moodle_temptables.php'); 30 31 /** 32 * Native mssql class representing moodle database interface. 33 * 34 * @package core_dml 35 * @copyright 2009 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com} 36 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 37 */ 38 class mssql_native_moodle_database extends moodle_database { 39 40 protected $mssql = null; 41 protected $last_error_reporting; // To handle mssql driver default verbosity 42 protected $collation; // current DB collation cache 43 /** 44 * Does the used db version support ANSI way of limiting (2012 and higher) 45 * @var bool 46 */ 47 protected $supportsoffsetfetch; 48 49 /** 50 * Detects if all needed PHP stuff installed. 51 * Note: can be used before connect() 52 * @return mixed true if ok, string if something 53 */ 54 public function driver_installed() { 55 if (!function_exists('mssql_connect')) { 56 return get_string('mssqlextensionisnotpresentinphp', 'install'); 57 } 58 return true; 59 } 60 61 /** 62 * Returns database family type - describes SQL dialect 63 * Note: can be used before connect() 64 * @return string db family name (mysql, postgres, mssql, oracle, etc.) 65 */ 66 public function get_dbfamily() { 67 return 'mssql'; 68 } 69 70 /** 71 * Returns more specific database driver type 72 * Note: can be used before connect() 73 * @return string db type mysqli, pgsql, oci, mssql, sqlsrv 74 */ 75 protected function get_dbtype() { 76 return 'mssql'; 77 } 78 79 /** 80 * Returns general database library name 81 * Note: can be used before connect() 82 * @return string db type pdo, native 83 */ 84 protected function get_dblibrary() { 85 return 'native'; 86 } 87 88 /** 89 * Returns localised database type name 90 * Note: can be used before connect() 91 * @return string 92 */ 93 public function get_name() { 94 return get_string('nativemssql', 'install'); 95 } 96 97 /** 98 * Returns localised database configuration help. 99 * Note: can be used before connect() 100 * @return string 101 */ 102 public function get_configuration_help() { 103 return get_string('nativemssqlhelp', 'install'); 104 } 105 106 /** 107 * Diagnose database and tables, this function is used 108 * to verify database and driver settings, db engine types, etc. 109 * 110 * @return string null means everything ok, string means problem found. 111 */ 112 public function diagnose() { 113 // Verify the database is running with READ_COMMITTED_SNAPSHOT enabled. 114 // (that's required to get snapshots/row versioning on READ_COMMITED mode). 115 $correctrcsmode = false; 116 $sql = "SELECT is_read_committed_snapshot_on 117 FROM sys.databases 118 WHERE name = '{$this->dbname}'"; 119 $this->query_start($sql, null, SQL_QUERY_AUX); 120 $result = mssql_query($sql, $this->mssql); 121 $this->query_end($result); 122 if ($result) { 123 if ($row = mssql_fetch_assoc($result)) { 124 $correctrcsmode = (bool)reset($row); 125 } 126 } 127 $this->free_result($result); 128 129 if (!$correctrcsmode) { 130 return get_string('mssqlrcsmodemissing', 'error'); 131 } 132 133 // Arrived here, all right. 134 return null; 135 } 136 137 /** 138 * Connect to db 139 * Must be called before other methods. 140 * @param string $dbhost The database host. 141 * @param string $dbuser The database username. 142 * @param string $dbpass The database username's password. 143 * @param string $dbname The name of the database being connected to. 144 * @param mixed $prefix string means moodle db prefix, false used for external databases where prefix not used 145 * @param array $dboptions driver specific options 146 * @return bool true 147 * @throws dml_connection_exception if error 148 */ 149 public function connect($dbhost, $dbuser, $dbpass, $dbname, $prefix, array $dboptions=null) { 150 if ($prefix == '' and !$this->external) { 151 //Enforce prefixes for everybody but mysql 152 throw new dml_exception('prefixcannotbeempty', $this->get_dbfamily()); 153 } 154 155 $driverstatus = $this->driver_installed(); 156 157 if ($driverstatus !== true) { 158 throw new dml_exception('dbdriverproblem', $driverstatus); 159 } 160 161 $this->store_settings($dbhost, $dbuser, $dbpass, $dbname, $prefix, $dboptions); 162 163 $dbhost = $this->dbhost; 164 // Zero shouldn't be used as a port number so doing a check with empty() should be fine. 165 if (!empty($dboptions['dbport'])) { 166 if (stristr(PHP_OS, 'win') && !stristr(PHP_OS, 'darwin')) { 167 $dbhost .= ','.$dboptions['dbport']; 168 } else { 169 $dbhost .= ':'.$dboptions['dbport']; 170 } 171 } 172 ob_start(); 173 if (!empty($this->dboptions['dbpersist'])) { // persistent connection 174 $this->mssql = mssql_pconnect($dbhost, $this->dbuser, $this->dbpass, true); 175 } else { 176 $this->mssql = mssql_connect($dbhost, $this->dbuser, $this->dbpass, true); 177 } 178 $dberr = ob_get_contents(); 179 ob_end_clean(); 180 181 if ($this->mssql === false) { 182 $this->mssql = null; 183 throw new dml_connection_exception($dberr); 184 } 185 186 // already connected, select database and set some env. variables 187 $this->query_start("--mssql_select_db", null, SQL_QUERY_AUX); 188 $result = mssql_select_db($this->dbname, $this->mssql); 189 $this->query_end($result); 190 191 // No need to set charset. It's UTF8, with transparent conversions 192 // back and forth performed both by FreeTDS or ODBTP 193 194 // Allow quoted identifiers 195 $sql = "SET QUOTED_IDENTIFIER ON"; 196 $this->query_start($sql, null, SQL_QUERY_AUX); 197 $result = mssql_query($sql, $this->mssql); 198 $this->query_end($result); 199 200 $this->free_result($result); 201 202 // Force ANSI nulls so the NULL check was done by IS NULL and NOT IS NULL 203 // instead of equal(=) and distinct(<>) symbols 204 $sql = "SET ANSI_NULLS ON"; 205 $this->query_start($sql, null, SQL_QUERY_AUX); 206 $result = mssql_query($sql, $this->mssql); 207 $this->query_end($result); 208 209 $this->free_result($result); 210 211 // Force ANSI warnings so arithmetic/string overflows will be 212 // returning error instead of transparently truncating data 213 $sql = "SET ANSI_WARNINGS ON"; 214 $this->query_start($sql, null, SQL_QUERY_AUX); 215 $result = mssql_query($sql, $this->mssql); 216 $this->query_end($result); 217 218 // Concatenating null with anything MUST return NULL 219 $sql = "SET CONCAT_NULL_YIELDS_NULL ON"; 220 $this->query_start($sql, null, SQL_QUERY_AUX); 221 $result = mssql_query($sql, $this->mssql); 222 $this->query_end($result); 223 224 $this->free_result($result); 225 226 // Set transactions isolation level to READ_COMMITTED 227 // prevents dirty reads when using transactions + 228 // is the default isolation level of MSSQL 229 // Requires database to run with READ_COMMITTED_SNAPSHOT ON 230 $sql = "SET TRANSACTION ISOLATION LEVEL READ COMMITTED"; 231 $this->query_start($sql, NULL, SQL_QUERY_AUX); 232 $result = mssql_query($sql, $this->mssql); 233 $this->query_end($result); 234 235 $this->free_result($result); 236 237 $serverinfo = $this->get_server_info(); 238 // Fetch/offset is supported staring from SQL Server 2012. 239 $this->supportsoffsetfetch = $serverinfo['version'] > '11'; 240 241 // Connection stabilised and configured, going to instantiate the temptables controller 242 $this->temptables = new mssql_native_moodle_temptables($this); 243 244 return true; 245 } 246 247 /** 248 * Close database connection and release all resources 249 * and memory (especially circular memory references). 250 * Do NOT use connect() again, create a new instance if needed. 251 */ 252 public function dispose() { 253 parent::dispose(); // Call parent dispose to write/close session and other common stuff before closing connection 254 if ($this->mssql) { 255 mssql_close($this->mssql); 256 $this->mssql = null; 257 } 258 } 259 260 /** 261 * Called before each db query. 262 * @param string $sql 263 * @param array array of parameters 264 * @param int $type type of query 265 * @param mixed $extrainfo driver specific extra information 266 * @return void 267 */ 268 protected function query_start($sql, array $params=null, $type, $extrainfo=null) { 269 parent::query_start($sql, $params, $type, $extrainfo); 270 // mssql driver tends to send debug to output, we do not need that ;-) 271 $this->last_error_reporting = error_reporting(0); 272 } 273 274 /** 275 * Called immediately after each db query. 276 * @param mixed db specific result 277 * @return void 278 */ 279 protected function query_end($result) { 280 // reset original debug level 281 error_reporting($this->last_error_reporting); 282 parent::query_end($result); 283 } 284 285 /** 286 * Returns database server info array 287 * @return array Array containing 'description' and 'version' info 288 */ 289 public function get_server_info() { 290 static $info; 291 if (!$info) { 292 $info = array(); 293 $sql = 'sp_server_info 2'; 294 $this->query_start($sql, null, SQL_QUERY_AUX); 295 $result = mssql_query($sql, $this->mssql); 296 $this->query_end($result); 297 $row = mssql_fetch_row($result); 298 $info['description'] = $row[2]; 299 $this->free_result($result); 300 301 $sql = 'sp_server_info 500'; 302 $this->query_start($sql, null, SQL_QUERY_AUX); 303 $result = mssql_query($sql, $this->mssql); 304 $this->query_end($result); 305 $row = mssql_fetch_row($result); 306 $info['version'] = $row[2]; 307 $this->free_result($result); 308 } 309 return $info; 310 } 311 312 /** 313 * Converts short table name {tablename} to real table name 314 * supporting temp tables (#) if detected 315 * 316 * @param string sql 317 * @return string sql 318 */ 319 protected function fix_table_names($sql) { 320 if (preg_match_all('/\{([a-z][a-z0-9_]*)\}/', $sql, $matches)) { 321 foreach($matches[0] as $key=>$match) { 322 $name = $matches[1][$key]; 323 if ($this->temptables->is_temptable($name)) { 324 $sql = str_replace($match, $this->temptables->get_correct_name($name), $sql); 325 } else { 326 $sql = str_replace($match, $this->prefix.$name, $sql); 327 } 328 } 329 } 330 return $sql; 331 } 332 333 /** 334 * Returns supported query parameter types 335 * @return int bitmask of accepted SQL_PARAMS_* 336 */ 337 protected function allowed_param_types() { 338 return SQL_PARAMS_QM; // Not really, but emulated, see emulate_bound_params() 339 } 340 341 /** 342 * Returns last error reported by database engine. 343 * @return string error message 344 */ 345 public function get_last_error() { 346 return mssql_get_last_message(); 347 } 348 349 /** 350 * Return tables in database WITHOUT current prefix 351 * @param bool $usecache if true, returns list of cached tables. 352 * @return array of table names in lowercase and without prefix 353 */ 354 public function get_tables($usecache=true) { 355 if ($usecache and $this->tables !== null) { 356 return $this->tables; 357 } 358 $this->tables = array(); 359 $sql = "SELECT table_name 360 FROM INFORMATION_SCHEMA.TABLES 361 WHERE table_name LIKE '$this->prefix%' 362 AND table_type = 'BASE TABLE'"; 363 $this->query_start($sql, null, SQL_QUERY_AUX); 364 $result = mssql_query($sql, $this->mssql); 365 $this->query_end($result); 366 367 if ($result) { 368 while ($row = mssql_fetch_row($result)) { 369 $tablename = reset($row); 370 if ($this->prefix !== false && $this->prefix !== '') { 371 if (strpos($tablename, $this->prefix) !== 0) { 372 continue; 373 } 374 $tablename = substr($tablename, strlen($this->prefix)); 375 } 376 $this->tables[$tablename] = $tablename; 377 } 378 $this->free_result($result); 379 } 380 381 // Add the currently available temptables 382 $this->tables = array_merge($this->tables, $this->temptables->get_temptables()); 383 return $this->tables; 384 } 385 386 /** 387 * Return table indexes - everything lowercased. 388 * @param string $table The table we want to get indexes from. 389 * @return array An associative array of indexes containing 'unique' flag and 'columns' being indexed 390 */ 391 public function get_indexes($table) { 392 $indexes = array(); 393 $tablename = $this->prefix.$table; 394 395 // Indexes aren't covered by information_schema metatables, so we need to 396 // go to sys ones. Skipping primary key indexes on purpose. 397 $sql = "SELECT i.name AS index_name, i.is_unique, ic.index_column_id, c.name AS column_name 398 FROM sys.indexes i 399 JOIN sys.index_columns ic ON i.object_id = ic.object_id AND i.index_id = ic.index_id 400 JOIN sys.columns c ON ic.object_id = c.object_id AND ic.column_id = c.column_id 401 JOIN sys.tables t ON i.object_id = t.object_id 402 WHERE t.name = '$tablename' 403 AND i.is_primary_key = 0 404 ORDER BY i.name, i.index_id, ic.index_column_id"; 405 406 $this->query_start($sql, null, SQL_QUERY_AUX); 407 $result = mssql_query($sql, $this->mssql); 408 $this->query_end($result); 409 410 if ($result) { 411 $lastindex = ''; 412 $unique = false; 413 $columns = array(); 414 while ($row = mssql_fetch_assoc($result)) { 415 if ($lastindex and $lastindex != $row['index_name']) { // Save lastindex to $indexes and reset info 416 $indexes[$lastindex] = array('unique' => $unique, 'columns' => $columns); 417 $unique = false; 418 $columns = array(); 419 } 420 $lastindex = $row['index_name']; 421 $unique = empty($row['is_unique']) ? false : true; 422 $columns[] = $row['column_name']; 423 } 424 if ($lastindex ) { // Add the last one if exists 425 $indexes[$lastindex] = array('unique' => $unique, 'columns' => $columns); 426 } 427 $this->free_result($result); 428 } 429 return $indexes; 430 } 431 432 /** 433 * Returns datailed information about columns in table. This information is cached internally. 434 * @param string $table name 435 * @param bool $usecache 436 * @return array array of database_column_info objects indexed with column names 437 */ 438 public function get_columns($table, $usecache=true) { 439 440 if ($usecache) { 441 if ($this->temptables->is_temptable($table)) { 442 if ($data = $this->get_temp_tables_cache()->get($table)) { 443 return $data; 444 } 445 } else { 446 if ($data = $this->get_metacache()->get($table)) { 447 return $data; 448 } 449 } 450 } 451 452 $structure = array(); 453 454 if (!$this->temptables->is_temptable($table)) { // normal table, get metadata from own schema 455 $sql = "SELECT column_name AS name, 456 data_type AS type, 457 numeric_precision AS max_length, 458 character_maximum_length AS char_max_length, 459 numeric_scale AS scale, 460 is_nullable AS is_nullable, 461 columnproperty(object_id(quotename(table_schema) + '.' + 462 quotename(table_name)), column_name, 'IsIdentity') AS auto_increment, 463 column_default AS default_value 464 FROM INFORMATION_SCHEMA.COLUMNS 465 WHERE table_name = '{" . $table . "}' 466 ORDER BY ordinal_position"; 467 } else { // temp table, get metadata from tempdb schema 468 $sql = "SELECT column_name AS name, 469 data_type AS type, 470 numeric_precision AS max_length, 471 character_maximum_length AS char_max_length, 472 numeric_scale AS scale, 473 is_nullable AS is_nullable, 474 columnproperty(object_id(quotename(table_schema) + '.' + 475 quotename(table_name)), column_name, 'IsIdentity') AS auto_increment, 476 column_default AS default_value 477 FROM tempdb.INFORMATION_SCHEMA.COLUMNS 478 JOIN tempdb..sysobjects ON name = table_name 479 WHERE id = object_id('tempdb..{" . $table . "}') 480 ORDER BY ordinal_position"; 481 } 482 483 list($sql, $params, $type) = $this->fix_sql_params($sql, null); 484 485 $this->query_start($sql, null, SQL_QUERY_AUX); 486 $result = mssql_query($sql, $this->mssql); 487 $this->query_end($result); 488 489 if (!$result) { 490 return array(); 491 } 492 493 while ($rawcolumn = mssql_fetch_assoc($result)) { 494 495 $rawcolumn = (object)$rawcolumn; 496 497 $info = new stdClass(); 498 $info->name = $rawcolumn->name; 499 $info->type = $rawcolumn->type; 500 $info->meta_type = $this->mssqltype2moodletype($info->type); 501 502 // Prepare auto_increment info 503 $info->auto_increment = $rawcolumn->auto_increment ? true : false; 504 505 // Define type for auto_increment columns 506 $info->meta_type = ($info->auto_increment && $info->meta_type == 'I') ? 'R' : $info->meta_type; 507 508 // id columns being auto_incremnt are PK by definition 509 $info->primary_key = ($info->name == 'id' && $info->meta_type == 'R' && $info->auto_increment); 510 511 if ($info->meta_type === 'C' and $rawcolumn->char_max_length == -1) { 512 // This is NVARCHAR(MAX), not a normal NVARCHAR. 513 $info->max_length = -1; 514 $info->meta_type = 'X'; 515 } else { 516 // Put correct length for character and LOB types 517 $info->max_length = $info->meta_type == 'C' ? $rawcolumn->char_max_length : $rawcolumn->max_length; 518 $info->max_length = ($info->meta_type == 'X' || $info->meta_type == 'B') ? -1 : $info->max_length; 519 } 520 521 // Scale 522 $info->scale = $rawcolumn->scale; 523 524 // Prepare not_null info 525 $info->not_null = $rawcolumn->is_nullable == 'NO' ? true : false; 526 527 // Process defaults 528 $info->has_default = !empty($rawcolumn->default_value); 529 if ($rawcolumn->default_value === NULL) { 530 $info->default_value = NULL; 531 } else { 532 $info->default_value = preg_replace("/^[\(N]+[']?(.*?)[']?[\)]+$/", '\\1', $rawcolumn->default_value); 533 } 534 535 // Process binary 536 $info->binary = $info->meta_type == 'B' ? true : false; 537 538 $structure[$info->name] = new database_column_info($info); 539 } 540 $this->free_result($result); 541 542 if ($usecache) { 543 if ($this->temptables->is_temptable($table)) { 544 $this->get_temp_tables_cache()->set($table, $structure); 545 } else { 546 $this->get_metacache()->set($table, $structure); 547 } 548 } 549 550 return $structure; 551 } 552 553 /** 554 * Normalise values based on varying RDBMS's dependencies (booleans, LOBs...) 555 * 556 * @param database_column_info $column column metadata corresponding with the value we are going to normalise 557 * @param mixed $value value we are going to normalise 558 * @return mixed the normalised value 559 */ 560 protected function normalise_value($column, $value) { 561 $this->detect_objects($value); 562 563 if (is_bool($value)) { // Always, convert boolean to int 564 $value = (int)$value; 565 } // And continue processing because text columns with numeric info need special handling below 566 567 if ($column->meta_type == 'B') { // BLOBs need to be properly "packed", but can be inserted directly if so. 568 if (!is_null($value)) { // If value not null, unpack it to unquoted hexadecimal byte-string format 569 $value = unpack('H*hex', $value); // we leave it as array, so emulate_bound_params() can detect it 570 } // easily and "bind" the param ok. 571 572 } else if ($column->meta_type == 'X') { // MSSQL doesn't cast from int to text, so if text column 573 if (is_numeric($value)) { // and is numeric value then cast to string 574 $value = array('numstr' => (string)$value); // and put into array, so emulate_bound_params() will know how 575 } // to "bind" the param ok, avoiding reverse conversion to number 576 577 } else if ($value === '') { 578 if ($column->meta_type == 'I' or $column->meta_type == 'F' or $column->meta_type == 'N') { 579 $value = 0; // prevent '' problems in numeric fields 580 } 581 } 582 return $value; 583 } 584 585 /** 586 * Selectively call mssql_free_result(), avoiding some warnings without using the horrible @ 587 * 588 * @param mssql_resource $resource resource to be freed if possible 589 */ 590 private function free_result($resource) { 591 if (!is_bool($resource)) { // true/false resources cannot be freed 592 mssql_free_result($resource); 593 } 594 } 595 596 /** 597 * Provides mapping between mssql native data types and moodle_database - database_column_info - ones) 598 * 599 * @param string $mssql_type native mssql data type 600 * @return string 1-char database_column_info data type 601 */ 602 private function mssqltype2moodletype($mssql_type) { 603 $type = null; 604 switch (strtoupper($mssql_type)) { 605 case 'BIT': 606 $type = 'L'; 607 break; 608 case 'INT': 609 case 'SMALLINT': 610 case 'INTEGER': 611 case 'BIGINT': 612 $type = 'I'; 613 break; 614 case 'DECIMAL': 615 case 'REAL': 616 case 'FLOAT': 617 $type = 'N'; 618 break; 619 case 'VARCHAR': 620 case 'NVARCHAR': 621 $type = 'C'; 622 break; 623 case 'TEXT': 624 case 'NTEXT': 625 case 'VARCHAR(MAX)': 626 case 'NVARCHAR(MAX)': 627 $type = 'X'; 628 break; 629 case 'IMAGE': 630 case 'VARBINARY': 631 case 'VARBINARY(MAX)': 632 $type = 'B'; 633 break; 634 case 'DATETIME': 635 $type = 'D'; 636 break; 637 } 638 if (!$type) { 639 throw new dml_exception('invalidmssqlnativetype', $mssql_type); 640 } 641 return $type; 642 } 643 644 /** 645 * Do NOT use in code, to be used by database_manager only! 646 * @param string|array $sql query 647 * @param array|null $tablenames an array of xmldb table names affected by this request. 648 * @return bool true 649 * @throws ddl_change_structure_exception A DDL specific exception is thrown for any errors. 650 */ 651 public function change_database_structure($sql, $tablenames = null) { 652 $this->get_manager(); // Includes DDL exceptions classes ;-) 653 $sqls = (array)$sql; 654 655 try { 656 foreach ($sqls as $sql) { 657 $this->query_start($sql, null, SQL_QUERY_STRUCTURE); 658 $result = mssql_query($sql, $this->mssql); 659 $this->query_end($result); 660 } 661 } catch (ddl_change_structure_exception $e) { 662 $this->reset_caches($tablenames); 663 throw $e; 664 } 665 666 $this->reset_caches($tablenames); 667 return true; 668 } 669 670 /** 671 * Very ugly hack which emulates bound parameters in queries 672 * because the mssql driver doesn't support placeholders natively at all 673 */ 674 protected function emulate_bound_params($sql, array $params=null) { 675 if (empty($params)) { 676 return $sql; 677 } 678 // ok, we have verified sql statement with ? and correct number of params 679 $parts = array_reverse(explode('?', $sql)); 680 $return = array_pop($parts); 681 foreach ($params as $param) { 682 if (is_bool($param)) { 683 $return .= (int)$param; 684 685 } else if (is_array($param) && isset($param['hex'])) { // detect hex binary, bind it specially 686 $return .= '0x' . $param['hex']; 687 688 } else if (is_array($param) && isset($param['numstr'])) { // detect numerical strings that *must not* 689 $return .= "N'{$param['numstr']}'"; // be converted back to number params, but bound as strings 690 691 } else if (is_null($param)) { 692 $return .= 'NULL'; 693 694 } else if (is_number($param)) { // we can not use is_numeric() because it eats leading zeros from strings like 0045646 695 $return .= "'".$param."'"; //fix for MDL-24863 to prevent auto-cast to int. 696 697 } else if (is_float($param)) { 698 $return .= $param; 699 700 } else { 701 $param = str_replace("'", "''", $param); 702 $param = str_replace("\0", "", $param); 703 $return .= "N'$param'"; 704 } 705 706 $return .= array_pop($parts); 707 } 708 return $return; 709 } 710 711 /** 712 * Execute general sql query. Should be used only when no other method suitable. 713 * Do NOT use this to make changes in db structure, use database_manager methods instead! 714 * @param string $sql query 715 * @param array $params query parameters 716 * @return bool true 717 * @throws dml_exception A DML specific exception is thrown for any errors. 718 */ 719 public function execute($sql, array $params=null) { 720 721 list($sql, $params, $type) = $this->fix_sql_params($sql, $params); 722 $rawsql = $this->emulate_bound_params($sql, $params); 723 724 if (strpos($sql, ';') !== false) { 725 throw new coding_exception('moodle_database::execute() Multiple sql statements found or bound parameters not used properly in query!'); 726 } 727 728 $this->query_start($sql, $params, SQL_QUERY_UPDATE); 729 $result = mssql_query($rawsql, $this->mssql); 730 $this->query_end($result); 731 $this->free_result($result); 732 733 return true; 734 } 735 736 /** 737 * Get a number of records as a moodle_recordset using a SQL statement. 738 * 739 * Since this method is a little less readable, use of it should be restricted to 740 * code where it's possible there might be large datasets being returned. For known 741 * small datasets use get_records_sql - it leads to simpler code. 742 * 743 * The return type is like: 744 * @see function get_recordset. 745 * 746 * @param string $sql the SQL select query to execute. 747 * @param array $params array of sql parameters 748 * @param int $limitfrom return a subset of records, starting at this point (optional, required if $limitnum is set). 749 * @param int $limitnum return a subset comprising this many records (optional, required if $limitfrom is set). 750 * @return moodle_recordset instance 751 * @throws dml_exception A DML specific exception is thrown for any errors. 752 */ 753 public function get_recordset_sql($sql, array $params=null, $limitfrom=0, $limitnum=0) { 754 755 list($limitfrom, $limitnum) = $this->normalise_limit_from_num($limitfrom, $limitnum); 756 757 if ($limitfrom or $limitnum) { 758 if (!$this->supportsoffsetfetch) { 759 if ($limitnum >= 1) { // Only apply TOP clause if we have any limitnum (limitfrom offset is handled later). 760 $fetch = $limitfrom + $limitnum; 761 if (PHP_INT_MAX - $limitnum < $limitfrom) { // Check PHP_INT_MAX overflow. 762 $fetch = PHP_INT_MAX; 763 } 764 $sql = preg_replace('/^([\s(])*SELECT([\s]+(DISTINCT|ALL))?(?!\s*TOP\s*\()/i', 765 "\\1SELECT\\2 TOP $fetch", $sql); 766 } 767 } else { 768 $sql = (substr($sql, -1) === ';') ? substr($sql, 0, -1) : $sql; 769 // We need order by to use FETCH/OFFSET. 770 // Ordering by first column shouldn't break anything if there was no order in the first place. 771 if (!strpos(strtoupper($sql), "ORDER BY")) { 772 $sql .= " ORDER BY 1"; 773 } 774 775 $sql .= " OFFSET ".$limitfrom." ROWS "; 776 777 if ($limitnum > 0) { 778 $sql .= " FETCH NEXT ".$limitnum." ROWS ONLY"; 779 } 780 } 781 } 782 783 list($sql, $params, $type) = $this->fix_sql_params($sql, $params); 784 $rawsql = $this->emulate_bound_params($sql, $params); 785 786 $this->query_start($sql, $params, SQL_QUERY_SELECT); 787 $result = mssql_query($rawsql, $this->mssql); 788 $this->query_end($result); 789 790 if ($limitfrom && !$this->supportsoffsetfetch) { // Skip $limitfrom records. 791 if (!@mssql_data_seek($result, $limitfrom)) { 792 // Nothing, most probably seek past the end. 793 mssql_free_result($result); 794 $result = null; 795 } 796 } 797 798 return $this->create_recordset($result); 799 } 800 801 protected function create_recordset($result) { 802 return new mssql_native_moodle_recordset($result); 803 } 804 805 /** 806 * Get a number of records as an array of objects using a SQL statement. 807 * 808 * Return value is like: 809 * @see function get_records. 810 * 811 * @param string $sql the SQL select query to execute. The first column of this SELECT statement 812 * must be a unique value (usually the 'id' field), as it will be used as the key of the 813 * returned array. 814 * @param array $params array of sql parameters 815 * @param int $limitfrom return a subset of records, starting at this point (optional, required if $limitnum is set). 816 * @param int $limitnum return a subset comprising this many records (optional, required if $limitfrom is set). 817 * @return array of objects, or empty array if no records were found 818 * @throws dml_exception A DML specific exception is thrown for any errors. 819 */ 820 public function get_records_sql($sql, array $params=null, $limitfrom=0, $limitnum=0) { 821 822 $rs = $this->get_recordset_sql($sql, $params, $limitfrom, $limitnum); 823 824 $results = array(); 825 826 foreach ($rs as $row) { 827 $id = reset($row); 828 if (isset($results[$id])) { 829 $colname = key($row); 830 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); 831 } 832 $results[$id] = $row; 833 } 834 $rs->close(); 835 836 return $results; 837 } 838 839 /** 840 * Selects records and return values (first field) as an array using a SQL statement. 841 * 842 * @param string $sql The SQL query 843 * @param array $params array of sql parameters 844 * @return array of values 845 * @throws dml_exception A DML specific exception is thrown for any errors. 846 */ 847 public function get_fieldset_sql($sql, array $params=null) { 848 849 $rs = $this->get_recordset_sql($sql, $params); 850 851 $results = array(); 852 853 foreach ($rs as $row) { 854 $results[] = reset($row); 855 } 856 $rs->close(); 857 858 return $results; 859 } 860 861 /** 862 * Insert new record into database, as fast as possible, no safety checks, lobs not supported. 863 * @param string $table name 864 * @param mixed $params data record as object or array 865 * @param bool $returnit return it of inserted record 866 * @param bool $bulk true means repeated inserts expected 867 * @param bool $customsequence true if 'id' included in $params, disables $returnid 868 * @return bool|int true or new id 869 * @throws dml_exception A DML specific exception is thrown for any errors. 870 */ 871 public function insert_record_raw($table, $params, $returnid=true, $bulk=false, $customsequence=false) { 872 if (!is_array($params)) { 873 $params = (array)$params; 874 } 875 876 $returning = ""; 877 $isidentity = false; 878 879 if ($customsequence) { 880 if (!isset($params['id'])) { 881 throw new coding_exception('moodle_database::insert_record_raw() id field must be specified if custom sequences used.'); 882 } 883 $returnid = false; 884 885 $columns = $this->get_columns($table); 886 if (isset($columns['id']) and $columns['id']->auto_increment) { 887 $isidentity = true; 888 } 889 890 // Disable IDENTITY column before inserting record with id, only if the 891 // column is identity, from meta information. 892 if ($isidentity) { 893 $sql = 'SET IDENTITY_INSERT {' . $table . '} ON'; // Yes, it' ON!! 894 list($sql, $xparams, $xtype) = $this->fix_sql_params($sql, null); 895 $this->query_start($sql, null, SQL_QUERY_AUX); 896 $result = mssql_query($sql, $this->mssql); 897 $this->query_end($result); 898 $this->free_result($result); 899 } 900 901 } else { 902 unset($params['id']); 903 if ($returnid) { 904 $returning = "OUTPUT inserted.id"; 905 } 906 } 907 908 if (empty($params)) { 909 throw new coding_exception('moodle_database::insert_record_raw() no fields found.'); 910 } 911 912 $fields = implode(',', array_keys($params)); 913 $qms = array_fill(0, count($params), '?'); 914 $qms = implode(',', $qms); 915 916 $sql = "INSERT INTO {" . $table . "} ($fields) $returning VALUES ($qms)"; 917 918 list($sql, $params, $type) = $this->fix_sql_params($sql, $params); 919 $rawsql = $this->emulate_bound_params($sql, $params); 920 921 $this->query_start($sql, $params, SQL_QUERY_INSERT); 922 $result = mssql_query($rawsql, $this->mssql); 923 // Expected results are: 924 // - true: insert ok and there isn't returned information. 925 // - false: insert failed and there isn't returned information. 926 // - resource: insert executed, need to look for returned (output) 927 // values to know if the insert was ok or no. Posible values 928 // are false = failed, integer = insert ok, id returned. 929 $end = false; 930 if (is_bool($result)) { 931 $end = $result; 932 } else if (is_resource($result)) { 933 $end = mssql_result($result, 0, 0); // Fetch 1st column from 1st row. 934 } 935 $this->query_end($end); // End the query with the calculated $end. 936 937 if ($returning !== "") { 938 $params['id'] = $end; 939 } 940 $this->free_result($result); 941 942 if ($customsequence) { 943 // Enable IDENTITY column after inserting record with id, only if the 944 // column is identity, from meta information. 945 if ($isidentity) { 946 $sql = 'SET IDENTITY_INSERT {' . $table . '} OFF'; // Yes, it' OFF!! 947 list($sql, $xparams, $xtype) = $this->fix_sql_params($sql, null); 948 $this->query_start($sql, null, SQL_QUERY_AUX); 949 $result = mssql_query($sql, $this->mssql); 950 $this->query_end($result); 951 $this->free_result($result); 952 } 953 } 954 955 if (!$returnid) { 956 return true; 957 } 958 959 return (int)$params['id']; 960 } 961 962 /** 963 * Insert a record into a table and return the "id" field if required. 964 * 965 * Some conversions and safety checks are carried out. Lobs are supported. 966 * If the return ID isn't required, then this just reports success as true/false. 967 * $data is an object containing needed data 968 * @param string $table The database table to be inserted into 969 * @param object $data A data object with values for one or more fields in the record 970 * @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. 971 * @return bool|int true or new id 972 * @throws dml_exception A DML specific exception is thrown for any errors. 973 */ 974 public function insert_record($table, $dataobject, $returnid=true, $bulk=false) { 975 $dataobject = (array)$dataobject; 976 977 $columns = $this->get_columns($table); 978 if (empty($columns)) { 979 throw new dml_exception('ddltablenotexist', $table); 980 } 981 $cleaned = array(); 982 983 foreach ($dataobject as $field => $value) { 984 if ($field === 'id') { 985 continue; 986 } 987 if (!isset($columns[$field])) { 988 continue; 989 } 990 $column = $columns[$field]; 991 $cleaned[$field] = $this->normalise_value($column, $value); 992 } 993 994 return $this->insert_record_raw($table, $cleaned, $returnid, $bulk); 995 } 996 997 /** 998 * Import a record into a table, id field is required. 999 * Safety checks are NOT carried out. Lobs are supported. 1000 * 1001 * @param string $table name of database table to be inserted into 1002 * @param object $dataobject A data object with values for one or more fields in the record 1003 * @return bool true 1004 * @throws dml_exception A DML specific exception is thrown for any errors. 1005 */ 1006 public function import_record($table, $dataobject) { 1007 $dataobject = (array)$dataobject; 1008 1009 $columns = $this->get_columns($table); 1010 $cleaned = array(); 1011 1012 foreach ($dataobject as $field => $value) { 1013 if (!isset($columns[$field])) { 1014 continue; 1015 } 1016 $column = $columns[$field]; 1017 $cleaned[$field] = $this->normalise_value($column, $value); 1018 } 1019 1020 $this->insert_record_raw($table, $cleaned, false, false, true); 1021 1022 return true; 1023 } 1024 1025 /** 1026 * Update record in database, as fast as possible, no safety checks, lobs not supported. 1027 * @param string $table name 1028 * @param mixed $params data record as object or array 1029 * @param bool true means repeated updates expected 1030 * @return bool true 1031 * @throws dml_exception A DML specific exception is thrown for any errors. 1032 */ 1033 public function update_record_raw($table, $params, $bulk=false) { 1034 $params = (array)$params; 1035 1036 if (!isset($params['id'])) { 1037 throw new coding_exception('moodle_database::update_record_raw() id field must be specified.'); 1038 } 1039 $id = $params['id']; 1040 unset($params['id']); 1041 1042 if (empty($params)) { 1043 throw new coding_exception('moodle_database::update_record_raw() no fields found.'); 1044 } 1045 1046 $sets = array(); 1047 foreach ($params as $field=>$value) { 1048 $sets[] = "$field = ?"; 1049 } 1050 1051 $params[] = $id; // last ? in WHERE condition 1052 1053 $sets = implode(',', $sets); 1054 $sql = "UPDATE {" . $table . "} SET $sets WHERE id = ?"; 1055 1056 list($sql, $params, $type) = $this->fix_sql_params($sql, $params); 1057 $rawsql = $this->emulate_bound_params($sql, $params); 1058 1059 $this->query_start($sql, $params, SQL_QUERY_UPDATE); 1060 $result = mssql_query($rawsql, $this->mssql); 1061 $this->query_end($result); 1062 1063 $this->free_result($result); 1064 return true; 1065 } 1066 1067 /** 1068 * Update a record in a table 1069 * 1070 * $dataobject is an object containing needed data 1071 * Relies on $dataobject having a variable "id" to 1072 * specify the record to update 1073 * 1074 * @param string $table The database table to be checked against. 1075 * @param object $dataobject An object with contents equal to fieldname=>fieldvalue. Must have an entry for 'id' to map to the table specified. 1076 * @param bool true means repeated updates expected 1077 * @return bool true 1078 * @throws dml_exception A DML specific exception is thrown for any errors. 1079 */ 1080 public function update_record($table, $dataobject, $bulk=false) { 1081 $dataobject = (array)$dataobject; 1082 1083 $columns = $this->get_columns($table); 1084 $cleaned = array(); 1085 1086 foreach ($dataobject as $field => $value) { 1087 if (!isset($columns[$field])) { 1088 continue; 1089 } 1090 $column = $columns[$field]; 1091 $cleaned[$field] = $this->normalise_value($column, $value); 1092 } 1093 1094 return $this->update_record_raw($table, $cleaned, $bulk); 1095 } 1096 1097 /** 1098 * Set a single field in every table record which match a particular WHERE clause. 1099 * 1100 * @param string $table The database table to be checked against. 1101 * @param string $newfield the field to set. 1102 * @param string $newvalue the value to set the field to. 1103 * @param string $select A fragment of SQL to be used in a where clause in the SQL call. 1104 * @param array $params array of sql parameters 1105 * @return bool true 1106 * @throws dml_exception A DML specific exception is thrown for any errors. 1107 */ 1108 public function set_field_select($table, $newfield, $newvalue, $select, array $params=null) { 1109 1110 if ($select) { 1111 $select = "WHERE $select"; 1112 } 1113 if (is_null($params)) { 1114 $params = array(); 1115 } 1116 1117 // convert params to ? types 1118 list($select, $params, $type) = $this->fix_sql_params($select, $params); 1119 1120 // Get column metadata 1121 $columns = $this->get_columns($table); 1122 $column = $columns[$newfield]; 1123 1124 $newvalue = $this->normalise_value($column, $newvalue); 1125 1126 if (is_null($newvalue)) { 1127 $newfield = "$newfield = NULL"; 1128 } else { 1129 $newfield = "$newfield = ?"; 1130 array_unshift($params, $newvalue); 1131 } 1132 $sql = "UPDATE {" . $table . "} SET $newfield $select"; 1133 1134 list($sql, $params, $type) = $this->fix_sql_params($sql, $params); 1135 $rawsql = $this->emulate_bound_params($sql, $params); 1136 1137 $this->query_start($sql, $params, SQL_QUERY_UPDATE); 1138 $result = mssql_query($rawsql, $this->mssql); 1139 $this->query_end($result); 1140 1141 $this->free_result($result); 1142 1143 return true; 1144 } 1145 1146 /** 1147 * Delete one or more records from a table which match a particular WHERE clause. 1148 * 1149 * @param string $table The database table to be checked against. 1150 * @param string $select A fragment of SQL to be used in a where clause in the SQL call (used to define the selection criteria). 1151 * @param array $params array of sql parameters 1152 * @return bool true 1153 * @throws dml_exception A DML specific exception is thrown for any errors. 1154 */ 1155 public function delete_records_select($table, $select, array $params=null) { 1156 1157 if ($select) { 1158 $select = "WHERE $select"; 1159 } 1160 1161 $sql = "DELETE FROM {" . $table . "} $select"; 1162 1163 list($sql, $params, $type) = $this->fix_sql_params($sql, $params); 1164 $rawsql = $this->emulate_bound_params($sql, $params); 1165 1166 $this->query_start($sql, $params, SQL_QUERY_UPDATE); 1167 $result = mssql_query($rawsql, $this->mssql); 1168 $this->query_end($result); 1169 1170 $this->free_result($result); 1171 1172 return true; 1173 } 1174 1175 public function sql_cast_char2int($fieldname, $text=false) { 1176 if (!$text) { 1177 return ' CAST(' . $fieldname . ' AS INT) '; 1178 } else { 1179 return ' CAST(' . $this->sql_compare_text($fieldname) . ' AS INT) '; 1180 } 1181 } 1182 1183 public function sql_cast_char2real($fieldname, $text=false) { 1184 if (!$text) { 1185 return ' CAST(' . $fieldname . ' AS REAL) '; 1186 } else { 1187 return ' CAST(' . $this->sql_compare_text($fieldname) . ' AS REAL) '; 1188 } 1189 } 1190 1191 public function sql_ceil($fieldname) { 1192 return ' CEILING(' . $fieldname . ')'; 1193 } 1194 1195 1196 protected function get_collation() { 1197 if (isset($this->collation)) { 1198 return $this->collation; 1199 } 1200 if (!empty($this->dboptions['dbcollation'])) { 1201 // perf speedup 1202 $this->collation = $this->dboptions['dbcollation']; 1203 return $this->collation; 1204 } 1205 1206 // make some default 1207 $this->collation = 'Latin1_General_CI_AI'; 1208 1209 $sql = "SELECT CAST(DATABASEPROPERTYEX('$this->dbname', 'Collation') AS varchar(255)) AS SQLCollation"; 1210 $this->query_start($sql, null, SQL_QUERY_AUX); 1211 $result = mssql_query($sql, $this->mssql); 1212 $this->query_end($result); 1213 1214 if ($result) { 1215 if ($rawcolumn = mssql_fetch_assoc($result)) { 1216 $this->collation = reset($rawcolumn); 1217 } 1218 $this->free_result($result); 1219 } 1220 1221 return $this->collation; 1222 } 1223 1224 /** 1225 * Returns 'LIKE' part of a query. 1226 * 1227 * @param string $fieldname usually name of the table column 1228 * @param string $param usually bound query parameter (?, :named) 1229 * @param bool $casesensitive use case sensitive search 1230 * @param bool $accensensitive use accent sensitive search (not all databases support accent insensitive) 1231 * @param bool $notlike true means "NOT LIKE" 1232 * @param string $escapechar escape char for '%' and '_' 1233 * @return string SQL code fragment 1234 */ 1235 public function sql_like($fieldname, $param, $casesensitive = true, $accentsensitive = true, $notlike = false, $escapechar = '\\') { 1236 if (strpos($param, '%') !== false) { 1237 debugging('Potential SQL injection detected, sql_like() expects bound parameters (? or :named)'); 1238 } 1239 1240 $collation = $this->get_collation(); 1241 1242 if ($casesensitive) { 1243 $collation = str_replace('_CI', '_CS', $collation); 1244 } else { 1245 $collation = str_replace('_CS', '_CI', $collation); 1246 } 1247 if ($accentsensitive) { 1248 $collation = str_replace('_AI', '_AS', $collation); 1249 } else { 1250 $collation = str_replace('_AS', '_AI', $collation); 1251 } 1252 1253 $LIKE = $notlike ? 'NOT LIKE' : 'LIKE'; 1254 1255 return "$fieldname COLLATE $collation $LIKE $param ESCAPE '$escapechar'"; 1256 } 1257 1258 public function sql_concat() { 1259 $arr = func_get_args(); 1260 foreach ($arr as $key => $ele) { 1261 $arr[$key] = ' CAST(' . $ele . ' AS NVARCHAR(255)) '; 1262 } 1263 $s = implode(' + ', $arr); 1264 if ($s === '') { 1265 return " '' "; 1266 } 1267 return " $s "; 1268 } 1269 1270 public function sql_concat_join($separator="' '", $elements=array()) { 1271 for ($n=count($elements)-1; $n > 0 ; $n--) { 1272 array_splice($elements, $n, 0, $separator); 1273 } 1274 return call_user_func_array(array($this, 'sql_concat'), $elements); 1275 } 1276 1277 public function sql_isempty($tablename, $fieldname, $nullablefield, $textfield) { 1278 if ($textfield) { 1279 return ' (' . $this->sql_compare_text($fieldname) . " = '') "; 1280 } else { 1281 return " ($fieldname = '') "; 1282 } 1283 } 1284 1285 /** 1286 * Returns the SQL text to be used to calculate the length in characters of one expression. 1287 * @param string fieldname or expression to calculate its length in characters. 1288 * @return string the piece of SQL code to be used in the statement. 1289 */ 1290 public function sql_length($fieldname) { 1291 return ' LEN(' . $fieldname . ')'; 1292 } 1293 1294 public function sql_order_by_text($fieldname, $numchars=32) { 1295 return " CONVERT(varchar({$numchars}), {$fieldname})"; 1296 } 1297 1298 /** 1299 * Returns the SQL for returning searching one string for the location of another. 1300 */ 1301 public function sql_position($needle, $haystack) { 1302 return "CHARINDEX(($needle), ($haystack))"; 1303 } 1304 1305 /** 1306 * Returns the proper substr() SQL text used to extract substrings from DB 1307 * NOTE: this was originally returning only function name 1308 * 1309 * @param string $expr some string field, no aggregates 1310 * @param mixed $start integer or expression evaluating to int 1311 * @param mixed $length optional integer or expression evaluating to int 1312 * @return string sql fragment 1313 */ 1314 public function sql_substr($expr, $start, $length=false) { 1315 if (count(func_get_args()) < 2) { 1316 throw new coding_exception('moodle_database::sql_substr() requires at least two parameters', 'Originaly this function wa 1317 s only returning name of SQL substring function, it now requires all parameters.'); 1318 } 1319 if ($length === false) { 1320 return "SUBSTRING($expr, " . $this->sql_cast_char2int($start) . ", 2^31-1)"; 1321 } else { 1322 return "SUBSTRING($expr, " . $this->sql_cast_char2int($start) . ", " . $this->sql_cast_char2int($length) . ")"; 1323 } 1324 } 1325 1326 /** 1327 * Does this driver support tool_replace? 1328 * 1329 * @since Moodle 2.6.1 1330 * @return bool 1331 */ 1332 public function replace_all_text_supported() { 1333 return true; 1334 } 1335 1336 public function session_lock_supported() { 1337 return true; 1338 } 1339 1340 /** 1341 * Obtain session lock 1342 * @param int $rowid id of the row with session record 1343 * @param int $timeout max allowed time to wait for the lock in seconds 1344 * @return bool success 1345 */ 1346 public function get_session_lock($rowid, $timeout) { 1347 if (!$this->session_lock_supported()) { 1348 return; 1349 } 1350 parent::get_session_lock($rowid, $timeout); 1351 1352 $timeoutmilli = $timeout * 1000; 1353 1354 $fullname = $this->dbname.'-'.$this->prefix.'-session-'.$rowid; 1355 // There is one bug in PHP/freetds (both reproducible with mssql_query() 1356 // and its mssql_init()/mssql_bind()/mssql_execute() alternative) for 1357 // stored procedures, causing scalar results of the execution 1358 // to be cast to boolean (true/fals). Here there is one 1359 // workaround that forces the return of one recordset resource. 1360 // $sql = "sp_getapplock '$fullname', 'Exclusive', 'Session', $timeoutmilli"; 1361 $sql = "BEGIN 1362 DECLARE @result INT 1363 EXECUTE @result = sp_getapplock @Resource='$fullname', 1364 @LockMode='Exclusive', 1365 @LockOwner='Session', 1366 @LockTimeout='$timeoutmilli' 1367 SELECT @result 1368 END"; 1369 $this->query_start($sql, null, SQL_QUERY_AUX); 1370 $result = mssql_query($sql, $this->mssql); 1371 $this->query_end($result); 1372 1373 if ($result) { 1374 $row = mssql_fetch_row($result); 1375 if ($row[0] < 0) { 1376 throw new dml_sessionwait_exception(); 1377 } 1378 } 1379 1380 $this->free_result($result); 1381 } 1382 1383 public function release_session_lock($rowid) { 1384 if (!$this->session_lock_supported()) { 1385 return; 1386 } 1387 if (!$this->used_for_db_sessions) { 1388 return; 1389 } 1390 1391 parent::release_session_lock($rowid); 1392 1393 $fullname = $this->dbname.'-'.$this->prefix.'-session-'.$rowid; 1394 $sql = "sp_releaseapplock '$fullname', 'Session'"; 1395 $this->query_start($sql, null, SQL_QUERY_AUX); 1396 $result = mssql_query($sql, $this->mssql); 1397 $this->query_end($result); 1398 1399 $this->free_result($result); 1400 } 1401 1402 /** 1403 * Driver specific start of real database transaction, 1404 * this can not be used directly in code. 1405 * @return void 1406 */ 1407 protected function begin_transaction() { 1408 // requires database to run with READ_COMMITTED_SNAPSHOT ON 1409 $sql = "BEGIN TRANSACTION"; // Will be using READ COMMITTED isolation 1410 $this->query_start($sql, NULL, SQL_QUERY_AUX); 1411 $result = mssql_query($sql, $this->mssql); 1412 $this->query_end($result); 1413 1414 $this->free_result($result); 1415 } 1416 1417 /** 1418 * Driver specific commit of real database transaction, 1419 * this can not be used directly in code. 1420 * @return void 1421 */ 1422 protected function commit_transaction() { 1423 $sql = "COMMIT TRANSACTION"; 1424 $this->query_start($sql, NULL, SQL_QUERY_AUX); 1425 $result = mssql_query($sql, $this->mssql); 1426 $this->query_end($result); 1427 1428 $this->free_result($result); 1429 } 1430 1431 /** 1432 * Driver specific abort of real database transaction, 1433 * this can not be used directly in code. 1434 * @return void 1435 */ 1436 protected function rollback_transaction() { 1437 $sql = "ROLLBACK TRANSACTION"; 1438 $this->query_start($sql, NULL, SQL_QUERY_AUX); 1439 $result = mssql_query($sql, $this->mssql); 1440 $this->query_end($result); 1441 1442 $this->free_result($result); 1443 } 1444 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Thu Aug 11 10:00:09 2016 | Cross-referenced by PHPXref 0.7.1 |