ajout sqlite
This commit is contained in:
parent
3550730a9f
commit
3bd1031dda
|
@ -0,0 +1,178 @@
|
||||||
|
<?php
|
||||||
|
namespace nur\sery\db\sqlite;
|
||||||
|
|
||||||
|
use nur\b\IllegalAccessException;
|
||||||
|
use nur\sery\cl;
|
||||||
|
use SQLite3;
|
||||||
|
use SQLite3Result;
|
||||||
|
use SQLite3Stmt;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class Sqlite: frontend vers une base de données sqlite3
|
||||||
|
*/
|
||||||
|
class Sqlite {
|
||||||
|
static function config_enableExceptions(self $sqlite) {
|
||||||
|
$sqlite->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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
<?php
|
||||||
|
namespace nur\sery\db\sqlite;
|
||||||
|
|
||||||
|
use nur\sery\php\func;
|
||||||
|
|
||||||
|
class SqliteConfig {
|
||||||
|
static function with($configs): SqliteConfig {
|
||||||
|
if ($configs instanceof self) return $configs;
|
||||||
|
return new static($configs);
|
||||||
|
}
|
||||||
|
|
||||||
|
const CONFIG = null;
|
||||||
|
|
||||||
|
function __construct($configs) {
|
||||||
|
if ($configs === null) $configs = static::CONFIG;
|
||||||
|
if ($configs === null) $configs = [];
|
||||||
|
elseif (is_string($configs)) $configs = [$configs];
|
||||||
|
elseif (is_callable($configs)) $configs = [$configs];
|
||||||
|
elseif (!is_array($configs)) $configs = [strval($configs)];
|
||||||
|
$this->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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
<?php
|
||||||
|
namespace nur\sery\db\sqlite;
|
||||||
|
|
||||||
|
use RuntimeException;
|
||||||
|
use SQLite3;
|
||||||
|
|
||||||
|
class SqliteException extends RuntimeException {
|
||||||
|
static final function check(SQLite3 $db, $value, bool $throw=true) {
|
||||||
|
if ($value !== false) return $value;
|
||||||
|
elseif (!$throw) return null;
|
||||||
|
else throw new static($db->lastErrorMsg(), $db->lastErrorCode());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
<?php
|
||||||
|
namespace nur\sery\db\sqlite;
|
||||||
|
|
||||||
|
use nur\sery\php\func;
|
||||||
|
|
||||||
|
class SqliteMigration {
|
||||||
|
static function with($migrations): SqliteMigration {
|
||||||
|
if ($migrations instanceof self) return $migrations;
|
||||||
|
return new static($migrations);
|
||||||
|
}
|
||||||
|
|
||||||
|
const MIGRATE = null;
|
||||||
|
|
||||||
|
function __construct($migrations) {
|
||||||
|
if ($migrations === null) $migrations = static::MIGRATE;
|
||||||
|
if ($migrations === null) $migrations = [];
|
||||||
|
elseif (is_string($migrations)) $migrations = [$migrations];
|
||||||
|
elseif (is_callable($migrations)) $migrations = [$migrations];
|
||||||
|
elseif (!is_array($migrations)) $migrations = [strval($migrations)];
|
||||||
|
$this->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,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
<?php
|
||||||
|
namespace nur\sery\db\sqlite;
|
||||||
|
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
class SqliteTest extends TestCase {
|
||||||
|
const CREATE_PERSON = "create table person(nom varchar, prenom varchar, age integer)";
|
||||||
|
const INSERT_JEPHTE = "insert into person(nom, prenom, age) values ('clain', 'jephte', 50)";
|
||||||
|
const INSERT_JEAN = "insert into person(nom, prenom, age) values ('payet', 'jean', 32)";
|
||||||
|
|
||||||
|
function testMigration() {
|
||||||
|
$sqlite = new Sqlite(":memory:", [
|
||||||
|
"migrate" => [
|
||||||
|
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")));
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue