PageRenderTime 47ms CodeModel.GetById 17ms RepoModel.GetById 1ms app.codeStats 0ms

/core/lib/Drupal/Core/Database/Driver/sqlite/Connection.php

https://gitlab.com/reasonat/test8
PHP | 413 lines | 200 code | 47 blank | 166 comment | 24 complexity | 60c578d1389f25965633f39612f5e3d4 MD5 | raw file
  1. <?php
  2. namespace Drupal\Core\Database\Driver\sqlite;
  3. use Drupal\Core\Database\Database;
  4. use Drupal\Core\Database\DatabaseNotFoundException;
  5. use Drupal\Core\Database\Connection as DatabaseConnection;
  6. /**
  7. * SQLite implementation of \Drupal\Core\Database\Connection.
  8. */
  9. class Connection extends DatabaseConnection {
  10. /**
  11. * Error code for "Unable to open database file" error.
  12. */
  13. const DATABASE_NOT_FOUND = 14;
  14. /**
  15. * Whether or not the active transaction (if any) will be rolled back.
  16. *
  17. * @var bool
  18. */
  19. protected $willRollback;
  20. /**
  21. * All databases attached to the current database. This is used to allow
  22. * prefixes to be safely handled without locking the table
  23. *
  24. * @var array
  25. */
  26. protected $attachedDatabases = array();
  27. /**
  28. * Whether or not a table has been dropped this request: the destructor will
  29. * only try to get rid of unnecessary databases if there is potential of them
  30. * being empty.
  31. *
  32. * This variable is set to public because Schema needs to
  33. * access it. However, it should not be manually set.
  34. *
  35. * @var bool
  36. */
  37. public $tableDropped = FALSE;
  38. /**
  39. * Constructs a \Drupal\Core\Database\Driver\sqlite\Connection object.
  40. */
  41. public function __construct(\PDO $connection, array $connection_options) {
  42. // We don't need a specific PDOStatement class here, we simulate it in
  43. // static::prepare().
  44. $this->statementClass = NULL;
  45. parent::__construct($connection, $connection_options);
  46. // This driver defaults to transaction support, except if explicitly passed FALSE.
  47. $this->transactionSupport = $this->transactionalDDLSupport = !isset($connection_options['transactions']) || $connection_options['transactions'] !== FALSE;
  48. $this->connectionOptions = $connection_options;
  49. // Attach one database for each registered prefix.
  50. $prefixes = $this->prefixes;
  51. foreach ($prefixes as &$prefix) {
  52. // Empty prefix means query the main database -- no need to attach anything.
  53. if (!empty($prefix)) {
  54. // Only attach the database once.
  55. if (!isset($this->attachedDatabases[$prefix])) {
  56. $this->attachedDatabases[$prefix] = $prefix;
  57. if ($connection_options['database'] === ':memory:') {
  58. // In memory database use ':memory:' as database name. According to
  59. // http://www.sqlite.org/inmemorydb.html it will open a unique
  60. // database so attaching it twice is not a problem.
  61. $this->query('ATTACH DATABASE :database AS :prefix', array(':database' => $connection_options['database'], ':prefix' => $prefix));
  62. }
  63. else {
  64. $this->query('ATTACH DATABASE :database AS :prefix', array(':database' => $connection_options['database'] . '-' . $prefix, ':prefix' => $prefix));
  65. }
  66. }
  67. // Add a ., so queries become prefix.table, which is proper syntax for
  68. // querying an attached database.
  69. $prefix .= '.';
  70. }
  71. }
  72. // Regenerate the prefixes replacement table.
  73. $this->setPrefix($prefixes);
  74. }
  75. /**
  76. * {@inheritdoc}
  77. */
  78. public static function open(array &$connection_options = array()) {
  79. // Allow PDO options to be overridden.
  80. $connection_options += array(
  81. 'pdo' => array(),
  82. );
  83. $connection_options['pdo'] += array(
  84. \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
  85. // Convert numeric values to strings when fetching.
  86. \PDO::ATTR_STRINGIFY_FETCHES => TRUE,
  87. );
  88. $pdo = new \PDO('sqlite:' . $connection_options['database'], '', '', $connection_options['pdo']);
  89. // Create functions needed by SQLite.
  90. $pdo->sqliteCreateFunction('if', array(__CLASS__, 'sqlFunctionIf'));
  91. $pdo->sqliteCreateFunction('greatest', array(__CLASS__, 'sqlFunctionGreatest'));
  92. $pdo->sqliteCreateFunction('pow', 'pow', 2);
  93. $pdo->sqliteCreateFunction('exp', 'exp', 1);
  94. $pdo->sqliteCreateFunction('length', 'strlen', 1);
  95. $pdo->sqliteCreateFunction('md5', 'md5', 1);
  96. $pdo->sqliteCreateFunction('concat', array(__CLASS__, 'sqlFunctionConcat'));
  97. $pdo->sqliteCreateFunction('concat_ws', array(__CLASS__, 'sqlFunctionConcatWs'));
  98. $pdo->sqliteCreateFunction('substring', array(__CLASS__, 'sqlFunctionSubstring'), 3);
  99. $pdo->sqliteCreateFunction('substring_index', array(__CLASS__, 'sqlFunctionSubstringIndex'), 3);
  100. $pdo->sqliteCreateFunction('rand', array(__CLASS__, 'sqlFunctionRand'));
  101. $pdo->sqliteCreateFunction('regexp', array(__CLASS__, 'sqlFunctionRegexp'));
  102. // SQLite does not support the LIKE BINARY operator, so we overload the
  103. // non-standard GLOB operator for case-sensitive matching. Another option
  104. // would have been to override another non-standard operator, MATCH, but
  105. // that does not support the NOT keyword prefix.
  106. $pdo->sqliteCreateFunction('glob', array(__CLASS__, 'sqlFunctionLikeBinary'));
  107. // Create a user-space case-insensitive collation with UTF-8 support.
  108. $pdo->sqliteCreateCollation('NOCASE_UTF8', array('Drupal\Component\Utility\Unicode', 'strcasecmp'));
  109. // Execute sqlite init_commands.
  110. if (isset($connection_options['init_commands'])) {
  111. $pdo->exec(implode('; ', $connection_options['init_commands']));
  112. }
  113. return $pdo;
  114. }
  115. /**
  116. * Destructor for the SQLite connection.
  117. *
  118. * We prune empty databases on destruct, but only if tables have been
  119. * dropped. This is especially needed when running the test suite, which
  120. * creates and destroy databases several times in a row.
  121. */
  122. public function __destruct() {
  123. if ($this->tableDropped && !empty($this->attachedDatabases)) {
  124. foreach ($this->attachedDatabases as $prefix) {
  125. // Check if the database is now empty, ignore the internal SQLite tables.
  126. try {
  127. $count = $this->query('SELECT COUNT(*) FROM ' . $prefix . '.sqlite_master WHERE type = :type AND name NOT LIKE :pattern', array(':type' => 'table', ':pattern' => 'sqlite_%'))->fetchField();
  128. // We can prune the database file if it doesn't have any tables.
  129. if ($count == 0) {
  130. // Detaching the database fails at this point, but no other queries
  131. // are executed after the connection is destructed so we can simply
  132. // remove the database file.
  133. unlink($this->connectionOptions['database'] . '-' . $prefix);
  134. }
  135. }
  136. catch (\Exception $e) {
  137. // Ignore the exception and continue. There is nothing we can do here
  138. // to report the error or fail safe.
  139. }
  140. }
  141. }
  142. }
  143. /**
  144. * Gets all the attached databases.
  145. *
  146. * @return array
  147. * An array of attached database names.
  148. *
  149. * @see \Drupal\Core\Database\Driver\sqlite\Connection::__construct()
  150. */
  151. public function getAttachedDatabases() {
  152. return $this->attachedDatabases;
  153. }
  154. /**
  155. * SQLite compatibility implementation for the IF() SQL function.
  156. */
  157. public static function sqlFunctionIf($condition, $expr1, $expr2 = NULL) {
  158. return $condition ? $expr1 : $expr2;
  159. }
  160. /**
  161. * SQLite compatibility implementation for the GREATEST() SQL function.
  162. */
  163. public static function sqlFunctionGreatest() {
  164. $args = func_get_args();
  165. foreach ($args as $v) {
  166. if (!isset($v)) {
  167. unset($args);
  168. }
  169. }
  170. if (count($args)) {
  171. return max($args);
  172. }
  173. else {
  174. return NULL;
  175. }
  176. }
  177. /**
  178. * SQLite compatibility implementation for the CONCAT() SQL function.
  179. */
  180. public static function sqlFunctionConcat() {
  181. $args = func_get_args();
  182. return implode('', $args);
  183. }
  184. /**
  185. * SQLite compatibility implementation for the CONCAT_WS() SQL function.
  186. *
  187. * @see http://dev.mysql.com/doc/refman/5.6/en/string-functions.html#function_concat-ws
  188. */
  189. public static function sqlFunctionConcatWs() {
  190. $args = func_get_args();
  191. $separator = array_shift($args);
  192. // If the separator is NULL, the result is NULL.
  193. if ($separator === FALSE || is_null($separator)) {
  194. return NULL;
  195. }
  196. // Skip any NULL values after the separator argument.
  197. $args = array_filter($args, function ($value) {
  198. return !is_null($value);
  199. });
  200. return implode($separator, $args);
  201. }
  202. /**
  203. * SQLite compatibility implementation for the SUBSTRING() SQL function.
  204. */
  205. public static function sqlFunctionSubstring($string, $from, $length) {
  206. return substr($string, $from - 1, $length);
  207. }
  208. /**
  209. * SQLite compatibility implementation for the SUBSTRING_INDEX() SQL function.
  210. */
  211. public static function sqlFunctionSubstringIndex($string, $delimiter, $count) {
  212. // If string is empty, simply return an empty string.
  213. if (empty($string)) {
  214. return '';
  215. }
  216. $end = 0;
  217. for ($i = 0; $i < $count; $i++) {
  218. $end = strpos($string, $delimiter, $end + 1);
  219. if ($end === FALSE) {
  220. $end = strlen($string);
  221. }
  222. }
  223. return substr($string, 0, $end);
  224. }
  225. /**
  226. * SQLite compatibility implementation for the RAND() SQL function.
  227. */
  228. public static function sqlFunctionRand($seed = NULL) {
  229. if (isset($seed)) {
  230. mt_srand($seed);
  231. }
  232. return mt_rand() / mt_getrandmax();
  233. }
  234. /**
  235. * SQLite compatibility implementation for the REGEXP SQL operator.
  236. *
  237. * The REGEXP operator is natively known, but not implemented by default.
  238. *
  239. * @see http://www.sqlite.org/lang_expr.html#regexp
  240. */
  241. public static function sqlFunctionRegexp($pattern, $subject) {
  242. // preg_quote() cannot be used here, since $pattern may contain reserved
  243. // regular expression characters already (such as ^, $, etc). Therefore,
  244. // use a rare character as PCRE delimiter.
  245. $pattern = '#' . addcslashes($pattern, '#') . '#i';
  246. return preg_match($pattern, $subject);
  247. }
  248. /**
  249. * SQLite compatibility implementation for the LIKE BINARY SQL operator.
  250. *
  251. * SQLite supports case-sensitive LIKE operations through the
  252. * 'case_sensitive_like' PRAGMA statement, but only for ASCII characters, so
  253. * we have to provide our own implementation with UTF-8 support.
  254. *
  255. * @see https://sqlite.org/pragma.html#pragma_case_sensitive_like
  256. * @see https://sqlite.org/lang_expr.html#like
  257. */
  258. public static function sqlFunctionLikeBinary($pattern, $subject) {
  259. // Replace the SQL LIKE wildcard meta-characters with the equivalent regular
  260. // expression meta-characters and escape the delimiter that will be used for
  261. // matching.
  262. $pattern = str_replace(array('%', '_'), array('.*?', '.'), preg_quote($pattern, '/'));
  263. return preg_match('/^' . $pattern . '$/', $subject);
  264. }
  265. /**
  266. * {@inheritdoc}
  267. */
  268. public function prepare($statement, array $driver_options = array()) {
  269. return new Statement($this->connection, $this, $statement, $driver_options);
  270. }
  271. /**
  272. * {@inheritdoc}
  273. */
  274. protected function handleQueryException(\PDOException $e, $query, array $args = array(), $options = array()) {
  275. // The database schema might be changed by another process in between the
  276. // time that the statement was prepared and the time the statement was run
  277. // (e.g. usually happens when running tests). In this case, we need to
  278. // re-run the query.
  279. // @see http://www.sqlite.org/faq.html#q15
  280. // @see http://www.sqlite.org/rescode.html#schema
  281. if (!empty($e->errorInfo[1]) && $e->errorInfo[1] === 17) {
  282. return $this->query($query, $args, $options);
  283. }
  284. parent::handleQueryException($e, $query, $args, $options);
  285. }
  286. public function queryRange($query, $from, $count, array $args = array(), array $options = array()) {
  287. return $this->query($query . ' LIMIT ' . (int) $from . ', ' . (int) $count, $args, $options);
  288. }
  289. public function queryTemporary($query, array $args = array(), array $options = array()) {
  290. // Generate a new temporary table name and protect it from prefixing.
  291. // SQLite requires that temporary tables to be non-qualified.
  292. $tablename = $this->generateTemporaryTableName();
  293. $prefixes = $this->prefixes;
  294. $prefixes[$tablename] = '';
  295. $this->setPrefix($prefixes);
  296. $this->query('CREATE TEMPORARY TABLE ' . $tablename . ' AS ' . $query, $args, $options);
  297. return $tablename;
  298. }
  299. public function driver() {
  300. return 'sqlite';
  301. }
  302. public function databaseType() {
  303. return 'sqlite';
  304. }
  305. /**
  306. * Overrides \Drupal\Core\Database\Connection::createDatabase().
  307. *
  308. * @param string $database
  309. * The name of the database to create.
  310. *
  311. * @throws \Drupal\Core\Database\DatabaseNotFoundException
  312. */
  313. public function createDatabase($database) {
  314. // Verify the database is writable.
  315. $db_directory = new \SplFileInfo(dirname($database));
  316. if (!$db_directory->isDir() && !drupal_mkdir($db_directory->getPathName(), 0755, TRUE)) {
  317. throw new DatabaseNotFoundException('Unable to create database directory ' . $db_directory->getPathName());
  318. }
  319. }
  320. public function mapConditionOperator($operator) {
  321. // We don't want to override any of the defaults.
  322. static $specials = array(
  323. 'LIKE' => array('postfix' => " ESCAPE '\\'"),
  324. 'NOT LIKE' => array('postfix' => " ESCAPE '\\'"),
  325. 'LIKE BINARY' => array('postfix' => " ESCAPE '\\'", 'operator' => 'GLOB'),
  326. 'NOT LIKE BINARY' => array('postfix' => " ESCAPE '\\'", 'operator' => 'NOT GLOB'),
  327. );
  328. return isset($specials[$operator]) ? $specials[$operator] : NULL;
  329. }
  330. /**
  331. * {@inheritdoc}
  332. */
  333. public function prepareQuery($query) {
  334. return $this->prepare($this->prefixTables($query));
  335. }
  336. public function nextId($existing_id = 0) {
  337. $this->startTransaction();
  338. // We can safely use literal queries here instead of the slower query
  339. // builder because if a given database breaks here then it can simply
  340. // override nextId. However, this is unlikely as we deal with short strings
  341. // and integers and no known databases require special handling for those
  342. // simple cases. If another transaction wants to write the same row, it will
  343. // wait until this transaction commits. Also, the return value needs to be
  344. // set to RETURN_AFFECTED as if it were a real update() query otherwise it
  345. // is not possible to get the row count properly.
  346. $affected = $this->query('UPDATE {sequences} SET value = GREATEST(value, :existing_id) + 1', array(
  347. ':existing_id' => $existing_id,
  348. ), array('return' => Database::RETURN_AFFECTED));
  349. if (!$affected) {
  350. $this->query('INSERT INTO {sequences} (value) VALUES (:existing_id + 1)', array(
  351. ':existing_id' => $existing_id,
  352. ));
  353. }
  354. // The transaction gets committed when the transaction object gets destroyed
  355. // because it gets out of scope.
  356. return $this->query('SELECT value FROM {sequences}')->fetchField();
  357. }
  358. /**
  359. * {@inheritdoc}
  360. */
  361. public function getFullQualifiedTableName($table) {
  362. $prefix = $this->tablePrefix($table);
  363. // Don't include the SQLite database file name as part of the table name.
  364. return $prefix . $table;
  365. }
  366. }