*/ protected $_baseConfig = [ 'persistent' => false, 'username' => null, 'password' => null, 'database' => ':memory:', 'encoding' => 'utf8', 'mask' => 0644, 'cache' => null, 'mode' => null, 'flags' => [], 'init' => [], ]; /** * The schema dialect class for this driver * * @var \Cake\Database\Schema\SqliteSchemaDialect|null */ protected $_schemaDialect; /** * Whether the connected server supports window functions. * * @var bool|null */ protected $_supportsWindowFunctions; /** * String used to start a database identifier quoting to make it safe * * @var string */ protected $_startQuote = '"'; /** * String used to end a database identifier quoting to make it safe * * @var string */ protected $_endQuote = '"'; /** * Mapping of date parts. * * @var array */ protected $_dateParts = [ 'day' => 'd', 'hour' => 'H', 'month' => 'm', 'minute' => 'M', 'second' => 'S', 'week' => 'W', 'year' => 'Y', ]; /** * Mapping of feature to db server version for feature availability checks. * * @var array */ protected $featureVersions = [ 'cte' => '3.8.3', 'window' => '3.28.0', ]; /** * Establishes a connection to the database server * * @return bool true on success */ public function connect(): bool { if ($this->_connection) { return true; } $config = $this->_config; $config['flags'] += [ PDO::ATTR_PERSISTENT => $config['persistent'], PDO::ATTR_EMULATE_PREPARES => false, PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, ]; if (!is_string($config['database']) || $config['database'] === '') { $name = $config['name'] ?? 'unknown'; throw new InvalidArgumentException( "The `database` key for the `{$name}` SQLite connection needs to be a non-empty string." ); } $chmodFile = false; if ($config['database'] !== ':memory:' && $config['mode'] !== 'memory') { $chmodFile = !file_exists($config['database']); } $params = []; if ($config['cache']) { $params[] = 'cache=' . $config['cache']; } if ($config['mode']) { $params[] = 'mode=' . $config['mode']; } if ($params) { if (PHP_VERSION_ID < 80100) { throw new RuntimeException('SQLite URI support requires PHP 8.1.'); } $dsn = 'sqlite:file:' . $config['database'] . '?' . implode('&', $params); } else { $dsn = 'sqlite:' . $config['database']; } $this->_connect($dsn, $config); if ($chmodFile) { // phpcs:disable @chmod($config['database'], $config['mask']); // phpcs:enable } if (!empty($config['init'])) { foreach ((array)$config['init'] as $command) { $this->getConnection()->exec($command); } } return true; } /** * Returns whether php is able to use this driver for connecting to database * * @return bool true if it is valid to use this driver */ public function enabled(): bool { return in_array('sqlite', PDO::getAvailableDrivers(), true); } /** * Prepares a sql statement to be executed * * @param \Cake\Database\Query|string $query The query to prepare. * @return \Cake\Database\StatementInterface */ public function prepare($query): StatementInterface { $this->connect(); $isObject = $query instanceof Query; /** * @psalm-suppress PossiblyInvalidMethodCall * @psalm-suppress PossiblyInvalidArgument */ $statement = $this->_connection->prepare($isObject ? $query->sql() : $query); $result = new SqliteStatement(new PDOStatement($statement, $this), $this); /** @psalm-suppress PossiblyInvalidMethodCall */ if ($isObject && $query->isBufferedResultsEnabled() === false) { $result->bufferResults(false); } return $result; } /** * @inheritDoc */ public function disableForeignKeySQL(): string { return 'PRAGMA foreign_keys = OFF'; } /** * @inheritDoc */ public function enableForeignKeySQL(): string { return 'PRAGMA foreign_keys = ON'; } /** * @inheritDoc */ public function supports(string $feature): bool { switch ($feature) { case static::FEATURE_CTE: case static::FEATURE_WINDOW: return version_compare( $this->version(), $this->featureVersions[$feature], '>=' ); case static::FEATURE_TRUNCATE_WITH_CONSTRAINTS: return true; } return parent::supports($feature); } /** * @inheritDoc */ public function supportsDynamicConstraints(): bool { return false; } /** * @inheritDoc */ public function schemaDialect(): SchemaDialect { if ($this->_schemaDialect === null) { $this->_schemaDialect = new SqliteSchemaDialect($this); } return $this->_schemaDialect; } /** * @inheritDoc */ public function newCompiler(): QueryCompiler { return new SqliteCompiler(); } /** * @inheritDoc */ protected function _expressionTranslators(): array { return [ FunctionExpression::class => '_transformFunctionExpression', TupleComparison::class => '_transformTupleComparison', ]; } /** * Receives a FunctionExpression and changes it so that it conforms to this * SQL dialect. * * @param \Cake\Database\Expression\FunctionExpression $expression The function expression to convert to TSQL. * @return void */ protected function _transformFunctionExpression(FunctionExpression $expression): void { switch ($expression->getName()) { case 'CONCAT': // CONCAT function is expressed as exp1 || exp2 $expression->setName('')->setConjunction(' ||'); break; case 'DATEDIFF': $expression ->setName('ROUND') ->setConjunction('-') ->iterateParts(function ($p) { return new FunctionExpression('JULIANDAY', [$p['value']], [$p['type']]); }); break; case 'NOW': $expression->setName('DATETIME')->add(["'now'" => 'literal']); break; case 'RAND': $expression ->setName('ABS') ->add(['RANDOM() % 1' => 'literal'], [], true); break; case 'CURRENT_DATE': $expression->setName('DATE')->add(["'now'" => 'literal']); break; case 'CURRENT_TIME': $expression->setName('TIME')->add(["'now'" => 'literal']); break; case 'EXTRACT': $expression ->setName('STRFTIME') ->setConjunction(' ,') ->iterateParts(function ($p, $key) { if ($key === 0) { $value = rtrim(strtolower($p), 's'); if (isset($this->_dateParts[$value])) { $p = ['value' => '%' . $this->_dateParts[$value], 'type' => null]; } } return $p; }); break; case 'DATE_ADD': $expression ->setName('DATE') ->setConjunction(',') ->iterateParts(function ($p, $key) { if ($key === 1) { $p = ['value' => $p, 'type' => null]; } return $p; }); break; case 'DAYOFWEEK': $expression ->setName('STRFTIME') ->setConjunction(' ') ->add(["'%w', " => 'literal'], [], true) ->add([') + (1' => 'literal']); // Sqlite starts on index 0 but Sunday should be 1 break; } } /** * Returns true if the server supports common table expressions. * * @return bool * @deprecated 4.3.0 Use `supports(DriverInterface::FEATURE_CTE)` instead */ public function supportsCTEs(): bool { deprecationWarning('Feature support checks are now implemented by `supports()` with FEATURE_* constants.'); return $this->supports(static::FEATURE_CTE); } /** * Returns true if the connected server supports window functions. * * @return bool * @deprecated 4.3.0 Use `supports(DriverInterface::FEATURE_WINDOW)` instead */ public function supportsWindowFunctions(): bool { deprecationWarning('Feature support checks are now implemented by `supports()` with FEATURE_* constants.'); return $this->supports(static::FEATURE_WINDOW); } }