spqr/vendor/cakephp/database/Driver/Sqlite.php

386 lines
11 KiB
PHP
Raw Normal View History

2024-11-05 12:10:06 +08:00
<?php
declare(strict_types=1);
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 3.0.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Database\Driver;
use Cake\Database\Driver;
use Cake\Database\Expression\FunctionExpression;
use Cake\Database\Expression\TupleComparison;
use Cake\Database\Query;
use Cake\Database\QueryCompiler;
use Cake\Database\Schema\SchemaDialect;
use Cake\Database\Schema\SqliteSchemaDialect;
use Cake\Database\SqliteCompiler;
use Cake\Database\Statement\PDOStatement;
use Cake\Database\Statement\SqliteStatement;
use Cake\Database\StatementInterface;
use InvalidArgumentException;
use PDO;
use RuntimeException;
use function Cake\Core\deprecationWarning;
/**
* Class Sqlite
*/
class Sqlite extends Driver
{
use SqlDialectTrait;
use TupleComparisonTranslatorTrait;
/**
* Base configuration settings for Sqlite driver
*
* - `mask` The mask used for created database
*
* @var array<string, mixed>
*/
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<string, string>
*/
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<string, string>
*/
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);
}
}