Skip to content

Commit 6ca92f1

Browse files
committed
Got most of #405 implemented on master
1 parent 74456af commit 6ca92f1

File tree

7 files changed

+174
-97
lines changed

7 files changed

+174
-97
lines changed

src/Tqdev/PhpCrudApi/Database/ColumnsBuilder.php

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,14 @@ public function getOffsetLimit(int $offset, int $limit): string
2222
return '';
2323
}
2424
switch ($this->driver) {
25-
case 'mysql':return " LIMIT $offset, $limit";
26-
case 'pgsql':return " LIMIT $limit OFFSET $offset";
27-
case 'sqlsrv':return " OFFSET $offset ROWS FETCH NEXT $limit ROWS ONLY";
25+
case 'mysql':
26+
return " LIMIT $offset, $limit";
27+
case 'pgsql':
28+
return " LIMIT $limit OFFSET $offset";
29+
case 'sqlsrv':
30+
return " OFFSET $offset ROWS FETCH NEXT $limit ROWS ONLY";
31+
case 'sqlite':
32+
return " LIMIT $limit OFFSET $offset";
2833
}
2934
}
3035

@@ -74,9 +79,14 @@ public function getInsert(ReflectedTable $table, array $columnValues): string
7479
$valuesSql = '(' . implode(',', $values) . ')';
7580
$outputColumn = $this->quoteColumnName($table->getPk());
7681
switch ($this->driver) {
77-
case 'mysql':return "$columnsSql VALUES $valuesSql";
78-
case 'pgsql':return "$columnsSql VALUES $valuesSql RETURNING $outputColumn";
79-
case 'sqlsrv':return "$columnsSql OUTPUT INSERTED.$outputColumn VALUES $valuesSql";
82+
case 'mysql':
83+
return "$columnsSql VALUES $valuesSql";
84+
case 'pgsql':
85+
return "$columnsSql VALUES $valuesSql RETURNING $outputColumn";
86+
case 'sqlsrv':
87+
return "$columnsSql OUTPUT INSERTED.$outputColumn VALUES $valuesSql";
88+
case 'sqlite':
89+
return "$columnsSql VALUES $valuesSql";
8090
}
8191
}
8292

src/Tqdev/PhpCrudApi/Database/DataConverter.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,10 @@ private function convertRecordValue($conversion, $value)
2727

2828
private function getRecordValueConversion(ReflectedColumn $column): string
2929
{
30-
if (in_array($this->driver, ['mysql', 'sqlsrv']) && $column->isBoolean()) {
30+
if (in_array($this->driver, ['mysql', 'sqlsrv', 'sqlite']) && $column->isBoolean()) {
3131
return 'boolean';
3232
}
33-
if ($this->driver == 'sqlsrv' && $column->getType() == 'bigint') {
33+
if (in_array($this->driver, ['sqlsrv', 'sqlite']) && in_array($column->getType(), ['integer', 'bigint'])) {
3434
return 'integer';
3535
}
3636
return 'none';

src/Tqdev/PhpCrudApi/Database/GenericDB.php

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,7 @@ private function getOptions(): array
8383
\PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE => true,
8484
];
8585
case 'sqlite':
86-
return $options + [
87-
];
86+
return $options + [];
8887
}
8988
}
9089

@@ -192,11 +191,17 @@ public function createSingle(ReflectedTable $table, array $columnValues) /*: ?St
192191
case 'mysql':
193192
$stmt = $this->query('SELECT LAST_INSERT_ID()', []);
194193
break;
194+
case 'sqlite':
195+
$stmt = $this->query('SELECT LAST_INSERT_ROWID()', []);
196+
break;
195197
}
196198
$pkValue = $stmt->fetchColumn(0);
197199
if ($this->driver == 'sqlsrv' && $table->getPk()->getType() == 'bigint') {
198200
return (int) $pkValue;
199201
}
202+
if ($this->driver == 'sqlite' && in_array($table->getPk()->getType(), ['integer', 'bigint'])) {
203+
return (int) $pkValue;
204+
}
200205
return $pkValue;
201206
}
202207

src/Tqdev/PhpCrudApi/Database/GenericReflection.php

Lines changed: 71 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -23,50 +23,47 @@ public function __construct(LazyPdo $pdo, string $driver, string $database, arra
2323

2424
private function createSqlLiteReflectionTables() /*: void */
2525
{
26-
$reflection = $this->query('SELECT "name" FROM "sqlite_master" WHERE "type" = \'table\' and name like \'sys/%\';',[]);
27-
if (count($reflection->fetchAll())==0) {
28-
//create reflection tables
29-
$this->query('CREATE table "sys/version" ("version" integer);',[]);
30-
$this->query('CREATE table "sys/tables" ("name" text, "type" text);',[]);
31-
$this->query('CREATE table "sys/columns" ("self" text,"cid" integer,"name" text,"type" integer,"notnull" integer,"dflt_value" integer,"pk" integer);',[]);
32-
$this->query('CREATE table "sys/foreign_keys" ("self" text,"id" integer,"seq" integer,"table" text,"from" text,"to" text,"on_update" text,"on_delete" text,"match" text);',[]);
33-
}
34-
$version = $this->query('pragma schema_version',[])->fetchColumn(0);
35-
if ($version != $this->query('SELECT "version" from "sys/version";')->fetchColumn(0)) {
36-
// reflection may take a while
37-
set_time_limit(3600);
38-
// update version data
39-
$this->query('DELETE FROM "sys/version";');
40-
$stmt = $this->pdo->prepare('INSERT into "sys/version" ("version") VALUES (?);');
41-
$stmt->execute(array($version));
42-
43-
// update tables data
44-
$this->query('DELETE FROM "sys/tables";');
45-
$result = $this->query('SELECT "name", "type" FROM sqlite_master WHERE ("type" = \'table\' or "type" = \'view\') and name not like "sys/%" and name<>"sqlite_sequence";');
46-
$tables = array();
47-
foreach($result as $row) {
48-
$tables[] = $row['name'];
49-
$stmt = $this->pdo->prepare('INSERT into "sys/tables" ("name", "type") VALUES (?, ?);');
50-
$stmt->execute(array($row['name'], $row['type']));
51-
}
52-
// update columns and foreign_keys data
53-
$this->query('DELETE FROM "sys/columns"');
54-
$this->query('DELETE FROM "sys/foreign_keys"');
55-
foreach ($tables as $table) {
56-
$result = $this->query("pragma table_info(`$table`);");
57-
foreach($result as $row) {
58-
array_unshift($row, $table);
59-
$stmt = $this->pdo->prepare('INSERT into "sys/columns" ("self","cid","name","type","notnull","dflt_value","pk") VALUES (?,?,?,?,?,?,?);');
60-
$stmt->execute(array_values($row));
61-
}
62-
$result = $this->query("pragma foreign_key_list(`$table`);");
63-
foreach($result as $row) {
64-
array_unshift($row, $table);
65-
$stmt = $this->pdo->prepare('INSERT into "sys/foreign_keys" ("self","id","seq","table","from","to","on_update","on_delete","match") VALUES (?,?,?,?,?,?,?,?,?);');
66-
$stmt->execute(array_values($row));
67-
}
68-
}
69-
}
26+
$reflection = $this->query('SELECT "name" FROM "sqlite_master" WHERE "type" = \'table\' and name like \'sys/%\';', []);
27+
if (count($reflection) == 0) {
28+
//create reflection tables
29+
$this->query('CREATE table "sys/version" ("version" integer);', []);
30+
$this->query('CREATE table "sys/tables" ("name" text, "type" text);', []);
31+
$this->query('CREATE table "sys/columns" ("self" text,"cid" integer,"name" text,"type" integer,"notnull" integer,"dflt_value" integer,"pk" integer);', []);
32+
$this->query('CREATE table "sys/foreign_keys" ("self" text,"id" integer,"seq" integer,"table" text,"from" text,"to" text,"on_update" text,"on_delete" text,"match" text);', []);
33+
}
34+
$version = $this->query('pragma schema_version;', [])[0]["schema_version"];
35+
$current = $this->query('SELECT "version" from "sys/version";', []);
36+
if (!$current || count($current) == 0 || !isset($current[0]["schema_version"]) || $version != $current[0]["schema_version"]) {
37+
// reflection may take a while
38+
set_time_limit(3600);
39+
// update version data
40+
$this->query('DELETE FROM "sys/version";', []);
41+
$this->query('INSERT into "sys/version" ("version") VALUES (?);', [$version]);
42+
43+
// update tables data
44+
$this->query('DELETE FROM "sys/tables";', []);
45+
$result = $this->query('SELECT "name", "type" FROM sqlite_master WHERE ("type" = \'table\' or "type" = \'view\') and name not like "sys/%" and name<>"sqlite_sequence";', []);
46+
$tables = array();
47+
foreach ($result as $row) {
48+
$tables[] = $row['name'];
49+
$this->query('INSERT into "sys/tables" ("name", "type") VALUES (?, ?);', [$row['name'], $row['type']]);
50+
}
51+
// update columns and foreign_keys data
52+
$this->query('DELETE FROM "sys/columns";', []);
53+
$this->query('DELETE FROM "sys/foreign_keys";', []);
54+
foreach ($tables as $table) {
55+
$result = $this->query("pragma table_info(`$table`);", []);
56+
foreach ($result as $row) {
57+
array_unshift($row, $table);
58+
$this->query('INSERT into "sys/columns" ("self","cid","name","type","notnull","dflt_value","pk") VALUES (?,?,?,?,?,?,?);', array_values($row));
59+
}
60+
$result = $this->query("pragma foreign_key_list(`$table`);", []);
61+
foreach ($result as $row) {
62+
array_unshift($row, $table);
63+
$this->query('INSERT into "sys/foreign_keys" ("self","id","seq","table","from","to","on_update","on_delete","match") VALUES (?,?,?,?,?,?,?,?,?);', array_values($row));
64+
}
65+
}
66+
}
7067
}
7168

7269
public function getIgnoredTables(): array
@@ -79,12 +76,10 @@ public function getIgnoredTables(): array
7976
case 'sqlsrv':
8077
return [];
8178
case 'sqlite':
82-
return ['sys/version','sys/tables','sys/columns','sys/foreign_keys'];
79+
return ['sys/version', 'sys/tables', 'sys/columns', 'sys/foreign_keys'];
8380
}
8481
}
8582

86-
87-
8883
private function getTablesSQL(): string
8984
{
9085
switch ($this->driver) {
@@ -96,7 +91,7 @@ private function getTablesSQL(): string
9691
return 'SELECT o.name as "TABLE_NAME", o.xtype as "TABLE_TYPE" FROM sysobjects o WHERE o.xtype IN (\'U\', \'V\') ORDER BY "TABLE_NAME"';
9792
case 'sqlite':
9893
$this->createSqlLiteReflectionTables();
99-
return 'SELECT t.name as "TABLE_NAME", t.type as "TABLE_TYPE" FROM "sys/tables" t WHERE t.type IN (\'table\', \'view\') ORDER BY "TABLE_NAME"';
94+
return 'SELECT t.name as "TABLE_NAME", t.type as "TABLE_TYPE" FROM "sys/tables" t WHERE t.type IN (\'table\', \'view\') AND \'\' <> ? ORDER BY "TABLE_NAME"';
10095
}
10196
}
10297

@@ -109,6 +104,8 @@ private function getTableColumnsSQL(): string
109104
return 'SELECT a.attname AS "COLUMN_NAME", case when a.attnotnull then \'NO\' else \'YES\' end as "IS_NULLABLE", pg_catalog.format_type(a.atttypid, -1) as "DATA_TYPE", case when a.atttypmod < 0 then NULL else a.atttypmod-4 end as "CHARACTER_MAXIMUM_LENGTH", case when a.atttypid != 1700 then NULL else ((a.atttypmod - 4) >> 16) & 65535 end as "NUMERIC_PRECISION", case when a.atttypid != 1700 then NULL else (a.atttypmod - 4) & 65535 end as "NUMERIC_SCALE", \'\' AS "COLUMN_TYPE" FROM pg_attribute a JOIN pg_class pgc ON pgc.oid = a.attrelid WHERE pgc.relname = ? AND \'\' <> ? AND a.attnum > 0 AND NOT a.attisdropped;';
110105
case 'sqlsrv':
111106
return 'SELECT c.name AS "COLUMN_NAME", c.is_nullable AS "IS_NULLABLE", t.Name AS "DATA_TYPE", (c.max_length/2) AS "CHARACTER_MAXIMUM_LENGTH", c.precision AS "NUMERIC_PRECISION", c.scale AS "NUMERIC_SCALE", \'\' AS "COLUMN_TYPE" FROM sys.columns c INNER JOIN sys.types t ON c.user_type_id = t.user_type_id WHERE c.object_id = OBJECT_ID(?) AND \'\' <> ?';
107+
case 'sqlite':
108+
return 'SELECT "name" AS "COLUMN_NAME", case when "notnull"==1 then \'no\' else \'yes\' end as "IS_NULLABLE", "type" AS "DATA_TYPE", 2147483647 AS "CHARACTER_MAXIMUM_LENGTH", 0 AS "NUMERIC_PRECISION", 0 AS "NUMERIC_SCALE", \'\' AS "COLUMN_TYPE" FROM "sys/columns" WHERE "self" = ? AND \'\' <> ?';
112109
}
113110
}
114111

@@ -121,6 +118,8 @@ private function getTablePrimaryKeysSQL(): string
121118
return 'SELECT a.attname AS "COLUMN_NAME" FROM pg_attribute a JOIN pg_constraint c ON (c.conrelid, c.conkey[1]) = (a.attrelid, a.attnum) JOIN pg_class pgc ON pgc.oid = a.attrelid WHERE pgc.relname = ? AND \'\' <> ? AND c.contype = \'p\'';
122119
case 'sqlsrv':
123120
return 'SELECT c.NAME as "COLUMN_NAME" FROM sys.key_constraints kc inner join sys.objects t on t.object_id = kc.parent_object_id INNER JOIN sys.index_columns ic ON kc.parent_object_id = ic.object_id and kc.unique_index_id = ic.index_id INNER JOIN sys.columns c ON ic.object_id = c.object_id AND ic.column_id = c.column_id WHERE kc.type = \'PK\' and t.object_id = OBJECT_ID(?) and \'\' <> ?';
121+
case 'sqlite':
122+
return 'SELECT "name" as "COLUMN_NAME" FROM "sys/columns" WHERE "pk"=1 AND "self"=? AND \'\' <> ?';
124123
}
125124
}
126125

@@ -133,6 +132,8 @@ private function getTableForeignKeysSQL(): string
133132
return 'SELECT a.attname AS "COLUMN_NAME", c.confrelid::regclass::text AS "REFERENCED_TABLE_NAME" FROM pg_attribute a JOIN pg_constraint c ON (c.conrelid, c.conkey[1]) = (a.attrelid, a.attnum) JOIN pg_class pgc ON pgc.oid = a.attrelid WHERE pgc.relname = ? AND \'\' <> ? AND c.contype = \'f\'';
134133
case 'sqlsrv':
135134
return 'SELECT COL_NAME(fc.parent_object_id, fc.parent_column_id) AS "COLUMN_NAME", OBJECT_NAME (f.referenced_object_id) AS "REFERENCED_TABLE_NAME" FROM sys.foreign_keys AS f INNER JOIN sys.foreign_key_columns AS fc ON f.OBJECT_ID = fc.constraint_object_id WHERE f.parent_object_id = OBJECT_ID(?) and \'\' <> ?';
135+
case 'sqlite':
136+
return 'SELECT "from" AS "COLUMN_NAME", "table" AS "REFERENCED_TABLE_NAME" FROM "sys/foreign_keys" WHERE "self" = ? AND \'\' <> ?';
136137
}
137138
}
138139

@@ -150,20 +151,22 @@ public function getTables(): array
150151
return !$tables || in_array($v['TABLE_NAME'], $tables);
151152
});
152153
foreach ($results as &$result) {
154+
$map = [];
153155
switch ($this->driver) {
154156
case 'mysql':
155157
$map = ['BASE TABLE' => 'table', 'VIEW' => 'view'];
156-
$result['TABLE_TYPE'] = $map[$result['TABLE_TYPE']];
157158
break;
158159
case 'pgsql':
159160
$map = ['r' => 'table', 'v' => 'view'];
160-
$result['TABLE_TYPE'] = $map[$result['TABLE_TYPE']];
161161
break;
162162
case 'sqlsrv':
163163
$map = ['U' => 'table', 'V' => 'view'];
164-
$result['TABLE_TYPE'] = $map[trim($result['TABLE_TYPE'])];
164+
break;
165+
case 'sqlite':
166+
$map = ['table' => 'table', 'view' => 'view'];
165167
break;
166168
}
169+
$result['TABLE_TYPE'] = $map[trim($result['TABLE_TYPE'])];
167170
}
168171
return $results;
169172
}
@@ -192,6 +195,23 @@ public function getTableColumns(string $tableName, string $type): array
192195
}
193196
}
194197
}
198+
if ($this->driver == 'sqlite') {
199+
foreach ($results as &$result) {
200+
// mysql does not properly reflect display width of types
201+
preg_match('|([a-z]+)(\(([0-9]+)(,([0-9]+))?\))?|', $result['DATA_TYPE'], $matches);
202+
if (isset($matches[1])) {
203+
$result['DATA_TYPE'] = $matches[1];
204+
} else {
205+
$result['DATA_TYPE'] = 'text';
206+
}
207+
if (isset($matches[5])) {
208+
$result['NUMERIC_PRECISION'] = $matches[3];
209+
$result['NUMERIC_SCALE'] = $matches[5];
210+
} else if (isset($matches[3])) {
211+
$result['CHARACTER_MAXIMUM_LENGTH'] = $matches[3];
212+
}
213+
}
214+
}
195215
return $results;
196216
}
197217

src/Tqdev/PhpCrudApi/Database/TypeConverter.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,20 @@ public function __construct(string $driver)
121121
'uniqueidentifier' => 'char',
122122
'xml' => 'clob',
123123
],
124+
'sqlite' => [
125+
'tinytext' => 'clob',
126+
'text' => 'clob',
127+
'mediumtext' => 'clob',
128+
'longtext' => 'clob',
129+
'mediumint' => 'integer',
130+
'int' => 'integer',
131+
'bigint' => 'bigint',
132+
'int2' => 'smallint',
133+
'int4' => 'integer',
134+
'int8' => 'bigint',
135+
'double precision' => 'double',
136+
'datetime' => 'timestamp',
137+
],
124138
];
125139

126140
// source: https://docs.oracle.com/javase/9/docs/api/java/sql/Types.html

src/Tqdev/PhpCrudApi/Middleware/Router/SimpleRouter.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,8 @@ public function handle(ServerRequestInterface $request): ResponseInterface
141141
} catch (\PDOException $e) {
142142
if (strpos(strtolower($e->getMessage()), 'duplicate') !== false) {
143143
$response = $this->responder->error(ErrorCode::DUPLICATE_KEY_EXCEPTION, '');
144+
} elseif (strpos(strtolower($e->getMessage()), 'unique constraint') !== false) {
145+
$response = $this->responder->error(ErrorCode::DUPLICATE_KEY_EXCEPTION, '');
144146
} elseif (strpos(strtolower($e->getMessage()), 'default value') !== false) {
145147
$response = $this->responder->error(ErrorCode::DATA_INTEGRITY_VIOLATION, '');
146148
} elseif (strpos(strtolower($e->getMessage()), 'allow nulls') !== false) {

0 commit comments

Comments
 (0)