224 lines
		
	
	
		
			7.8 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			224 lines
		
	
	
		
			7.8 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
| <?php
 | |
| 
 | |
| namespace Ulmus\Adapter;
 | |
| 
 | |
| use Ulmus\Common\PdoObject;
 | |
| use Ulmus\ConnectionAdapter;
 | |
| 
 | |
| use Ulmus\Entity;
 | |
| use Ulmus\Migration\FieldDefinition;
 | |
| use Ulmus\{Migration\MigrateInterface, Repository, QueryBuilder};
 | |
| 
 | |
| class SQLite implements AdapterInterface, MigrateInterface, SqlAdapterInterface {
 | |
|     use SqlAdapterTrait;
 | |
| 
 | |
|     const ALLOWED_ATTRIBUTES = [
 | |
|         'default', 'primary_key', 'auto_increment'
 | |
|     ];
 | |
| 
 | |
|     const DSN_PREFIX = "sqlite";
 | |
| 
 | |
|     public function __construct(
 | |
|         public null|string $path = null,
 | |
|         public null|array $pragmaBegin = null,
 | |
|         public null|array $pragmaClose = null,
 | |
|     ) { }
 | |
| 
 | |
|     public function connect() : PdoObject
 | |
|     {
 | |
|         try {
 | |
|             $pdo = new PdoObject($this->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->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 function escapeIdentifier(string $segment, int $type) : string 
 | |
|     {
 | |
|         switch($type) {
 | |
|             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":
 | |
|                 case "string":
 | |
|                     $type = "TEXT";
 | |
|                     $length = null;
 | |
|                     break;
 | |
| 
 | |
|                 case "float":
 | |
|                     $type = "REAL";
 | |
|                     break;
 | |
| 
 | |
|                 default:
 | |
|                     $type = "BLOB";
 | |
|                     break;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         return $typeOnly ? $type : $type . ( $length ? "($length" . ( isset($precision) ? ",$precision" : "" ) . ")" : "" );
 | |
|     }
 | |
| 
 | |
|     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('d'), 1);
 | |
|         $pdo->sqliteCreateFunction('month', fn($date) => ( new \DateTime($date) )->format('m'), 1);
 | |
|         $pdo->sqliteCreateFunction('year', fn($date) => ( new \DateTime($date) )->format('Y'), 1);
 | |
|     }
 | |
| 
 | |
|     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;
 | |
|     }
 | |
| }
 |