database.inc 37 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048
  1. <?php
  2. /**
  3. * @file
  4. * Database interface code for Microsoft SQL Server.
  5. */
  6. /**
  7. * @ingroup database
  8. * @{
  9. */
  10. include_once 'fastcache.inc';
  11. include_once 'enum.inc';
  12. include_once 'transaction.inc';
  13. include_once 'utils.inc';
  14. include_once 'context.inc';
  15. /**
  16. * Summary of DatabaseConnection_sqlsrv
  17. *
  18. * Temporary tables: temporary table support is done by means of global temporary tables (#)
  19. * to avoid the use of DIRECT QUERIES. You can enable and disable the use of direct queries
  20. * with $conn->directQuery = TRUE|FALSE.
  21. * http://blogs.msdn.com/b/brian_swan/archive/2010/06/15/ctp2-of-microsoft-driver-for-php-for-sql-server-released.aspx
  22. *
  23. */
  24. class DatabaseConnection_sqlsrv extends DatabaseConnection {
  25. // Do not preprocess the query before execution.
  26. public $bypassQueryPreprocess = FALSE;
  27. // Prepare statements with SQLSRV_ATTR_DIRECT_QUERY = TRUE.
  28. public $directQuery = FALSE;
  29. // Wether to have or not statement caching.
  30. public $statementCaching = FALSE;
  31. /**
  32. * Override of DatabaseConnection::driver().
  33. *
  34. * @status tested
  35. */
  36. public function driver() {
  37. return 'sqlsrv';
  38. }
  39. /**
  40. * Override of DatabaseConnection::databaseType().
  41. *
  42. * @status tested
  43. */
  44. public function databaseType() {
  45. return 'sqlsrv';
  46. }
  47. /**
  48. * The PDO constants do not matcc the actual isolation names
  49. * used in SQL.
  50. */
  51. private static function DefaultTransactionIsolationLevelInStatement() {
  52. return str_replace('_', ' ', DatabaseUtils::GetConfigConstant('MSSQL_DEFAULT_ISOLATION_LEVEL', FALSE));
  53. }
  54. /**
  55. * Override of DatabaseConnection::databaseType().
  56. *
  57. * @status complete
  58. */
  59. public function __construct(array $connection_options = array()) {
  60. global $conf;
  61. // Store connection options for future reference.
  62. $this->connectionOptions = &$connection_options;
  63. // Set our custom statement class.
  64. $this->statementClass = 'DatabaseStatement_sqlsrv';
  65. // This driver defaults to transaction support, except if explicitly passed FALSE.
  66. $this->transactionSupport = !isset($connection_options['transactions']) || $connection_options['transactions'] !== FALSE;
  67. $this->transactionalDDLSupport = $this->transactionSupport;
  68. // Build the DSN.
  69. $options = array();
  70. $options['Server'] = $connection_options['host'] . (!empty($connection_options['port']) ? ',' . $connection_options['port'] : '');
  71. // We might not have a database in the
  72. // connection options, for example, during
  73. // database creation in Install.
  74. if (!empty($connection_options['database'])) {
  75. $options['Database'] = $connection_options['database'];
  76. }
  77. // Set isolation level if specified.
  78. if ($level = DatabaseUtils::GetConfigConstant('MSSQL_DEFAULT_ISOLATION_LEVEL', FALSE)) {
  79. $options['TransactionIsolation'] = $level;
  80. }
  81. // Set default direct query behaviour
  82. $this->directQuery = DatabaseUtils::GetConfigBoolean('MSSQL_DEFAULT_DIRECTQUERY');
  83. $this->statementCaching = DatabaseUtils::GetConfigBoolean('MSSQL_STATEMENT_CACHING');
  84. // Build the DSN
  85. $dsn = 'sqlsrv:';
  86. foreach ($options as $key => $value) {
  87. $dsn .= (empty($key) ? '' : "{$key}=") . $value . ';';
  88. }
  89. // PDO Options are set at a connection level.
  90. // and apply to all statements.
  91. $connection_options['pdo'] = array();
  92. // Set proper error mode for all statements
  93. $connection_options['pdo'][PDO::ATTR_ERRMODE] = PDO::ERRMODE_EXCEPTION;
  94. // Set a Statement class, unless the driver opted out.
  95. if (!empty($this->statementClass)) {
  96. $connection_options['pdo'][PDO::ATTR_STATEMENT_CLASS] = array($this->statementClass, array($this));
  97. }
  98. // Initialize and prepare the connection prefix.
  99. $this->setPrefix(isset($this->connectionOptions['prefix']) ? $this->connectionOptions['prefix'] : '');
  100. // Call PDO::__construct and PDO::setAttribute.
  101. PDO::__construct($dsn, $connection_options['username'], $connection_options['password'], $connection_options['pdo']);
  102. }
  103. /**
  104. * Prepared PDO statements only makes sense if we cache them...
  105. *
  106. * @var mixed
  107. */
  108. private $statement_cache = array();
  109. /**
  110. * Temporary override of DatabaseConnection::prepareQuery().
  111. *
  112. * @todo: remove that when DatabaseConnection::prepareQuery() is fixed to call
  113. * $this->prepare() and not parent::prepare().
  114. * @status: tested, temporary
  115. *
  116. * @param mixed $query
  117. * @param mixed $insecure
  118. * @return PDOStatement
  119. */
  120. public function prepareQuery($query, array $options = array()) {
  121. // Merge default statement options. These options are
  122. // only specific for this preparation and will only override
  123. // the global configuration if set to different than NULL.
  124. $options = array_merge(array(
  125. 'insecure' => FALSE,
  126. 'statement_caching' => $this->statementCaching,
  127. 'direct_query' => $this->directQuery,
  128. 'prefix_tables' => TRUE,
  129. ), $options);
  130. // Prefix tables. There is no global setting for this.
  131. if ($options['prefix_tables'] !== FALSE) {
  132. $query = $this->prefixTables($query);
  133. }
  134. // The statement caching settings only affect the storage
  135. // in the cache, but if a statement is already available
  136. // why not reuse it!
  137. if (isset($this->statement_cache[$query])) {
  138. return $this->statement_cache[$query];
  139. }
  140. #region PDO Options
  141. $pdo_options = array();
  142. // Set insecure options if requested so. There is no global
  143. // setting for this, only at the statement level.
  144. if ($options['insecure'] === TRUE) {
  145. // We have to log this, prepared statements are a security RISK.
  146. // watchdog('SQL Server Driver', 'An insecure query has been executed against the database. This is not critical, but worth looking into: %query', array('%query' => $query));
  147. // These are defined in class Connection.
  148. // This PDO options are INSECURE, but will overcome the following issues:
  149. // (1) Duplicate placeholders
  150. // (2) > 2100 parameter limit
  151. // (3) Using expressions for group by with parameters are not detected as equal.
  152. // This options are not applied by default, they are just stored in the connection
  153. // options and applied when needed. See {Statement} class.
  154. // We ask PDO to perform the placeholders replacement itself because
  155. // SQL Server is not able to detect duplicated placeholders in
  156. // complex statements.
  157. // E.g. This query is going to fail because SQL Server cannot
  158. // detect that length1 and length2 are equals.
  159. // SELECT SUBSTRING(title, 1, :length1)
  160. // FROM node
  161. // GROUP BY SUBSTRING(title, 1, :length2
  162. // This is only going to work in PDO 3 but doesn't hurt in PDO 2.
  163. // The security of parameterized queries is not in effect when you use PDO::ATTR_EMULATE_PREPARES => true.
  164. // Your application should ensure that the data that is bound to the parameter(s) does not contain malicious
  165. // Transact-SQL code.
  166. // Never use this when you need special column binding.
  167. // THIS ONLY WORKS IF SET AT THE STATEMENT LEVEL.
  168. $pdo_options[PDO::ATTR_EMULATE_PREPARES] = TRUE;
  169. }
  170. // We run the statements in "direct mode" because the way PDO prepares
  171. // statement in non-direct mode cause temporary tables to be destroyed
  172. // at the end of the statement.
  173. // If you are using the PDO_SQLSRV driver and you want to execute a query that
  174. // changes a database setting (e.g. SET NOCOUNT ON), use the PDO::query method with
  175. // the PDO::SQLSRV_ATTR_DIRECT_QUERY attribute.
  176. // http://blogs.iis.net/bswan/archive/2010/12/09/how-to-change-database-settings-with-the-pdo-sqlsrv-driver.aspx
  177. // If a query requires the context that was set in a previous query,
  178. // you should execute your queries with PDO::SQLSRV_ATTR_DIRECT_QUERY set to True.
  179. // For example, if you use temporary tables in your queries, PDO::SQLSRV_ATTR_DIRECT_QUERY must be set
  180. // to True.
  181. // If we are not going to cache prepared statements, always use direct queries!
  182. // There is no point in preparing the statement if you are not going to cache it.
  183. if (!$this->statementCaching || $options['direct_query'] == TRUE) {
  184. $pdo_options[PDO::SQLSRV_ATTR_DIRECT_QUERY] = TRUE;
  185. }
  186. // It creates a cursor for the query, which allows you to iterate over the result set
  187. // without fetching the whole result at once. A scrollable cursor, specifically, is one that allows
  188. // iterating backwards.
  189. // https://msdn.microsoft.com/en-us/library/hh487158%28v=sql.105%29.aspx
  190. $pdo_options[PDO::ATTR_CURSOR] = PDO::CURSOR_SCROLL;
  191. // Lets you access rows in any order. Creates a client-side cursor query.
  192. $pdo_options[PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE] = PDO::SQLSRV_CURSOR_BUFFERED;
  193. #endregion
  194. // Call our overriden prepare.
  195. $stmt = $this->PDOPrepare($query, $pdo_options);
  196. // If statement caching is enabled, store current statement for reuse
  197. if ($options['statement_caching'] === TRUE) {
  198. $this->statement_cache[$query] = $stmt;
  199. }
  200. return $stmt;
  201. }
  202. /**
  203. * Internal function: prepare a query by calling PDO directly.
  204. *
  205. * This function has to be public because it is called by other parts of the
  206. * database layer, but do not call it directly, as you risk locking down the
  207. * PHP process.
  208. */
  209. public function PDOPrepare($query, array $options = array()) {
  210. // Preprocess the query.
  211. if (!$this->bypassQueryPreprocess) {
  212. $query = $this->preprocessQuery($query);
  213. }
  214. // You can set the MSSQL_APPEND_CALLSTACK_COMMENT to TRUE
  215. // to append to each query, in the form of comments, the current
  216. // backtrace plus other details that aid in debugging deadlocks
  217. // or long standing locks. Use in combination with MSSQL profiler.
  218. global $conf;
  219. if (DatabaseUtils::GetConfigBoolean('MSSQL_APPEND_CALLSTACK_COMMENT') == TRUE) {
  220. global $user;
  221. $trim = strlen(DRUPAL_ROOT);
  222. $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
  223. static $request_id;
  224. if (empty($request_id)) {
  225. $request_id = uniqid('', TRUE);
  226. }
  227. // Remove las item (it's alwasy PDOPrepare)
  228. $trace = array_splice($trace, 1);
  229. $comment = PHP_EOL . PHP_EOL;
  230. $comment .= '-- uid:' . (empty($user) ? 'null' : $user->uid) . PHP_EOL;
  231. $uri = (isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : 'none') ;
  232. $uri = preg_replace("/[^a-zA-Z0-9]/i", "_", $uri);
  233. $comment .= '-- url:' . $uri . PHP_EOL;
  234. //$comment .= '-- request_id:' . $request_id . PHP_EOL;
  235. foreach ($trace as $t) {
  236. $function = isset($t['function']) ? $t['function'] : '';
  237. $file = '';
  238. if(isset($t['file'])) {
  239. $len = strlen($t['file']);
  240. if ($len > $trim) {
  241. $file = substr($t['file'], $trim, $len - $trim) . " [{$t['line']}]";
  242. }
  243. }
  244. $comment .= '-- ' . str_pad($function, 35) . ' ' . $file . PHP_EOL;
  245. }
  246. $query = $comment . PHP_EOL . $query;
  247. }
  248. return parent::prepare($query, $options);
  249. }
  250. /**
  251. * This is the original replacement regexp from Microsoft.
  252. *
  253. * We could probably simplify it a lot because queries only contain
  254. * placeholders when we modify them.
  255. *
  256. * NOTE: removed 'escape' from the list, because it explodes
  257. * with LIKE xxx ESCAPE yyy syntax.
  258. */
  259. const RESERVED_REGEXP = '/\G
  260. # Everything that follows a boundary that is not : or _.
  261. \b(?<![:\[_])(?:
  262. # Any reserved words, followed by a boundary that is not an opening parenthesis.
  263. (action|admin|alias|any|are|array|at|begin|boolean|class|commit|contains|current|data|date|day|depth|domain|external|file|full|function|get|go|host|input|language|last|less|local|map|min|module|new|no|object|old|open|operation|parameter|parameters|path|plan|prefix|proc|public|ref|result|returns|role|rule|save|search|second|section|session|size|state|statistics|temporary|than|time|timestamp|tran|translate|translation|trim|user|value|variable|view|without)
  264. (?!\()
  265. |
  266. # Or a normal word.
  267. ([a-z]+)
  268. )\b
  269. |
  270. \b(
  271. [^a-z\'"\\\\]+
  272. )\b
  273. |
  274. (?=[\'"])
  275. (
  276. " [^\\\\"] * (?: \\\\. [^\\\\"] *) * "
  277. |
  278. \' [^\\\\\']* (?: \\\\. [^\\\\\']*) * \'
  279. )
  280. /Six';
  281. /**
  282. * This method gets called between 3,000 and 10,000 times
  283. * on cold caches. Make sure it is simple and fast.
  284. *
  285. * @param mixed $matches
  286. * @return mixed
  287. */
  288. protected function replaceReservedCallback($matches) {
  289. if ($matches[1] !== '') {
  290. // Replace reserved words. We are not calling
  291. // quoteIdentifier() on purpose.
  292. return '[' . $matches[1] . ']';
  293. }
  294. // Let other value passthru.
  295. // by the logic of the regex above, this will always be the last match.
  296. return end($matches);
  297. }
  298. public function quoteIdentifier($identifier) {
  299. return '[' . $identifier .']';
  300. }
  301. public function escapeField($field) {
  302. if ($cache = fastcache::cache_get($field, 'schema_escapeField')) {
  303. return $cache->data;
  304. }
  305. if (strlen($field) > 0) {
  306. $result = implode('.', array_map(array($this, 'quoteIdentifier'), explode('.', preg_replace('/[^A-Za-z0-9_.]+/', '', $field))));
  307. }
  308. else {
  309. $result = '';
  310. }
  311. fastcache::cache_set($field, $result, 'schema_escapeField');
  312. return $result;
  313. }
  314. public function quoteIdentifiers($identifiers) {
  315. return array_map(array($this, 'quoteIdentifier'), $identifiers);
  316. }
  317. /**
  318. * Override of DatabaseConnection::escapeLike().
  319. */
  320. public function escapeLike($string) {
  321. return preg_replace('/([\\[\\]%_])/', '[$1]', $string);
  322. }
  323. /**
  324. * Override of DatabaseConnection::queryRange().
  325. */
  326. public function queryRange($query, $from, $count, array $args = array(), array $options = array()) {
  327. $query = $this->addRangeToQuery($query, $from, $count);
  328. return $this->query($query, $args, $options);
  329. }
  330. /**
  331. * Generates a temporary table name. Because we are using
  332. * global temporary tables, these are visible between
  333. * connections so we need to make sure that their
  334. * names are as unique as possible to prevent collisions.
  335. *
  336. * @return
  337. * A table name.
  338. */
  339. protected function generateTemporaryTableName() {
  340. static $temp_key;
  341. if (!isset($temp_key)) {
  342. $temp_key = strtoupper(md5(uniqid(rand(), true)));
  343. }
  344. return "db_temp_" . $this->temporaryNameIndex++ . '_' . $temp_key;
  345. }
  346. /**
  347. * Override of DatabaseConnection::queryTemporary().
  348. *
  349. * @status tested
  350. */
  351. public function queryTemporary($query, array $args = array(), array $options = array()) {
  352. // Generate a new GLOBAL temporary table name and protect it from prefixing.
  353. // SQL Server requires that temporary tables to be non-qualified.
  354. $tablename = '##' . $this->generateTemporaryTableName();
  355. $prefixes = $this->prefixes;
  356. $prefixes[$tablename] = '';
  357. $this->setPrefix($prefixes);
  358. // Having comments in the query can be tricky and break the SELECT FROM -> SELECT INTO conversion
  359. $query = $this->schema()->removeSQLComments($query);
  360. // Replace SELECT xxx FROM table by SELECT xxx INTO #table FROM table.
  361. $query = preg_replace('/^SELECT(.*?)FROM/is', 'SELECT$1 INTO ' . $tablename . ' FROM', $query);
  362. $this->query($query, $args, $options);
  363. return $tablename;
  364. }
  365. /**
  366. * {@inheritdoc}
  367. *
  368. * This method is overriden to manage the insecure (EMULATE_PREPARE)
  369. * behaviour to prevent some compatibility issues with SQL Server.
  370. */
  371. public function query($query, array $args = array(), $options = array()) {
  372. // Use default values if not already set.
  373. $options += $this->defaultOptions();
  374. $stmt = NULL;
  375. try {
  376. // We allow either a pre-bound statement object or a literal string.
  377. // In either case, we want to end up with an executed statement object,
  378. // which we pass to PDOStatement::execute.
  379. if ($query instanceof DatabaseStatementInterface) {
  380. $stmt = $query;
  381. $stmt->execute(NULL, $options);
  382. }
  383. else {
  384. $this->expandArguments($query, $args);
  385. $insecure = isset($options['insecure']) ? $options['insecure'] : FALSE;
  386. // Try to detect duplicate place holders, this check's performance
  387. // is not a good addition to the driver, but does a good job preventing
  388. // duplicate placeholder errors.
  389. $argcount = count($args);
  390. if ($insecure === TRUE || $argcount >= 2100 || ($argcount != substr_count($query, ':'))) {
  391. $insecure = TRUE;
  392. }
  393. $stmt = $this->prepareQuery($query, array('insecure' => TRUE));
  394. $stmt->execute($args, $options);
  395. }
  396. // Depending on the type of query we may need to return a different value.
  397. // See DatabaseConnection::defaultOptions() for a description of each
  398. // value.
  399. switch ($options['return']) {
  400. case Database::RETURN_STATEMENT:
  401. return $stmt;
  402. case Database::RETURN_AFFECTED:
  403. return $stmt->rowCount();
  404. case Database::RETURN_INSERT_ID:
  405. return $this->lastInsertId();
  406. case Database::RETURN_NULL:
  407. return NULL;
  408. default:
  409. throw new PDOException('Invalid return directive: ' . $options['return']);
  410. }
  411. }
  412. catch (PDOException $e) {
  413. // Most database drivers will return NULL here, but some of them
  414. // (e.g. the SQLite driver) may need to re-run the query, so the return
  415. // value will be the same as for static::query().
  416. return $this->handleQueryException($e, $stmt, $args, $options);
  417. }
  418. }
  419. /**
  420. * Like query but with no insecure detection or query preprocessing.
  421. * The caller is sure that his query is MS SQL compatible! Used internally
  422. * from the schema class, but could be called from anywhere.
  423. *
  424. * @param mixed $query
  425. * @param array $args
  426. * @param mixed $options
  427. * @throws PDOException
  428. * @return mixed
  429. */
  430. public function query_direct($query, array $args = array(), $options = array()) {
  431. // Use default values if not already set.
  432. $options += $this->defaultOptions();
  433. $stmt = NULL;
  434. try {
  435. // Bypass query preprocessing and use direct queries.
  436. $ctx = new \DatabaseContext($this, TRUE, TRUE);
  437. // Prepare the statement and execute it.
  438. $stmt = $this->prepareQuery($query, $options);
  439. $stmt->execute($args, $options);
  440. // Reset the context settings.
  441. unset($ctx);
  442. // Depending on the type of query we may need to return a different value.
  443. // See DatabaseConnection::defaultOptions() for a description of each
  444. // value.
  445. switch ($options['return']) {
  446. case Database::RETURN_STATEMENT:
  447. return $stmt;
  448. case Database::RETURN_AFFECTED:
  449. return $stmt->rowCount();
  450. case Database::RETURN_INSERT_ID:
  451. return $this->lastInsertId();
  452. case Database::RETURN_NULL:
  453. return NULL;
  454. default:
  455. throw new PDOException('Invalid return directive: ' . $options['return']);
  456. }
  457. }
  458. catch (PDOException $e) {
  459. // Most database drivers will return NULL here, but some of them
  460. // (e.g. the SQLite driver) may need to re-run the query, so the return
  461. // value will be the same as for static::query().
  462. return $this->handleQueryException($e, $stmt, $args, $options);
  463. }
  464. }
  465. /**
  466. * Wraps and re-throws any PDO exception thrown by static::query().
  467. *
  468. * @param \PDOException $e
  469. * The exception thrown by static::query().
  470. * @param $query
  471. * The query executed by static::query().
  472. * @param array $args
  473. * An array of arguments for the prepared statement.
  474. * @param array $options
  475. * An associative array of options to control how the query is run.
  476. *
  477. * @return DatabaseStatementInterface|int|null
  478. * Most database drivers will return NULL when a PDO exception is thrown for
  479. * a query, but some of them may need to re-run the query, so they can also
  480. * return a \Drupal\Core\Database\StatementInterface object or an integer.
  481. */
  482. public function handleQueryException(\PDOException $e, $query, array $args = array(), $options = array()) {
  483. if ($options['throw_exception']) {
  484. // Add additional debug information.
  485. if ($query instanceof DatabaseStatementInterface) {
  486. $e->query_string = $query->getQueryString();
  487. }
  488. else {
  489. $e->query_string = $query;
  490. }
  491. $e->args = $args;
  492. throw $e;
  493. }
  494. return NULL;
  495. }
  496. /**
  497. * Internal function: massage a query to make it compliant with SQL Server.
  498. */
  499. public function preprocessQuery($query) {
  500. // Generate a cache signature for this query.
  501. $query_signature = 'query_cache_' . md5($query);
  502. // Drill through everything...
  503. $success = FALSE;
  504. $cache = wincache_ucache_get($query_signature, $success);
  505. if ($success) {
  506. return $cache;
  507. }
  508. // Force quotes around some SQL Server reserved keywords.
  509. if (preg_match('/^SELECT/i', $query)) {
  510. $query = preg_replace_callback(self::RESERVED_REGEXP, array($this, 'replaceReservedCallback'), $query);
  511. }
  512. // Last chance to modify some SQL Server-specific syntax.
  513. $replacements = array();
  514. // Add prefixes to Drupal-specific functions.
  515. $defaultSchema = $this->schema()->GetDefaultSchema();
  516. foreach ($this->schema()->DrupalSpecificFunctions() as $function) {
  517. $replacements['/\b(?<![:.])(' . preg_quote($function) . ')\(/i'] = "{$defaultSchema}.$1(";
  518. }
  519. // Rename some functions.
  520. $funcs = array(
  521. 'LENGTH' => 'LEN',
  522. 'POW' => 'POWER',
  523. );
  524. foreach ($funcs as $function => $replacement) {
  525. $replacements['/\b(?<![:.])(' . preg_quote($function) . ')\(/i'] = $replacement . '(';
  526. }
  527. // Replace the ANSI concatenation operator with SQL Server poor one.
  528. $replacements['/\|\|/'] = '+';
  529. // Now do all the replacements at once.
  530. $query = preg_replace(array_keys($replacements), array_values($replacements), $query);
  531. // Store the processed query, and make sure we expire it some time
  532. // so that scarcely used queries don't stay in the cache forever.
  533. wincache_ucache_set($query_signature, $query, rand(600, 3600));
  534. return $query;
  535. }
  536. /**
  537. * Internal function: add range options to a query.
  538. *
  539. * This cannot be set protected because it is used in other parts of the
  540. * database engine.
  541. *
  542. * @status tested
  543. */
  544. public function addRangeToQuery($query, $from, $count) {
  545. if ($from == 0) {
  546. // Easy case: just use a TOP query if we don't have to skip any rows.
  547. $query = preg_replace('/^\s*SELECT(\s*DISTINCT)?/Dsi', 'SELECT$1 TOP(' . $count . ')', $query);
  548. }
  549. else {
  550. if ($this->schema()->EngineVersionNumber() >= 11) {
  551. // As of SQL Server 2012 there is an easy (and faster!) way to page results.
  552. $query = $query .= " OFFSET {$from} ROWS FETCH NEXT {$count} ROWS ONLY";
  553. }
  554. else {
  555. // More complex case: use a TOP query to retrieve $from + $count rows, and
  556. // filter out the first $from rows using a window function.
  557. $query = preg_replace('/^\s*SELECT(\s*DISTINCT)?/Dsi', 'SELECT$1 TOP(' . ($from + $count) . ') ', $query);
  558. $query = '
  559. SELECT * FROM (
  560. SELECT sub2.*, ROW_NUMBER() OVER(ORDER BY sub2.__line2) AS __line3 FROM (
  561. SELECT 1 AS __line2, sub1.* FROM (' . $query . ') AS sub1
  562. ) as sub2
  563. ) AS sub3
  564. WHERE __line3 BETWEEN ' . ($from + 1) . ' AND ' . ($from + $count);
  565. }
  566. }
  567. return $query;
  568. }
  569. public function mapConditionOperator($operator) {
  570. // SQL Server doesn't need special escaping for the \ character in a string
  571. // literal, because it uses '' to escape the single quote, not \'.
  572. static $specials = array(
  573. 'LIKE' => array(),
  574. 'NOT LIKE' => array(),
  575. );
  576. return isset($specials[$operator]) ? $specials[$operator] : NULL;
  577. }
  578. /**
  579. * Override of DatabaseConnection::nextId().
  580. *
  581. * @status tested
  582. */
  583. public function nextId($existing = 0) {
  584. // If an exiting value is passed, for its insertion into the sequence table.
  585. if ($existing > 0) {
  586. try {
  587. $this->query_direct('SET IDENTITY_INSERT {sequences} ON; INSERT INTO {sequences} (value) VALUES(:existing); SET IDENTITY_INSERT {sequences} OFF', array(':existing' => $existing));
  588. }
  589. catch (Exception $e) {
  590. // Doesn't matter if this fails, it just means that this value is already
  591. // present in the table.
  592. }
  593. }
  594. // Refactored to use OUTPUT because under high concurrency LAST_INSERTED_ID does not work properly.
  595. return $this->query_direct('INSERT INTO {sequences} OUTPUT (Inserted.[value]) DEFAULT VALUES')->fetchField();
  596. }
  597. /**
  598. * Override DatabaseConnection::escapeTable().
  599. *
  600. * @status needswork
  601. */
  602. public function escapeTable($table) {
  603. // A static cache is better suited for this.
  604. static $tables = array();
  605. if (isset($tables[$table])) {
  606. return $tables[$table];
  607. }
  608. // Rescue the # prefix from the escaping.
  609. $is_temporary = $table[0] == '#';
  610. $is_temporary_global = $is_temporary && isset($table[1]) && $table[1] == '#';
  611. // Any temporary table prefix will be removed.
  612. $result = preg_replace('/[^A-Za-z0-9_.]+/', '', $table);
  613. // Restore the temporary prefix.
  614. if ($is_temporary) {
  615. if ($is_temporary_global) {
  616. $result = '##' . $result;
  617. }
  618. else {
  619. $result = '#' . $result;
  620. }
  621. }
  622. $tables[$table] = $result;
  623. return $result;
  624. }
  625. #region Transactions
  626. /**
  627. * Overriden to allow transaction settings.
  628. */
  629. public function startTransaction($name = '', DatabaseTransactionSettings $settings = NULL) {
  630. if ($settings == NULL) {
  631. $settings = DatabaseTransactionSettings::GetDefaults();
  632. }
  633. return new DatabaseTransaction_sqlsrv($this, $name, $settings);
  634. }
  635. /**
  636. * Overriden.
  637. */
  638. public function rollback($savepoint_name = 'drupal_transaction') {
  639. if (!$this->supportsTransactions()) {
  640. return;
  641. }
  642. if (!$this->inTransaction()) {
  643. throw new DatabaseTransactionNoActiveException();
  644. }
  645. // A previous rollback to an earlier savepoint may mean that the savepoint
  646. // in question has already been accidentally committed.
  647. if (!isset($this->transactionLayers[$savepoint_name])) {
  648. throw new DatabaseTransactionNoActiveException();
  649. }
  650. // We need to find the point we're rolling back to, all other savepoints
  651. // before are no longer needed. If we rolled back other active savepoints,
  652. // we need to throw an exception.
  653. $rolled_back_other_active_savepoints = FALSE;
  654. while ($savepoint = array_pop($this->transactionLayers)) {
  655. if ($savepoint['name'] == $savepoint_name) {
  656. // If it is the last the transaction in the stack, then it is not a
  657. // savepoint, it is the transaction itself so we will need to roll back
  658. // the transaction rather than a savepoint.
  659. if (empty($this->transactionLayers)) {
  660. break;
  661. }
  662. if ($savepoint['started'] == TRUE) {
  663. $this->query_direct('ROLLBACK TRANSACTION ' . $savepoint['name']);
  664. }
  665. $this->popCommittableTransactions();
  666. if ($rolled_back_other_active_savepoints) {
  667. throw new DatabaseTransactionOutOfOrderException();
  668. }
  669. return;
  670. }
  671. else {
  672. $rolled_back_other_active_savepoints = TRUE;
  673. }
  674. }
  675. \PDO::rollBack();
  676. // Restore original transaction isolation level
  677. if ($level = static::DefaultTransactionIsolationLevelInStatement()) {
  678. if($savepoint['settings']->Get_IsolationLevel() != DatabaseTransactionIsolationLevel::Ignore()) {
  679. if ($level != $savepoint['settings']->Get_IsolationLevel()) {
  680. $this->query_direct("SET TRANSACTION ISOLATION LEVEL {$level}");
  681. }
  682. }
  683. }
  684. if ($rolled_back_other_active_savepoints) {
  685. throw new DatabaseTransactionOutOfOrderException();
  686. }
  687. }
  688. /**
  689. * Summary of pushTransaction
  690. * @param string $name
  691. * @param DatabaseTransactionSettings $settings
  692. * @throws DatabaseTransactionNameNonUniqueException
  693. * @return void
  694. */
  695. public function pushTransaction($name, $settings = NULL) {
  696. if (!$this->supportsTransactions()) {
  697. return;
  698. }
  699. if (isset($this->transactionLayers[$name])) {
  700. throw new DatabaseTransactionNameNonUniqueException($name . " is already in use.");
  701. }
  702. $started = FALSE;
  703. // If we're already in a transaction.
  704. // TODO: Transaction scope Options is not working properly
  705. // for first level transactions. It assumes that - always - a first level
  706. // transaction must be started.
  707. if ($this->inTransaction()) {
  708. switch ($settings->Get_ScopeOption()) {
  709. case DatabaseTransactionScopeOption::RequiresNew():
  710. $this->query_direct('SAVE TRANSACTION ' . $name);
  711. $started = TRUE;
  712. break;
  713. case DatabaseTransactionScopeOption::Required():
  714. // We are already in a transaction, do nothing.
  715. break;
  716. case DatabaseTransactionScopeOption::Supress():
  717. // The only way to supress the ambient transaction is to use a new connection
  718. // during the scope of this transaction, a bit messy to implement.
  719. throw new Exception('DatabaseTransactionScopeOption::Supress not implemented.');
  720. }
  721. }
  722. else {
  723. if ($settings->Get_IsolationLevel() != DatabaseTransactionIsolationLevel::Ignore()) {
  724. $user_options = $this->schema()->UserOptions();
  725. $current_isolation_level = strtoupper($user_options['isolation level']);
  726. // Se what isolation level was requested.
  727. $level = $settings->Get_IsolationLevel()->__toString();
  728. if (strcasecmp($current_isolation_level, $level) !== 0) {
  729. $this->query_direct("SET TRANSACTION ISOLATION LEVEL {$level}");
  730. }
  731. }
  732. // In order to start a transaction current statement cursors
  733. // must be closed.
  734. foreach($this->statement_cache as $statement) {
  735. $statement->closeCursor();
  736. }
  737. \PDO::beginTransaction();
  738. }
  739. // Store the name and settings in the stack.
  740. $this->transactionLayers[$name] = array('settings' => $settings, 'active' => TRUE, 'name' => $name, 'started' => $started);
  741. }
  742. /**
  743. * Decreases the depth of transaction nesting.
  744. *
  745. * If we pop off the last transaction layer, then we either commit or roll
  746. * back the transaction as necessary. If no transaction is active, we return
  747. * because the transaction may have manually been rolled back.
  748. *
  749. * @param $name
  750. * The name of the savepoint
  751. *
  752. * @throws DatabaseTransactionNoActiveException
  753. * @throws DatabaseTransactionCommitFailedException
  754. *
  755. * @see DatabaseTransaction
  756. */
  757. public function popTransaction($name) {
  758. if (!$this->supportsTransactions()) {
  759. return;
  760. }
  761. // The transaction has already been committed earlier. There is nothing we
  762. // need to do. If this transaction was part of an earlier out-of-order
  763. // rollback, an exception would already have been thrown by
  764. // Database::rollback().
  765. if (!isset($this->transactionLayers[$name])) {
  766. return;
  767. }
  768. // Mark this layer as committable.
  769. $this->transactionLayers[$name]['active'] = FALSE;
  770. $this->popCommittableTransactions();
  771. }
  772. /**
  773. * Internal function: commit all the transaction layers that can commit.
  774. */
  775. protected function popCommittableTransactions() {
  776. // Commit all the committable layers.
  777. foreach (array_reverse($this->transactionLayers) as $name => $state) {
  778. // Stop once we found an active transaction.
  779. if ($state['active']) {
  780. break;
  781. }
  782. // If there are no more layers left then we should commit.
  783. unset($this->transactionLayers[$name]);
  784. if (empty($this->transactionLayers)) {
  785. try {
  786. // PDO::commit() can either return FALSE or throw an exception itself
  787. if (!PDO::commit()) {
  788. throw new DatabaseTransactionCommitFailedException();
  789. }
  790. }
  791. finally {
  792. // Restore original transaction isolation level
  793. if ($level = static::DefaultTransactionIsolationLevelInStatement()) {
  794. if($state['settings']->Get_IsolationLevel() != DatabaseTransactionIsolationLevel::Ignore()) {
  795. if ($level != $state['settings']->Get_IsolationLevel()->__toString()) {
  796. $this->query_direct("SET TRANSACTION ISOLATION LEVEL {$level}");
  797. }
  798. }
  799. }
  800. }
  801. }
  802. else {
  803. // Savepoints cannot be commited, only rolled back.
  804. }
  805. }
  806. }
  807. #endregion
  808. }
  809. class DatabaseStatement_sqlsrv extends DatabaseStatementBase implements DatabaseStatementInterface {
  810. // Flag to tell if statement should be run insecure.
  811. private $insecure = FALSE;
  812. // Tells the statement to set insecure parameters
  813. // such as SQLSRV_ATTR_DIRECT_QUERY and ATTR_EMULATE_PREPARES.
  814. public function RequireInsecure() {
  815. $this->insecure = TRUE;
  816. }
  817. public function execute($args = array(), $options = array()) {
  818. if (isset($options['fetch'])) {
  819. if (is_string($options['fetch'])) {
  820. // Default to an object. Note: db fields will be added to the object
  821. // before the constructor is run. If you need to assign fields after
  822. // the constructor is run, see http://drupal.org/node/315092.
  823. $this->setFetchMode(PDO::FETCH_CLASS, $options['fetch']);
  824. }
  825. else {
  826. $this->setFetchMode($options['fetch']);
  827. }
  828. }
  829. $logger = $this->dbh->getLogger();
  830. if (!empty($logger)) {
  831. $query_start = microtime(TRUE);
  832. }
  833. // If parameteres have already been binded
  834. // to the statement and we pass an empty array here
  835. // we will get a PDO Exception.
  836. if (empty($args)) {
  837. $args = NULL;
  838. }
  839. // Execute the query. Bypass parent override
  840. // and directly call PDOStatement implementation.
  841. $return = PDOStatement::execute($args);
  842. if (!$return) {
  843. $this->throwPDOException($statement);
  844. }
  845. // Fetch all the data from the reply, in order to release any lock
  846. // as soon as possible.
  847. $this->rowCount = $this->rowCount();
  848. // Bind column types properly.
  849. $null = array();
  850. $this->columnNames = array();
  851. for ($i = 0; $i < $this->columnCount(); $i++) {
  852. $meta = $this->getColumnMeta($i);
  853. $this->columnNames[]= $meta['name'];
  854. $sqlsrv_type = $meta['sqlsrv:decl_type'];
  855. $parts = explode(' ', $sqlsrv_type);
  856. $type = reset($parts);
  857. switch($type) {
  858. case 'varbinary':
  859. $null[$i] = NULL;
  860. $this->bindColumn($i + 1, $null[$i], PDO::PARAM_LOB, 0, PDO::SQLSRV_ENCODING_BINARY);
  861. break;
  862. case 'int':
  863. case 'bit':
  864. case 'smallint':
  865. case 'tinyint':
  866. $null[$i] = NULL;
  867. $this->bindColumn($i + 1, $null[$i], PDO::PARAM_INT);
  868. break;
  869. }
  870. }
  871. if (!empty($logger)) {
  872. $query_end = microtime(TRUE);
  873. $logger->log($this, $args, $query_end - $query_start);
  874. }
  875. // Remove technical columns from the final result set.
  876. $droppable_columns = array_flip(isset($options['sqlsrv_drop_columns']) ? $options['sqlsrv_drop_columns'] : array());
  877. $dropped_columns = array();
  878. foreach ($this->columnNames as $k => $column) {
  879. if (substr($column, 0, 2) == '__' || isset($droppable_columns[$column])) {
  880. $dropped_columns[] = $column;
  881. unset($this->columnNames[$k]);
  882. }
  883. }
  884. return $return;
  885. }
  886. /**
  887. * Throw a PDO Exception based on the last PDO error.
  888. *
  889. * @status: Unfinished.
  890. */
  891. protected function throwPDOException(&$statement = NULL) {
  892. // This is what a SQL Server PDO "no error" looks like.
  893. $null_error = array(0 => '00000', 1 => NULL, 2 => NULL);
  894. // The implementation in Drupal's Core StatementPrefetch Class
  895. // takes for granted that the error information is in the PDOConnection
  896. // but it is regularly held in the PDOStatement.
  897. $error_info_connection = $this->dbh->errorInfo();
  898. $error_info_statement = !empty($statement) ? $statement->errorInfo() : $null_error;
  899. // TODO: Concatenate error information when both connection
  900. // and statement error info are valid.
  901. // We rebuild a message formatted in the same way as PDO.
  902. $error_info = ($error_info_connection === $null_error) ? $error_info_statement : $error_info_connection;
  903. $exception = new PDOException("SQLSTATE[" . $error_info[0] . "]: General error " . $error_info[1] . ": " . $error_info[2]);
  904. $exception->errorInfo = $error_info;
  905. unset($statement);
  906. throw $exception;
  907. }
  908. /**
  909. * Experimental, do not iterate if not needed.
  910. *
  911. * @param mixed $key_index
  912. * @param mixed $value_index
  913. * @return array|DatabaseStatement_sqlsrv
  914. */
  915. public function fetchAllKeyed($key_index = 0, $value_index = 1) {
  916. // If we are asked for the default behaviour, rely
  917. // on the PDO as being faster.
  918. if ($key_index == 0 && $value_index == 1 && $this->columnCount() == 2) {
  919. $this->setFetchMode(PDO::FETCH_KEY_PAIR);
  920. return $this->fetchAll();
  921. }
  922. // We need to do this manually.
  923. $return = array();
  924. $this->setFetchMode(PDO::FETCH_NUM);
  925. foreach ($this as $record) {
  926. $return[$record[$key_index]] = $record[$value_index];
  927. }
  928. return $return;
  929. }
  930. }
  931. /**
  932. * @} End of "ingroup database".
  933. */