Connection.php 17 KB

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