diff --git a/src/db/sqlite/Sqlite.php b/src/db/sqlite/Sqlite.php index b3886e8..d185c31 100644 --- a/src/db/sqlite/Sqlite.php +++ b/src/db/sqlite/Sqlite.php @@ -93,14 +93,14 @@ class Sqlite { return SqliteException::check($this->db, $result); } - function _exec(string $sql): bool { + function _exec(string $query): bool { $this->open(); - return $this->db->exec($sql); + return $this->db->exec($query); } - function exec($sql, ?array $params=null): bool { + function exec($query, ?array $params=null): bool { $this->open(); - $query = new _Query($sql, $params); + $query = new _Query($query, $params); $db = $this->db; if ($query->useStmt($db, $stmt, $sql)) { try { @@ -113,14 +113,14 @@ class Sqlite { } } - function _get(string $sql, bool $entireRow=false) { + function _get(string $query, bool $entireRow=false) { $this->open(); - return $this->db->querySingle($sql, $entireRow); + return $this->db->querySingle($query, $entireRow); } - function get($sql, ?array $params=null, bool $entireRow=false) { + function get($query, ?array $params=null, bool $entireRow=false) { $this->open(); - $query = new _Query($sql, $params); + $query = new _Query($query, $params); $db = $this->db; if ($query->useStmt($db, $stmt, $sql)) { try { @@ -151,9 +151,9 @@ class Sqlite { } } - function all($sql, ?array $params=null): iterable { + function all($query, ?array $params=null): iterable { $this->open(); - $query = new _Query($sql, $params); + $query = new _Query($query, $params); $db = $this->db; if ($query->useStmt($db, $stmt, $sql)) { try { diff --git a/src/db/sqlite/_Config.php b/src/db/sqlite/_Config.php index fad404e..670833a 100644 --- a/src/db/sqlite/_Config.php +++ b/src/db/sqlite/_Config.php @@ -25,10 +25,11 @@ class _Config { function configure(Sqlite $sqlite): void { foreach ($this->configs as $key => $config) { - if (is_callable($config)) { - func::call($config, $sqlite, $key, $this); - } else { + if (is_string($config) && !func::is_method($config)) { $sqlite->exec($config); + } else { + func::ensure_func($config, $this, $args); + func::call($config, $sqlite, $key, ...$args); } } } diff --git a/src/db/sqlite/_Migration.php b/src/db/sqlite/_Migration.php index 45970e8..4c9b787 100644 --- a/src/db/sqlite/_Migration.php +++ b/src/db/sqlite/_Migration.php @@ -35,10 +35,11 @@ class _Migration { "value" => $migration, "done" => 0, ]); - if (is_callable($migration)) { - func::call($migration, $sqlite, $key, $this); - } else { + if (is_string($migration) && !func::is_method($migration)) { $sqlite->exec($migration); + } else { + func::ensure_func($migration, $this, $args); + func::call($migration, $sqlite, $key, ...$args); } $sqlite->exec("update _migration set done = 1 where key = :key", [ "key" => $key, diff --git a/src/db/sqlite/_Query.php b/src/db/sqlite/_Query.php index be3882b..2280d99 100644 --- a/src/db/sqlite/_Query.php +++ b/src/db/sqlite/_Query.php @@ -1,7 +1,8 @@ "string", "table" => "string", - "cols" => "array", + "schema" => "?array", + "cols" => "?array", "suffix" => "?string", ]; static function is_create(string $sql): bool { + return false; } static function parse_create(array $query, ?array &$params=null): string { } const select_SCHEMA = [ "prefix" => "string", + "schema" => "?array", "cols" => "?array", "from" => "?string", "where" => "?array", @@ -52,6 +61,7 @@ class _Query { "having" => "?array", ]; static function is_select(string $sql): bool { + return false; } static function parse_select(array $query, ?array &$params=null): string { } @@ -59,22 +69,90 @@ class _Query { const insert_SCHEMA = [ "prefix" => "string", "into" => "?string", + "schema" => "?array", "cols" => "?array", "values" => "?array", ]; static function is_insert(string $sql): bool { + return preg_match("/^insert\b/i", $sql); } static function parse_insert(array $query, ?array &$params=null): string { + # fusionner d'abord toutes les parties séquentielles + $index = 0; + $tmpsql = ""; + foreach ($query as $key => $value) { + if ($key === $index) { + $index++; + if ($tmpsql && !str::ends_with(" ", $tmpsql) && !str::starts_with(" ", $value)) { + $tmpsql .= " "; + } + $tmpsql .= $value; + } + } + $usersql = $tmpsql; + # vérifier la présence des parties nécessaires + $sql = []; + if (($prefix = $query["prefix"] ?? null) !== null) { + $sql["prefix"] = $prefix; + } + self::consume('insert\s*', $tmpsql); $sql[] = "insert"; + self::consume('into\s*', $tmpsql); $sql[] = "into"; + if (self::consume('([a-z_][a-z0-9_]*)\s*', $tmpsql, $ms)) { + $sql[] = $ms[1]; + } elseif (($into = $query["into"] ?? null) !== null) { + $sql[] = $into; + } else { + throw new ValueException("expected table name: $usersql"); + } + $xcols = []; + $xvalues = []; + if (self::consume('\(([^)]*)\)\s*', $tmpsql, $ms)) { + $xcols = array_merge($xcols, preg_split("/\s*,\s*/", $ms[1])); + } + $cols = cl::withn($query["cols"] ?? null); + $values = cl::withn($query["values"] ?? null); + $schema = $query["schema"] ?? null; + if ($cols === null) { + if ($xcols) { + $cols = $xcols; + } elseif ($values) { + $cols = array_keys($values); + $xcols = array_merge($xcols, $cols); + } elseif ($schema && is_array($schema)) { + #XXX implémenter support AssocSchema + $cols = array_keys($schema); + $xcols = array_merge($xcols, $cols); + } + } + if (self::consume('values\s+\(\s*(.*)\s*\)\s*', $tmpsql, $ms)) { + if ($ms[1]) $xvalues[] = $ms[1]; + } + self::consume(';\s*', $tmpsql); + if ($tmpsql) { + throw new ValueException("unexpected value at end: $usersql"); + } + if ($cols !== null && !$xvalues) { + if (!$xcols) $xcols = $cols; + foreach ($cols as $col) { + $xvalues[] = ":$col"; + $params[$col] = $values[$col] ?? null; + } + } + $sql[] = "(".implode(", ", $xcols).")"; + $sql[] = "values (".implode(", ", $xvalues).")"; + return implode(" ", $sql); } const update_SCHEMA = [ "prefix" => "string", "table" => "?string", + "schema" => "?array", "cols" => "?array", "values" => "?array", "where" => "?array", ]; static function is_update(string $sql): bool { + return false; } static function parse_update(array $query, ?array &$params=null): string { } @@ -85,6 +163,7 @@ class _Query { "where" => "?array", ]; static function is_delete(string $sql): bool { + return false; } static function parse_delete(array $query, ?array &$params=null): string { } diff --git a/tests/db/sqlite/SqliteTest.php b/tests/db/sqlite/SqliteTest.php index e32efed..bb436cc 100644 --- a/tests/db/sqlite/SqliteTest.php +++ b/tests/db/sqlite/SqliteTest.php @@ -33,4 +33,33 @@ class SqliteTest extends TestCase { ["key" => "1", "value" => self::INSERT_JEPHTE, "done" => 1], ], iterator_to_array($sqlite->all("select key, value, done from _migration"))); } + + function testInsert() { + $sqlite = new Sqlite(":memory:", [ + "migrate" => "create table mapping (i integer, s varchar)" + ]); + $sqlite->exec(["insert into mapping", "values" => ["i" => 1, "s" => "un"]]); + $sqlite->exec(["insert mapping", "values" => ["i" => 2, "s" => "deux"]]); + $sqlite->exec(["insert into", "into" => "mapping", "values" => ["i" => 3, "s" => "trois"]]); + $sqlite->exec(["insert", "into" => "mapping", "values" => ["i" => 4, "s" => "quatre"]]); + $sqlite->exec(["insert into mapping(i)", "values" => ["i" => 5, "s" => "cinq"]]); + $sqlite->exec(["insert into (i)", "into" => "mapping", "values" => ["i" => 6, "s" => "six"]]); + $sqlite->exec(["insert into mapping(i) values ()", "values" => ["i" => 7, "s" => "sept"]]); + $sqlite->exec(["insert into mapping(i) values (8)", "values" => ["i" => 42, "s" => "whatever"]]); + $sqlite->exec(["insert into mapping(i, s) values (9, 'neuf')", "values" => ["i" => 43, "s" => "garbage"]]); + $sqlite->exec(["insert into mapping", "cols" => ["i"], "values" => ["i" => 10, "s" => "dix"]]); + + self::assertSame([ + ["i" => 1, "s" => "un"], + ["i" => 2, "s" => "deux"], + ["i" => 3, "s" => "trois"], + ["i" => 4, "s" => "quatre"], + ["i" => 5, "s" => null/*"cinq"*/], + ["i" => 6, "s" => null/*"six"*/], + ["i" => 7, "s" => null/*"sept"*/], + ["i" => 8, "s" => null/*"huit"*/], + ["i" => 9, "s" => "neuf"], + ["i" => 10, "s" => null/*"dix"*/], + ], iterator_to_array($sqlite->all("select * from mapping"))); + } }