From 3bd1031dda48c765dd154d114081c92e23218aeb Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Wed, 24 Apr 2024 23:16:55 +0400 Subject: [PATCH] ajout sqlite --- src/db/sqlite/Sqlite.php | 178 ++++++++++++++++++++++++++++++ src/db/sqlite/SqliteConfig.php | 35 ++++++ src/db/sqlite/SqliteException.php | 13 +++ src/db/sqlite/SqliteMigration.php | 49 ++++++++ tests/db/sqlite/SqliteTest.php | 36 ++++++ 5 files changed, 311 insertions(+) create mode 100644 src/db/sqlite/Sqlite.php create mode 100644 src/db/sqlite/SqliteConfig.php create mode 100644 src/db/sqlite/SqliteException.php create mode 100644 src/db/sqlite/SqliteMigration.php create mode 100644 tests/db/sqlite/SqliteTest.php diff --git a/src/db/sqlite/Sqlite.php b/src/db/sqlite/Sqlite.php new file mode 100644 index 0000000..db7384b --- /dev/null +++ b/src/db/sqlite/Sqlite.php @@ -0,0 +1,178 @@ +db->enableExceptions(true); + } + + static function verifix_query(&$query, ?array &$params=null): void { + if (is_array($query)) { + throw IllegalAccessException::not_implemented(); #XXX + } elseif (!is_string($query)) { + $query = strval($query); + } + } + + const CONFIG = [ + [self::class, "config_enableExceptions"], + ]; + const MIGRATE = null; + + const SCHEMA = [ + "file" => ["string", ""], + "flags" => ["int", SQLITE3_OPEN_READWRITE + SQLITE3_OPEN_CREATE], + "encryption_key" => ["string", ""], + "config" => ["?array|callable"], + "migrate" => ["?array|string|callable"], + "auto_open" => ["bool", true], + ]; + + function __construct(?string $file=null, ?array $params=null) { + if ($file !== null) $params["file"] = $file; + ##schéma + $default_file = self::SCHEMA["file"][1]; + $this->file = strval($params["file"] ?? $default_file); + # + $default_flags = self::SCHEMA["flags"][1]; + $this->flags = intval($params["flags"] ?? $default_flags); + # + $default_encryptionKey = self::SCHEMA["encryption_key"][1]; + $this->encryptionKey = strval($params["encryption_key"] ?? $default_encryptionKey); + # configuration + $this->config = $params["config"] ?? static::CONFIG; + # migrations + $this->migration = $params["migrate"] ?? static::MIGRATE; + # + $default_autoOpen = self::SCHEMA["auto_open"][1]; + if ($params["auto_open"] ?? $default_autoOpen) { + $this->open(); + } + } + + /** @var string */ + protected $file; + /** @var int */ + protected $flags; + /** @var string */ + protected $encryptionKey; + /** @var array|string|callable */ + protected $config; + /** @var array|string|callable */ + protected $migration; + + /** @var SQLite3 */ + protected $db; + + function open(): self { + if ($this->db === null) { + $this->db = new SQLite3($this->file, $this->flags, $this->encryptionKey); + SqliteConfig::with($this->config)->run($this); + SqliteMigration::with($this->migration)->run($this); + } + return $this; + } + + function close(): void { + if ($this->db !== null) { + $this->db->close(); + $this->db = null; + } + } + + function exec($query, ?array $params=null): bool { + $this->open(); + self::verifix_query($query, $params); + $db = $this->db; + if ($params === null) { + return $db->exec($query); + } else { + /** @var SQLite3Stmt $stmt */ + $stmt = SqliteException::check($db, $db->prepare($query)); + try { + foreach ($params as $param => $value) { + SqliteException::check($db, $stmt->bindValue($param, $value)); + } + /** @var SQLite3Result $result */ + $result = SqliteException::check($db, $stmt->execute()); + return $result->finalize(); + } finally { + $stmt->close(); + } + } + } + + function get($query, ?array $params=null, bool $entireRow=false) { + $this->open(); + self::verifix_query($query, $params); + $db = $this->db; + if ($params === null) { + return $db->querySingle($query, $entireRow); + } else { + /** @var SQLite3Stmt $stmt */ + $stmt = SqliteException::check($db, $db->prepare($query)); + try { + foreach ($params as $param => $value) { + SqliteException::check($db, $stmt->bindValue($param, $value)); + } + /** @var SQLite3Result $result */ + $result = SqliteException::check($db, $stmt->execute()); + try { + $row = $result->fetchArray(SQLITE3_ASSOC); + if ($row === false) return null; + elseif ($entireRow) return $row; + else return cl::first($row); + } finally { + $result->finalize(); + } + } finally { + $stmt->close(); + } + } + } + + function all($query, ?array $params=null): iterable { + $this->open(); + self::verifix_query($query, $params); + $db = $this->db; + if ($params === null) { + /** @var SQLite3Result $result */ + $result = SqliteException::check($db, $db->query($query)); + try { + while (($row = $result->fetchArray(SQLITE3_ASSOC)) !== false) { + yield $row; + } + } finally { + $result->finalize(); + } + } else { + /** @var SQLite3Stmt $stmt */ + $stmt = SqliteException::check($db, $db->prepare($query)); + try { + foreach ($params as $param => $value) { + SqliteException::check($db, $stmt->bindValue($param, $value)); + } + /** @var SQLite3Result $result */ + $result = SqliteException::check($db, $stmt->execute()); + try { + while (($row = $result->fetchArray(SQLITE3_ASSOC)) !== false) { + yield $row; + } + } finally { + $result->finalize(); + } + } finally { + $stmt->close(); + } + } + } +} diff --git a/src/db/sqlite/SqliteConfig.php b/src/db/sqlite/SqliteConfig.php new file mode 100644 index 0000000..b5364cb --- /dev/null +++ b/src/db/sqlite/SqliteConfig.php @@ -0,0 +1,35 @@ +configs = $configs; + } + + /** @var array */ + protected $configs; + + function run(Sqlite $sqlite): void { + foreach ($this->configs as $key => $config) { + if (is_callable($config)) { + func::call($config, $sqlite, $key, $this); + } else { + $sqlite->exec($config); + } + } + } +} diff --git a/src/db/sqlite/SqliteException.php b/src/db/sqlite/SqliteException.php new file mode 100644 index 0000000..4cd9250 --- /dev/null +++ b/src/db/sqlite/SqliteException.php @@ -0,0 +1,13 @@ +lastErrorMsg(), $db->lastErrorCode()); + } +} diff --git a/src/db/sqlite/SqliteMigration.php b/src/db/sqlite/SqliteMigration.php new file mode 100644 index 0000000..a315a28 --- /dev/null +++ b/src/db/sqlite/SqliteMigration.php @@ -0,0 +1,49 @@ +migrations = $migrations; + } + + /** @var callable[]|string[] */ + protected $migrations; + + function run(Sqlite $sqlite): void { + $sqlite->exec("create table if not exists _migration(key varchar primary key, value varchar not null, done integer default 0)"); + foreach ($this->migrations as $key => $migration) { + $exists = $sqlite->get("select 1 from _migration where key = :key and done = 1", [ + "key" => $key, + ]); + if (!$exists) { + $sqlite->exec("insert or replace into _migration(key, value, done) values(:key, :value, :done)", [ + "key" => $key, + "value" => $migration, + "done" => 0, + ]); + if (is_callable($migration)) { + func::call($migration, $sqlite, $key, $this); + } else { + $sqlite->exec($migration); + } + $sqlite->exec("update _migration set done = 1 where key = :key", [ + "key" => $key, + ]); + } + } + } +} diff --git a/tests/db/sqlite/SqliteTest.php b/tests/db/sqlite/SqliteTest.php new file mode 100644 index 0000000..e32efed --- /dev/null +++ b/tests/db/sqlite/SqliteTest.php @@ -0,0 +1,36 @@ + [ + self::CREATE_PERSON, + self::INSERT_JEPHTE, + ], + ]); + self::assertSame("clain", $sqlite->get("select nom, age from person")); + self::assertSame([ + "nom" => "clain", + "age" => 50, + ], $sqlite->get("select nom, age from person", null, true)); + + $sqlite->exec(self::INSERT_JEAN); + self::assertSame("payet", $sqlite->get("select nom, age from person where nom = 'payet'")); + self::assertSame([ + "nom" => "payet", + "age" => 32, + ], $sqlite->get("select nom, age from person where nom = 'payet'", null, true)); + + self::assertSame([ + ["key" => "0", "value" => self::CREATE_PERSON, "done" => 1], + ["key" => "1", "value" => self::INSERT_JEPHTE, "done" => 1], + ], iterator_to_array($sqlite->all("select key, value, done from _migration"))); + } +}