buildDataSourceName(), null, null); $pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); $pdo->setAttribute(\PDO::ATTR_EMULATE_PREPARES, false); $pdo->setAttribute(\PDO::ATTR_DEFAULT_FETCH_MODE, \PDO::FETCH_ASSOC); $pdo->onClose = function(PdoObject $obj) { static::registerPragma($obj, $this->pragmaClose); }; $this->exportFunctions($pdo); $this->exportCollations($pdo); $this->registerPragma($pdo, $this->pragmaBegin); } catch(\PDOException $ex){ throw $ex; } return $pdo; } public function buildDataSourceName() : string { $parts[] = $this->path; return static::DSN_PREFIX . ":" . implode(';', $parts); } public function setup(array $configuration) : void { $this->path = $configuration['path'] ?? ""; $this->pragmaBegin = array_filter($configuration['pragma_begin'] ?? []); $this->pragmaClose = array_filter($configuration['pragma_close'] ?? []); } # https://sqlite.org/lang_keywords.html public static function escapeIdentifier(string $segment, int $type) : string { switch($type) { default: case static::IDENTIFIER_DATABASE: case static::IDENTIFIER_TABLE: case static::IDENTIFIER_FIELD: return '"' . trim(str_replace('"', '""', $segment), '"') . '"'; case static::IDENTIFIER_VALUE: return "'$segment'"; } } public function defaultEngine(): ? string { return null; } public function databaseName() : string { $base = basename($this->path); return substr($base, 0, strrpos($base, '.') ?: strlen($base)); } public function schemaTable(ConnectionAdapter $adapter, string $databaseName, string $tableName) : null|object { return Entity\Sqlite\Table::repository(Repository::DEFAULT_ALIAS, $adapter) ->select(\Ulmus\Common\Sql::raw('this.*')) ->loadOneFromField(Entity\Sqlite\Table::field('tableName'), $tableName); } public function mapFieldType(FieldDefinition $field, bool $typeOnly = false) : string { $type = $field->type; $length = $field->length; if ( is_a($type, Entity\Field\Date::class, true) || is_a($type, Entity\Field\Time::class, true) || is_a($type, \DateTime::class, true) ) { $type = "TEXT"; $length = strlen((string) $type); } else { switch($type) { case "bool": $check = sprintf("CHECK (%s IN (0, 1))", $field->getColumnName()); case "bigint": case "int": $type = "INTEGER"; break; case "array": $type = "JSON"; $length = null; break; case "string": $type = "TEXT"; $length = null; break; case "float": $type = "REAL"; break; default: $type = "BLOB"; break; } } if (in_array($type, [ 'JSON', 'TEXT', 'BLOB', 'GEOMETRY' ])) { unset($field->default); } return $typeOnly ? $type : $type . ( $length ? "($length" . ")" : "" ); } public function tableSyntax() : array { return [ 'ai' => "AUTOINCREMENT", 'pk' => "PRIMARY KEY", 'unsigned' => "", ]; } public function repositoryClass() : string { return Repository\SqliteRepository::class; } public function queryBuilderClass() : string { return QueryBuilder\Sql\SqliteQueryBuilder::class; } public function exportFunctions(PdoObject $pdo) : void { $pdo->sqliteCreateFunction('if', fn($comparison, $yes, $no) => $comparison ? $yes : $no, 3); $pdo->sqliteCreateFunction('length', fn($string) => strlen($string), 1); $pdo->sqliteCreateFunction('lcase', fn($string) => strtolower($string), 1); $pdo->sqliteCreateFunction('ucase', fn($string) => strtoupper($string), 1); $pdo->sqliteCreateFunction('md5', fn($string) => md5($string), 1); $pdo->sqliteCreateFunction('sha1', fn($string) => sha1($string), 1); $pdo->sqliteCreateFunction('sha256', fn($string) => hash('sha256', $string), 1); $pdo->sqliteCreateFunction('sha512', fn($string) => hash('sha512', $string), 1); $pdo->sqliteCreateFunction('whirlpool', fn($string) => hash('whirlpool', $string), 1); $pdo->sqliteCreateFunction('murmur3a', fn($string) => hash('murmur3a', $string), 1); $pdo->sqliteCreateFunction('murmur3c', fn($string) => hash('murmur3c', $string), 1); $pdo->sqliteCreateFunction('murmur3f', fn($string) => hash('murmur3f', $string), 1); $pdo->sqliteCreateFunction('left', fn($string, $length) => substr($string, 0, $length), 2); $pdo->sqliteCreateFunction('right', fn($string, $length) => substr($string, -$length), 2); $pdo->sqliteCreateFunction('strcmp', fn($s1, $s2) => strcmp($s1, $s2), 2); $pdo->sqliteCreateFunction('lpad', fn($string, $length, $pad) => str_pad($string, $length, $pad, STR_PAD_LEFT), 3); $pdo->sqliteCreateFunction('rpad', fn($string, $length, $pad) => str_pad($string, $length, $pad, STR_PAD_RIGHT), 3); $pdo->sqliteCreateFunction('concat', fn(...$args) => implode('', $args), -1); $pdo->sqliteCreateFunction('concat_ws', fn($separator, ...$args) => implode($separator, $args), -1); $pdo->sqliteCreateFunction('find_in_set', function($string, $string_list) { if ( $string === null || $string_list === null ) { return null; } if ($string_list === "") { return 0; } return (int) in_array($string, explode(',', $string_list)); }, 2); $pdo->sqliteCreateFunction('day', fn($date) => ( new \DateTime($date) )->format('j'), 1); $pdo->sqliteCreateFunction('month', fn($date) => ( new \DateTime($date) )->format('n'), 1); $pdo->sqliteCreateFunction('year', fn($date) => ( new \DateTime($date) )->format('Y'), 1); } public function exportCollations(PdoObject $pdo) : void { if ( class_exists('Locale') ) { # @TODO debug this, case-sensitivity not working properly !! $collator = new Collator(\Locale::getDefault()); $collator->setStrength(Collator::TERTIARY); $collator->setAttribute(Collator::NUMERIC_COLLATION, Collator::ON); $pdo->sqliteCreateCollation('locale_cmp', fn($e1, $e2) => $collator->compare($e1, $e2)); $pdo->sqliteCreateCollation('locale_cmp_desc', fn($e1, $e2) => $collator->compare($e2, $e1)); $collatorCi = new Collator(\Locale::getDefault()); $collatorCi->setStrength(Collator::SECONDARY); $collatorCi->setAttribute(Collator::NUMERIC_COLLATION, Collator::ON); $pdo->sqliteCreateCollation('locale_cmp_ci', fn($e1, $e2) => $collatorCi->compare($e1, $e2)); $pdo->sqliteCreateCollation('locale_cmp_ci_desc', fn($e1, $e2) => $collatorCi->compare($e2, $e1)); } else { $comp = fn(string $e1, string $e2) => \iconv('UTF-8', 'ASCII//TRANSLIT', $e1) <=> \iconv('UTF-8', 'ASCII//TRANSLIT', $e2); $pdo->sqliteCreateCollation('locale_cmp', fn($e1, $e2) => $comp); $pdo->sqliteCreateCollation('locale_cmp_desc', fn($e2, $e1) => $comp); $compCi = fn(string $e1, string $e2) => strtoupper(\iconv('UTF-8', 'ASCII//TRANSLIT', $e1)) <=> strtoupper(\iconv('UTF-8', 'ASCII//TRANSLIT', $e2)); $pdo->sqliteCreateCollation('locale_cmp_ci', fn($e1, $e2) => $compCi); $pdo->sqliteCreateCollation('locale_cmp_ci_desc', fn($e2, $e1) => $compCi); } } public static function registerPragma(PdoObject $pdo, array $pragmaList) : void { $builder = new QueryBuilder\Sql\SqliteQueryBuilder(); foreach($pragmaList as $pragma) { list($key, $value) = explode('=', $pragma) + [ null, null ]; $sql = $builder->pragma($key, $value)->render(); $query = $pdo->query($sql); if ( ! $query->execute() ) { throw new \InvalidArgumentException(sprintf("Pragma query could not be executed : %s", $sql)); } $builder->reset(); } } public function generateAlterColumn(FieldDefinition $definition, array $field) : string|\Stringable { return implode(" ", [ strtoupper($field['action']), $this->escapeIdentifier($definition->getSqlName(), static::IDENTIFIER_FIELD), $definition->getSqlType(), $definition->getSqlParams(), ]); } public function splitAlterQuery() : bool { return true; } }