cms/drupal/includes/database/sqlite/database.inc
changeset 541 e756a8c72c3d
equal deleted inserted replaced
540:07239de796bb 541:e756a8c72c3d
       
     1 <?php
       
     2 
       
     3 /**
       
     4  * @file
       
     5  * Database interface code for SQLite embedded database engine.
       
     6  */
       
     7 
       
     8 /**
       
     9  * @addtogroup database
       
    10  * @{
       
    11  */
       
    12 
       
    13 include_once DRUPAL_ROOT . '/includes/database/prefetch.inc';
       
    14 
       
    15 /**
       
    16  * Specific SQLite implementation of DatabaseConnection.
       
    17  */
       
    18 class DatabaseConnection_sqlite extends DatabaseConnection {
       
    19 
       
    20   /**
       
    21    * Whether this database connection supports savepoints.
       
    22    *
       
    23    * Version of sqlite lower then 3.6.8 can't use savepoints.
       
    24    * See http://www.sqlite.org/releaselog/3_6_8.html
       
    25    *
       
    26    * @var boolean
       
    27    */
       
    28   protected $savepointSupport = FALSE;
       
    29 
       
    30   /**
       
    31    * Whether or not the active transaction (if any) will be rolled back.
       
    32    *
       
    33    * @var boolean
       
    34    */
       
    35   protected $willRollback;
       
    36 
       
    37   /**
       
    38    * All databases attached to the current database. This is used to allow
       
    39    * prefixes to be safely handled without locking the table
       
    40    *
       
    41    * @var array
       
    42    */
       
    43   protected $attachedDatabases = array();
       
    44 
       
    45   /**
       
    46    * Whether or not a table has been dropped this request: the destructor will
       
    47    * only try to get rid of unnecessary databases if there is potential of them
       
    48    * being empty.
       
    49    *
       
    50    * This variable is set to public because DatabaseSchema_sqlite needs to
       
    51    * access it. However, it should not be manually set.
       
    52    *
       
    53    * @var boolean
       
    54    */
       
    55   var $tableDropped = FALSE;
       
    56 
       
    57   public function __construct(array $connection_options = array()) {
       
    58     // We don't need a specific PDOStatement class here, we simulate it below.
       
    59     $this->statementClass = NULL;
       
    60 
       
    61     // This driver defaults to transaction support, except if explicitly passed FALSE.
       
    62     $this->transactionSupport = $this->transactionalDDLSupport = !isset($connection_options['transactions']) || $connection_options['transactions'] !== FALSE;
       
    63 
       
    64     $this->connectionOptions = $connection_options;
       
    65 
       
    66     // Allow PDO options to be overridden.
       
    67     $connection_options += array(
       
    68       'pdo' => array(),
       
    69     );
       
    70     $connection_options['pdo'] += array(
       
    71       // Convert numeric values to strings when fetching.
       
    72       PDO::ATTR_STRINGIFY_FETCHES => TRUE,
       
    73     );
       
    74     parent::__construct('sqlite:' . $connection_options['database'], '', '', $connection_options['pdo']);
       
    75 
       
    76     // Attach one database for each registered prefix.
       
    77     $prefixes = $this->prefixes;
       
    78     foreach ($prefixes as $table => &$prefix) {
       
    79       // Empty prefix means query the main database -- no need to attach anything.
       
    80       if (!empty($prefix)) {
       
    81         // Only attach the database once.
       
    82         if (!isset($this->attachedDatabases[$prefix])) {
       
    83           $this->attachedDatabases[$prefix] = $prefix;
       
    84           $this->query('ATTACH DATABASE :database AS :prefix', array(':database' => $connection_options['database'] . '-' . $prefix, ':prefix' => $prefix));
       
    85         }
       
    86 
       
    87         // Add a ., so queries become prefix.table, which is proper syntax for
       
    88         // querying an attached database.
       
    89         $prefix .= '.';
       
    90       }
       
    91     }
       
    92     // Regenerate the prefixes replacement table.
       
    93     $this->setPrefix($prefixes);
       
    94 
       
    95     // Detect support for SAVEPOINT.
       
    96     $version = $this->query('SELECT sqlite_version()')->fetchField();
       
    97     $this->savepointSupport = (version_compare($version, '3.6.8') >= 0);
       
    98 
       
    99     // Create functions needed by SQLite.
       
   100     $this->sqliteCreateFunction('if', array($this, 'sqlFunctionIf'));
       
   101     $this->sqliteCreateFunction('greatest', array($this, 'sqlFunctionGreatest'));
       
   102     $this->sqliteCreateFunction('pow', 'pow', 2);
       
   103     $this->sqliteCreateFunction('length', 'strlen', 1);
       
   104     $this->sqliteCreateFunction('md5', 'md5', 1);
       
   105     $this->sqliteCreateFunction('concat', array($this, 'sqlFunctionConcat'));
       
   106     $this->sqliteCreateFunction('substring', array($this, 'sqlFunctionSubstring'), 3);
       
   107     $this->sqliteCreateFunction('substring_index', array($this, 'sqlFunctionSubstringIndex'), 3);
       
   108     $this->sqliteCreateFunction('rand', array($this, 'sqlFunctionRand'));
       
   109 
       
   110     // Execute sqlite init_commands.
       
   111     if (isset($connection_options['init_commands'])) {
       
   112       $this->exec(implode('; ', $connection_options['init_commands']));
       
   113     }
       
   114   }
       
   115 
       
   116   /**
       
   117    * Destructor for the SQLite connection.
       
   118    *
       
   119    * We prune empty databases on destruct, but only if tables have been
       
   120    * dropped. This is especially needed when running the test suite, which
       
   121    * creates and destroy databases several times in a row.
       
   122    */
       
   123   public function __destruct() {
       
   124     if ($this->tableDropped && !empty($this->attachedDatabases)) {
       
   125       foreach ($this->attachedDatabases as $prefix) {
       
   126         // Check if the database is now empty, ignore the internal SQLite tables.
       
   127         try {
       
   128           $count = $this->query('SELECT COUNT(*) FROM ' . $prefix . '.sqlite_master WHERE type = :type AND name NOT LIKE :pattern', array(':type' => 'table', ':pattern' => 'sqlite_%'))->fetchField();
       
   129 
       
   130           // We can prune the database file if it doesn't have any tables.
       
   131           if ($count == 0) {
       
   132             // Detach the database.
       
   133             $this->query('DETACH DATABASE :schema', array(':schema' => $prefix));
       
   134             // Destroy the database file.
       
   135             unlink($this->connectionOptions['database'] . '-' . $prefix);
       
   136           }
       
   137         }
       
   138         catch (Exception $e) {
       
   139           // Ignore the exception and continue. There is nothing we can do here
       
   140           // to report the error or fail safe.
       
   141         }
       
   142       }
       
   143     }
       
   144   }
       
   145 
       
   146   /**
       
   147    * SQLite compatibility implementation for the IF() SQL function.
       
   148    */
       
   149   public function sqlFunctionIf($condition, $expr1, $expr2 = NULL) {
       
   150     return $condition ? $expr1 : $expr2;
       
   151   }
       
   152 
       
   153   /**
       
   154    * SQLite compatibility implementation for the GREATEST() SQL function.
       
   155    */
       
   156   public function sqlFunctionGreatest() {
       
   157     $args = func_get_args();
       
   158     foreach ($args as $k => $v) {
       
   159       if (!isset($v)) {
       
   160         unset($args);
       
   161       }
       
   162     }
       
   163     if (count($args)) {
       
   164       return max($args);
       
   165     }
       
   166     else {
       
   167       return NULL;
       
   168     }
       
   169   }
       
   170 
       
   171   /**
       
   172    * SQLite compatibility implementation for the CONCAT() SQL function.
       
   173    */
       
   174   public function sqlFunctionConcat() {
       
   175     $args = func_get_args();
       
   176     return implode('', $args);
       
   177   }
       
   178 
       
   179   /**
       
   180    * SQLite compatibility implementation for the SUBSTRING() SQL function.
       
   181    */
       
   182   public function sqlFunctionSubstring($string, $from, $length) {
       
   183     return substr($string, $from - 1, $length);
       
   184   }
       
   185 
       
   186   /**
       
   187    * SQLite compatibility implementation for the SUBSTRING_INDEX() SQL function.
       
   188    */
       
   189   public function sqlFunctionSubstringIndex($string, $delimiter, $count) {
       
   190     // If string is empty, simply return an empty string.
       
   191     if (empty($string)) {
       
   192       return '';
       
   193     }
       
   194     $end = 0;
       
   195     for ($i = 0; $i < $count; $i++) {
       
   196       $end = strpos($string, $delimiter, $end + 1);
       
   197       if ($end === FALSE) {
       
   198         $end = strlen($string);
       
   199       }
       
   200     }
       
   201     return substr($string, 0, $end);
       
   202   }
       
   203 
       
   204   /**
       
   205    * SQLite compatibility implementation for the RAND() SQL function.
       
   206    */
       
   207   public function sqlFunctionRand($seed = NULL) {
       
   208     if (isset($seed)) {
       
   209       mt_srand($seed);
       
   210     }
       
   211     return mt_rand() / mt_getrandmax();
       
   212   }
       
   213 
       
   214   /**
       
   215    * SQLite-specific implementation of DatabaseConnection::prepare().
       
   216    *
       
   217    * We don't use prepared statements at all at this stage. We just create
       
   218    * a DatabaseStatement_sqlite object, that will create a PDOStatement
       
   219    * using the semi-private PDOPrepare() method below.
       
   220    */
       
   221   public function prepare($query, $options = array()) {
       
   222     return new DatabaseStatement_sqlite($this, $query, $options);
       
   223   }
       
   224 
       
   225   /**
       
   226    * NEVER CALL THIS FUNCTION: YOU MIGHT DEADLOCK YOUR PHP PROCESS.
       
   227    *
       
   228    * This is a wrapper around the parent PDO::prepare method. However, as
       
   229    * the PDO SQLite driver only closes SELECT statements when the PDOStatement
       
   230    * destructor is called and SQLite does not allow data change (INSERT,
       
   231    * UPDATE etc) on a table which has open SELECT statements, you should never
       
   232    * call this function and keep a PDOStatement object alive as that can lead
       
   233    * to a deadlock. This really, really should be private, but as
       
   234    * DatabaseStatement_sqlite needs to call it, we have no other choice but to
       
   235    * expose this function to the world.
       
   236    */
       
   237   public function PDOPrepare($query, array $options = array()) {
       
   238     return parent::prepare($query, $options);
       
   239   }
       
   240 
       
   241   public function queryRange($query, $from, $count, array $args = array(), array $options = array()) {
       
   242     return $this->query($query . ' LIMIT ' . (int) $from . ', ' . (int) $count, $args, $options);
       
   243   }
       
   244 
       
   245   public function queryTemporary($query, array $args = array(), array $options = array()) {
       
   246     // Generate a new temporary table name and protect it from prefixing.
       
   247     // SQLite requires that temporary tables to be non-qualified.
       
   248     $tablename = $this->generateTemporaryTableName();
       
   249     $prefixes = $this->prefixes;
       
   250     $prefixes[$tablename] = '';
       
   251     $this->setPrefix($prefixes);
       
   252 
       
   253     $this->query('CREATE TEMPORARY TABLE ' . $tablename . ' AS ' . $query, $args, $options);
       
   254     return $tablename;
       
   255   }
       
   256 
       
   257   public function driver() {
       
   258     return 'sqlite';
       
   259   }
       
   260 
       
   261   public function databaseType() {
       
   262     return 'sqlite';
       
   263   }
       
   264 
       
   265   public function mapConditionOperator($operator) {
       
   266     // We don't want to override any of the defaults.
       
   267     static $specials = array(
       
   268       'LIKE' => array('postfix' => " ESCAPE '\\'"),
       
   269       'NOT LIKE' => array('postfix' => " ESCAPE '\\'"),
       
   270     );
       
   271     return isset($specials[$operator]) ? $specials[$operator] : NULL;
       
   272   }
       
   273 
       
   274   public function prepareQuery($query) {
       
   275     return $this->prepare($this->prefixTables($query));
       
   276   }
       
   277 
       
   278   public function nextId($existing_id = 0) {
       
   279     $transaction = $this->startTransaction();
       
   280     // We can safely use literal queries here instead of the slower query
       
   281     // builder because if a given database breaks here then it can simply
       
   282     // override nextId. However, this is unlikely as we deal with short strings
       
   283     // and integers and no known databases require special handling for those
       
   284     // simple cases. If another transaction wants to write the same row, it will
       
   285     // wait until this transaction commits.
       
   286     $stmt = $this->query('UPDATE {sequences} SET value = GREATEST(value, :existing_id) + 1', array(
       
   287       ':existing_id' => $existing_id,
       
   288     ));
       
   289     if (!$stmt->rowCount()) {
       
   290       $this->query('INSERT INTO {sequences} (value) VALUES (:existing_id + 1)', array(
       
   291         ':existing_id' => $existing_id,
       
   292       ));
       
   293     }
       
   294     // The transaction gets committed when the transaction object gets destroyed
       
   295     // because it gets out of scope.
       
   296     return $this->query('SELECT value FROM {sequences}')->fetchField();
       
   297   }
       
   298 
       
   299   public function rollback($savepoint_name = 'drupal_transaction') {
       
   300     if ($this->savepointSupport) {
       
   301       return parent::rollBack($savepoint_name);
       
   302     }
       
   303 
       
   304     if (!$this->inTransaction()) {
       
   305       throw new DatabaseTransactionNoActiveException();
       
   306     }
       
   307     // A previous rollback to an earlier savepoint may mean that the savepoint
       
   308     // in question has already been rolled back.
       
   309     if (!in_array($savepoint_name, $this->transactionLayers)) {
       
   310       return;
       
   311     }
       
   312 
       
   313     // We need to find the point we're rolling back to, all other savepoints
       
   314     // before are no longer needed.
       
   315     while ($savepoint = array_pop($this->transactionLayers)) {
       
   316       if ($savepoint == $savepoint_name) {
       
   317         // Mark whole stack of transactions as needed roll back.
       
   318         $this->willRollback = TRUE;
       
   319         // If it is the last the transaction in the stack, then it is not a
       
   320         // savepoint, it is the transaction itself so we will need to roll back
       
   321         // the transaction rather than a savepoint.
       
   322         if (empty($this->transactionLayers)) {
       
   323           break;
       
   324         }
       
   325         return;
       
   326       }
       
   327     }
       
   328     if ($this->supportsTransactions()) {
       
   329       PDO::rollBack();
       
   330     }
       
   331   }
       
   332 
       
   333   public function pushTransaction($name) {
       
   334     if ($this->savepointSupport) {
       
   335       return parent::pushTransaction($name);
       
   336     }
       
   337     if (!$this->supportsTransactions()) {
       
   338       return;
       
   339     }
       
   340     if (isset($this->transactionLayers[$name])) {
       
   341       throw new DatabaseTransactionNameNonUniqueException($name . " is already in use.");
       
   342     }
       
   343     if (!$this->inTransaction()) {
       
   344       PDO::beginTransaction();
       
   345     }
       
   346     $this->transactionLayers[$name] = $name;
       
   347   }
       
   348 
       
   349   public function popTransaction($name) {
       
   350     if ($this->savepointSupport) {
       
   351       return parent::popTransaction($name);
       
   352     }
       
   353     if (!$this->supportsTransactions()) {
       
   354       return;
       
   355     }
       
   356     if (!$this->inTransaction()) {
       
   357       throw new DatabaseTransactionNoActiveException();
       
   358     }
       
   359 
       
   360     // Commit everything since SAVEPOINT $name.
       
   361     while($savepoint = array_pop($this->transactionLayers)) {
       
   362       if ($savepoint != $name) continue;
       
   363 
       
   364       // If there are no more layers left then we should commit or rollback.
       
   365       if (empty($this->transactionLayers)) {
       
   366         // If there was any rollback() we should roll back whole transaction.
       
   367         if ($this->willRollback) {
       
   368           $this->willRollback = FALSE;
       
   369           PDO::rollBack();
       
   370         }
       
   371         elseif (!PDO::commit()) {
       
   372           throw new DatabaseTransactionCommitFailedException();
       
   373         }
       
   374       }
       
   375       else {
       
   376         break;
       
   377       }
       
   378     }
       
   379   }
       
   380 
       
   381   public function utf8mb4IsActive() {
       
   382     return TRUE;
       
   383   }
       
   384 
       
   385   public function utf8mb4IsSupported() {
       
   386     return TRUE;
       
   387   }
       
   388 
       
   389 }
       
   390 
       
   391 /**
       
   392  * Specific SQLite implementation of DatabaseConnection.
       
   393  *
       
   394  * See DatabaseConnection_sqlite::PDOPrepare() for reasons why we must prefetch
       
   395  * the data instead of using PDOStatement.
       
   396  *
       
   397  * @see DatabaseConnection_sqlite::PDOPrepare()
       
   398  */
       
   399 class DatabaseStatement_sqlite extends DatabaseStatementPrefetch implements Iterator, DatabaseStatementInterface {
       
   400 
       
   401   /**
       
   402    * SQLite specific implementation of getStatement().
       
   403    *
       
   404    * The PDO SQLite layer doesn't replace numeric placeholders in queries
       
   405    * correctly, and this makes numeric expressions (such as COUNT(*) >= :count)
       
   406    * fail. We replace numeric placeholders in the query ourselves to work
       
   407    * around this bug.
       
   408    *
       
   409    * See http://bugs.php.net/bug.php?id=45259 for more details.
       
   410    */
       
   411   protected function getStatement($query, &$args = array()) {
       
   412     if (count($args)) {
       
   413       // Check if $args is a simple numeric array.
       
   414       if (range(0, count($args) - 1) === array_keys($args)) {
       
   415         // In that case, we have unnamed placeholders.
       
   416         $count = 0;
       
   417         $new_args = array();
       
   418         foreach ($args as $value) {
       
   419           if (is_float($value) || is_int($value)) {
       
   420             if (is_float($value)) {
       
   421               // Force the conversion to float so as not to loose precision
       
   422               // in the automatic cast.
       
   423               $value = sprintf('%F', $value);
       
   424             }
       
   425             $query = substr_replace($query, $value, strpos($query, '?'), 1);
       
   426           }
       
   427           else {
       
   428             $placeholder = ':db_statement_placeholder_' . $count++;
       
   429             $query = substr_replace($query, $placeholder, strpos($query, '?'), 1);
       
   430             $new_args[$placeholder] = $value;
       
   431           }
       
   432         }
       
   433         $args = $new_args;
       
   434       }
       
   435       else {
       
   436         // Else, this is using named placeholders.
       
   437         foreach ($args as $placeholder => $value) {
       
   438           if (is_float($value) || is_int($value)) {
       
   439             if (is_float($value)) {
       
   440               // Force the conversion to float so as not to loose precision
       
   441               // in the automatic cast.
       
   442               $value = sprintf('%F', $value);
       
   443             }
       
   444 
       
   445             // We will remove this placeholder from the query as PDO throws an
       
   446             // exception if the number of placeholders in the query and the
       
   447             // arguments does not match.
       
   448             unset($args[$placeholder]);
       
   449             // PDO allows placeholders to not be prefixed by a colon. See
       
   450             // http://marc.info/?l=php-internals&m=111234321827149&w=2 for
       
   451             // more.
       
   452             if ($placeholder[0] != ':') {
       
   453               $placeholder = ":$placeholder";
       
   454             }
       
   455             // When replacing the placeholders, make sure we search for the
       
   456             // exact placeholder. For example, if searching for
       
   457             // ':db_placeholder_1', do not replace ':db_placeholder_11'.
       
   458             $query = preg_replace('/' . preg_quote($placeholder) . '\b/', $value, $query);
       
   459           }
       
   460         }
       
   461       }
       
   462     }
       
   463 
       
   464     return $this->dbh->PDOPrepare($query);
       
   465   }
       
   466 
       
   467   public function execute($args = array(), $options = array()) {
       
   468     try {
       
   469       $return = parent::execute($args, $options);
       
   470     }
       
   471     catch (PDOException $e) {
       
   472       if (!empty($e->errorInfo[1]) && $e->errorInfo[1] === 17) {
       
   473         // The schema has changed. SQLite specifies that we must resend the query.
       
   474         $return = parent::execute($args, $options);
       
   475       }
       
   476       else {
       
   477         // Rethrow the exception.
       
   478         throw $e;
       
   479       }
       
   480     }
       
   481 
       
   482     // In some weird cases, SQLite will prefix some column names by the name
       
   483     // of the table. We post-process the data, by renaming the column names
       
   484     // using the same convention as MySQL and PostgreSQL.
       
   485     $rename_columns = array();
       
   486     foreach ($this->columnNames as $k => $column) {
       
   487       // In some SQLite versions, SELECT DISTINCT(field) will return "(field)"
       
   488       // instead of "field".
       
   489       if (preg_match("/^\((.*)\)$/", $column, $matches)) {
       
   490         $rename_columns[$column] = $matches[1];
       
   491         $this->columnNames[$k] = $matches[1];
       
   492         $column = $matches[1];
       
   493       }
       
   494 
       
   495       // Remove "table." prefixes.
       
   496       if (preg_match("/^.*\.(.*)$/", $column, $matches)) {
       
   497         $rename_columns[$column] = $matches[1];
       
   498         $this->columnNames[$k] = $matches[1];
       
   499       }
       
   500     }
       
   501     if ($rename_columns) {
       
   502       // DatabaseStatementPrefetch already extracted the first row,
       
   503       // put it back into the result set.
       
   504       if (isset($this->currentRow)) {
       
   505         $this->data[0] = &$this->currentRow;
       
   506       }
       
   507 
       
   508       // Then rename all the columns across the result set.
       
   509       foreach ($this->data as $k => $row) {
       
   510         foreach ($rename_columns as $old_column => $new_column) {
       
   511           $this->data[$k][$new_column] = $this->data[$k][$old_column];
       
   512           unset($this->data[$k][$old_column]);
       
   513         }
       
   514       }
       
   515 
       
   516       // Finally, extract the first row again.
       
   517       $this->currentRow = $this->data[0];
       
   518       unset($this->data[0]);
       
   519     }
       
   520 
       
   521     return $return;
       
   522   }
       
   523 }
       
   524 
       
   525 /**
       
   526  * @} End of "addtogroup database".
       
   527  */