diff --git a/README.md b/README.md index 077491f..5fec0c2 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,7 @@ The documentation for version 1.x you can see [here](https://github.com/lapaliv/ - Database: - MySQL: __5.7+__ - PostgreSQL __9.6+__ + - SQLite __3.32+__ - PHP: __8.0+__ - Laravel: __8.0+__ @@ -768,8 +769,8 @@ class BulkRow { * Bulk touching * Bulk updating without updating timestamps * Supporting `DB::raw()` as a value -* Supporting `SQLite` * Support a custom database driver +* Update rows and return the number of updated ones ### Tests diff --git a/src/Drivers/SqLite/SqLiteDriverForceDeleteFeature.php b/src/Drivers/SqLite/SqLiteDriverForceDeleteFeature.php new file mode 100644 index 0000000..fb8d031 --- /dev/null +++ b/src/Drivers/SqLite/SqLiteDriverForceDeleteFeature.php @@ -0,0 +1,33 @@ +mixedValueToSqlConverter); + + $result = $connection->delete($grammar->delete($builder), $grammar->getBindings()); + + unset($grammar); + + return $result; + } +} diff --git a/src/Drivers/SqLite/SqLiteDriverInsertWithResultFeature.php b/src/Drivers/SqLite/SqLiteDriverInsertWithResultFeature.php new file mode 100644 index 0000000..1945a3d --- /dev/null +++ b/src/Drivers/SqLite/SqLiteDriverInsertWithResultFeature.php @@ -0,0 +1,51 @@ +selectOne( + sprintf( + 'select max(%s) as id from %s', + $primaryKeyName, + $builder->getInto() + ) + ); + + $lastPrimaryBeforeInserting = $lastRow->id ?? 0; + } + + $grammar = new SqLiteGrammar($this->mixedValueToSqlConverter); + $sql = $grammar->insert($builder); + $bindings = $grammar->getBindings(); + $connection->insert($sql, $bindings); + + unset($grammar); + + return new BulkSqLiteInsertResult( + is_numeric($lastPrimaryBeforeInserting) + ? (int) $lastPrimaryBeforeInserting + : null + ); + } +} diff --git a/src/Drivers/SqLite/SqLiteDriverQuietInsertFeature.php b/src/Drivers/SqLite/SqLiteDriverQuietInsertFeature.php new file mode 100644 index 0000000..acb54a6 --- /dev/null +++ b/src/Drivers/SqLite/SqLiteDriverQuietInsertFeature.php @@ -0,0 +1,33 @@ +mixedValueToSqlConverter); + + $connection->insert($grammar->insert($builder), $grammar->getBindings()); + + unset($grammar); + } +} diff --git a/src/Drivers/SqLite/SqLiteDriverUpdateFeature.php b/src/Drivers/SqLite/SqLiteDriverUpdateFeature.php new file mode 100644 index 0000000..acbcd13 --- /dev/null +++ b/src/Drivers/SqLite/SqLiteDriverUpdateFeature.php @@ -0,0 +1,33 @@ +mixedValueToSqlConverter); + + $result = $connection->update($grammar->update($builder), $grammar->getBindings()); + + unset($grammar); + + return $result; + } +} diff --git a/src/Drivers/SqLiteBulkDriver.php b/src/Drivers/SqLiteBulkDriver.php new file mode 100644 index 0000000..175e8f9 --- /dev/null +++ b/src/Drivers/SqLiteBulkDriver.php @@ -0,0 +1,49 @@ +insertWithResultFeature->handle($connection, $builder, $primaryKeyName); + } + + public function quietInsert(ConnectionInterface $connection, InsertBuilder $builder): void + { + $this->quietInsertFeature->handle($connection, $builder); + } + + public function update(ConnectionInterface $connection, UpdateBulkBuilder $builder): int + { + return $this->updateFeature->handle($connection, $builder); + } + + public function forceDelete(ConnectionInterface $connection, DeleteBulkBuilder $builder): int + { + return $this->forceDeleteFeature->handle($connection, $builder); + } +} diff --git a/src/Entities/BulkSqLiteInsertResult.php b/src/Entities/BulkSqLiteInsertResult.php new file mode 100644 index 0000000..ac3cbdb --- /dev/null +++ b/src/Entities/BulkSqLiteInsertResult.php @@ -0,0 +1,26 @@ +maxPrimaryBeforeInserting; + } +} diff --git a/src/Grammars/SqLiteGrammar.php b/src/Grammars/SqLiteGrammar.php new file mode 100644 index 0000000..7d70f5e --- /dev/null +++ b/src/Grammars/SqLiteGrammar.php @@ -0,0 +1,155 @@ +getValues() as $value) { + $item = []; + + foreach ($builder->getColumns() as $column) { + $item[] = $this->mixedValueToSqlConverter->handle($value[$column] ?? null, $this->bindings); + } + $values[] = implode(',', $item); + } + + return sprintf( + 'insert %s into %s (`%s`) values (%s)', + $builder->doNothingAtConflict() ? 'or ignore' : '', + $builder->getInto(), + implode('`,`', $builder->getColumns()), + implode('),(', $values), + ); + } + + public function update(UpdateBulkBuilder $builder): string + { + $sets = []; + + foreach ($builder->getSets() as $field => $set) { + $whens = []; + + if ($set instanceof BuilderCase && count($set->getWhens()) === 1) { + $when = $set->getWhens()[0]; + $sets[] = sprintf( + '`%s` = iif(%s, %s, `%s`)', + $field, + $this->getSqlWhereClause($when->getWheres()), + $this->mixedValueToSqlConverter->handle($when->getThen(), $this->bindings), + $this->mixedValueToSqlConverter->handle($set->getElse(), $this->bindings), + ); + } elseif ($set instanceof BuilderCase) { + foreach ($set->getWhens() as $when) { + $whens[] = sprintf( + 'when %s then %s', + $this->getSqlWhereClause($when->getWheres()), + $this->mixedValueToSqlConverter->handle($when->getThen(), $this->bindings) + ); + } + + $sets[] = sprintf( + '`%s` = case %s else `%s` end', + $field, + implode(' ', $whens), + $this->mixedValueToSqlConverter->handle($set->getElse(), $this->bindings), + ); + } else { + $sets[] = sprintf( + '`%s` = %s', + $field, + $this->mixedValueToSqlConverter->handle($set, $this->bindings), + ); + } + } + + return sprintf( + 'update %s set %s where %s', + $builder->getTable(), + implode(',', $sets), + $this->getSqlWhereClause($builder->getWheres()) + ); + } + + public function delete(DeleteBulkBuilder $builder): string + { + return sprintf( + 'delete from %s where %s', + $builder->getFrom(), + $this->getSqlWhereClause($builder->getWheres()) + ); + } + + public function getBindings(): array + { + return $this->bindings; + } + + private function getSqlWhereClause(array $wheres): string + { + $result = []; + + foreach ($wheres as $where) { + if (!empty($result)) { + $result[] = $where->boolean; + } + + if ($where instanceof BuilderWhereCallback) { + $selectBuilder = new SelectBulkBuilder(); + call_user_func($where->callback, $selectBuilder); + + $result[] = '(' . $this->getSqlWhereClause($selectBuilder->getWheres()) . ')'; + } elseif ($where instanceof BuilderWhereCondition) { + $result[] = sprintf( + '`%s` %s %s', + $where->field, + $where->operator, + $this->mixedValueToSqlConverter->handle($where->value, $this->bindings), + ); + } elseif ($where instanceof BuilderWhereIn) { + $values = []; + + foreach ($where->values as $value) { + $values[] = $this->mixedValueToSqlConverter->handle($value, $this->bindings); + } + + if (count($values) === 1) { + $result[] = sprintf( + '`%s` = %s', + $where->field, + $values[0], + ); + } else { + $result[] = sprintf( + '`%s` in(%s)', + $where->field, + implode(',', $values), + ); + } + } + } + + return implode(' ', $result); + } +} diff --git a/src/Providers/BulkUpsertServiceProvider.php b/src/Providers/BulkUpsertServiceProvider.php index 748ecdb..bf62ed7 100644 --- a/src/Providers/BulkUpsertServiceProvider.php +++ b/src/Providers/BulkUpsertServiceProvider.php @@ -8,6 +8,7 @@ use Lapaliv\BulkUpsert\Contracts\BulkDriverManager; use Lapaliv\BulkUpsert\Drivers\MySqlBulkDriver; use Lapaliv\BulkUpsert\Drivers\PostgreSqlBulkDriver; +use Lapaliv\BulkUpsert\Drivers\SqLiteBulkDriver; use Lapaliv\BulkUpsert\Events\BulkEventDispatcher; use Lapaliv\BulkUpsert\Features\AddWhereClauseToBuilderFeature; use Lapaliv\BulkUpsert\Features\GetDateFieldsFeature; @@ -57,6 +58,10 @@ public function register(): void 'pgsql', $this->app->make(PostgreSqlBulkDriver::class) ); + $driverManager->registerDriver( + 'sqlite', + $this->app->make(SqLiteBulkDriver::class) + ); BulkEventDispatcher::setIlluminateEventDispatcher($this->app->make('events')); } diff --git a/tests/database/.gitignore b/tests/database/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/tests/database/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore