diff --git a/.github/workflows/phpunit.yml b/.github/workflows/phpunit.yml index b65df91..7a8e244 100644 --- a/.github/workflows/phpunit.yml +++ b/.github/workflows/phpunit.yml @@ -20,6 +20,23 @@ jobs: - "8.2" - "8.1" + services: + mysql: + image: mysql:8.0.20 + env: + MYSQL_ROOT_PASSWORD: password + MYSQL_AUTHENTICATION_PLUGIN: mysql_native_password + ports: + - "3306:3306" + options: >- + --health-cmd="mysqladmin ping" + --health-interval=10s + --health-timeout=20s + --health-retries=10 + + env: + MYSQL_TEST_HOST: mysql + steps: - uses: actions/checkout@v4 - run: composer install diff --git a/.idea/runConfigurations/MySQL_Server_Test.xml b/.idea/runConfigurations/MySQL_Server_Test.xml new file mode 100644 index 0000000..04f7a1e --- /dev/null +++ b/.idea/runConfigurations/MySQL_Server_Test.xml @@ -0,0 +1,10 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations/PHPUnit.xml b/.idea/runConfigurations/PHPUnit.xml index a5a683e..f81c245 100644 --- a/.idea/runConfigurations/PHPUnit.xml +++ b/.idea/runConfigurations/PHPUnit.xml @@ -1,6 +1,6 @@ - + \ No newline at end of file diff --git a/README.md b/README.md index d29bccf..f834130 100644 --- a/README.md +++ b/README.md @@ -166,6 +166,7 @@ $result = $repository->getByQuery($query); * [Using FieldAlias](docs/using-fieldalias.md) * [Tables without auto increments fields](docs/tables-without-auto-increment-fields.md) * [Using With Recursive SQL Command](docs/using-with-recursive-sql-command.md) +* [QueryRaw (raw SQL)](docs/query-raw.md) ## Install diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..072f693 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,13 @@ +services: + mysql: + image: mysql:8.0.20 + environment: + - MYSQL_ROOT_PASSWORD=password + - MYSQL_AUTHENTICATION_PLUGIN=mysql_native_password + ports: + - "3306:3306" + healthcheck: + test: [ "CMD", "mysqladmin" ,"ping", "-h", "localhost" ] + timeout: 20s + interval: 10s + retries: 10 diff --git a/docs/query-raw.md b/docs/query-raw.md new file mode 100644 index 0000000..9f22ee5 --- /dev/null +++ b/docs/query-raw.md @@ -0,0 +1,83 @@ +# QueryRaw + +QueryRaw lets you execute a raw SQL statement while still benefiting from the repository pipeline (parameter binding, +connection handling, iterators, and optional caching when used via SqlStatement). + +Important: QueryRaw does NOT generate or adapt SQL based on the DbDriver/DB dialect. It passes through exactly the SQL +string you provide. That means the SQL you write must already be valid for the target database engine. Because of this, +QueryRaw should be used only for very specific use cases where the high-level Query/Update/Insert/Delete builders cannot +express what you need. + +When to use QueryRaw + +- Vendor-specific features that are not covered by the query builders (e.g., dialect functions, specialized hints). +- One-off statements where you accept tight coupling to a single database dialect. +- Chaining within bulk operations when you need to run a raw select right after a write. + +When NOT to use QueryRaw + +- Everyday selects/joins/filters/limits that can be expressed with Query or Union. +- Inserts/updates/deletes that can be handled by InsertQuery/UpdateQuery/DeleteQuery. +- Anywhere you want portability across databases or automatic dialect handling. + +Behavior + +- build(): returns a SqlObject with your SQL and parameters unchanged. The optional DbDriver parameter is ignored for + SQL generation. +- buildAndGetIterator($dbDriver): executes your SQL against the provided driver and returns a GenericIterator. +- Parameter binding: pass parameters as an array (named or positional) and the underlying driver will bind them. + +Examples + +1) Basic raw select returning rows as arrays + +```php +use ByJG\MicroOrm\QueryRaw; + +$query = QueryRaw::getInstance( + 'select id, name from users where name like :part', + ['part' => 'A%'] +); + +$rows = $repository->getByQueryRaw($query); // array> +``` + +2) Using dialect-specific functions (DB-specific SQL) + +```php +// Example for SQLite using julianday(); not portable to other databases +$query = QueryRaw::getInstance( + "select name, julianday('2020-06-28') - julianday(createdate) as days from users limit 1" +); + +$rows = $repository->getByQueryRaw($query); +// e.g., [ [ 'name' => 'Jane Doe', 'days' => 1271.0 ] ] +``` + +3) Bulk execution: insert followed by selecting last inserted id + +```php +use ByJG\MicroOrm\InsertQuery; +use ByJG\MicroOrm\QueryRaw; + +$insert = InsertQuery::getInstance('users', [ + 'name' => 'Charlie', + 'createdate' => '2025-01-01', +]); + +// Use DB helper to get the correct SQL for your driver +$selectLastId = QueryRaw::getInstance( + $repository->getDbDriver()->getDbHelper()->getSqlLastInsertId() +); + +$it = $repository->bulkExecute([$insert, $selectLastId]); +$result = $it->toArray(); +// $result is the rows from the last statement (the select) +``` + +Notes + +- QueryRaw ties your code to the specific SQL dialect you write. If you switch databases, you may need to rewrite the + raw SQL. +- Always use bound parameters to avoid SQL injection; pass them in the second argument to getInstance(). +- For portable queries, prefer Query/Union and the provided Insert/Update/Delete builders. diff --git a/docs/updating-the-database.md b/docs/updating-the-database.md index 130e12a..dd9730d 100644 --- a/docs/updating-the-database.md +++ b/docs/updating-the-database.md @@ -136,4 +136,94 @@ You can delete records using the `DeleteQuery` object. See an example: $deleteQuery = new \ByJG\MicroOrm\DeleteQuery(); $deleteQuery->table('test'); $deleteQuery->where('fld1 = :value', ['value' => 'A']); -``` \ No newline at end of file +``` + +## Execute multiple write queries in bulk + +You can execute multiple write queries (insert, update, delete) sequentially within a single transaction using +`Repository::bulkExecute`. +This is useful when you need to perform a set of changes atomically: either all of them succeed, or none of them are +applied. + +Signature: + +```php +public function Repository::bulkExecute( + array $queries, + ?\ByJG\AnyDataset\Db\IsolationLevelEnum $isolationLevel = null +): ?\ByJG\AnyDataset\Core\GenericIterator +``` + +Rules and behavior: + +- Accepts an array of QueryBuilderInterface or Updatable instances (e.g., InsertQuery, UpdateQuery, DeleteQuery). Each + item is built with the repository write driver. Non-SELECT statements are batched and executed together; a trailing + SELECT (if present) is executed separately. +- All queries are executed inside a transaction. If any query throws an exception, the transaction is rolled back and + the exception is rethrown. +- If the last command is a SELECT, bulkExecute returns an iterator (GenericIterator) with its results. Intermediate + SELECT statements are allowed but do not return iterators; their results are not yielded. +- If there is no trailing SELECT, bulkExecute returns an empty iterator. +- Passing an empty array throws InvalidArgumentException. +- Passing an item that is not a QueryBuilderInterface or Updatable throws InvalidArgumentException. +- You can optionally pass a transaction isolation level using IsolationLevelEnum. The transaction allows joining an + existing transaction if present. + +Example (writes only): + +```php + 'Alice', + 'createdate' => '2020-01-01' +]); + +$update = UpdateQuery::getInstance() + ->table('users') + ->set('name', 'Bob') + ->where('id = :id', ['id' => 1]); + +$delete = DeleteQuery::getInstance() + ->table('users') + ->where('name = :name', ['name' => 'OldName']); + +$it = $repository->bulkExecute([$insert, $update, $delete]); +// $it is an empty iterator because there is no trailing SELECT +``` + +Example (returning results when the last command is a SELECT): + +```php + 'Charlie', + 'createdate' => '2025-01-01' +]); + +// Example of a SELECT as the last command (driver-specific last insert id) +$selectLastId = QueryRaw::getInstance($repository->getDbDriver()->getDbHelper()->getSqlLastInsertId()); + +$it = $repository->bulkExecute([$insert, $selectLastId]); +foreach ($it as $row) { + // process results (e.g., $row['id']) +} +``` + +Notes: + +- Parameter names can overlap between queries (e.g., multiple queries using :name). bulkExecute internally batches + non-SELECT statements and safely renames parameters to avoid collisions; the trailing SELECT (if present) is executed + separately. +- If you need a specific transaction isolation level, pass it as the second argument, e.g., + `IsolationLevelEnum::SERIALIZABLE`. \ No newline at end of file diff --git a/src/DeleteQuery.php b/src/DeleteQuery.php index 9a4baa7..e1c548c 100644 --- a/src/DeleteQuery.php +++ b/src/DeleteQuery.php @@ -2,6 +2,7 @@ namespace ByJG\MicroOrm; +use ByJG\AnyDataset\Db\DbDriverInterface; use ByJG\AnyDataset\Db\DbFunctionsInterface; use ByJG\MicroOrm\Exception\InvalidArgumentException; use ByJG\MicroOrm\Interface\QueryBuilderInterface; @@ -13,7 +14,7 @@ public static function getInstance(): DeleteQuery return new DeleteQuery(); } - public function build(DbFunctionsInterface $dbHelper = null): SqlObject + public function build(DbFunctionsInterface|DbDriverInterface|null $dbDriverOrHelper = null): SqlObject { $whereStr = $this->getWhere(); if (is_null($whereStr)) { diff --git a/src/InsertBulkQuery.php b/src/InsertBulkQuery.php index 4f931bf..a41247e 100644 --- a/src/InsertBulkQuery.php +++ b/src/InsertBulkQuery.php @@ -2,6 +2,7 @@ namespace ByJG\MicroOrm; +use ByJG\AnyDataset\Db\DbDriverInterface; use ByJG\AnyDataset\Db\DbFunctionsInterface; use ByJG\MicroOrm\Exception\OrmInvalidFieldsException; use ByJG\MicroOrm\Interface\QueryBuilderInterface; @@ -60,19 +61,23 @@ public function values(array $values, bool $allowNonMatchFields = true): static } /** - * @param DbFunctionsInterface|null $dbHelper + * @param DbDriverInterface|DbFunctionsInterface|null $dbDriverOrHelper * @return SqlObject * @throws OrmInvalidFieldsException */ - public function build(DbFunctionsInterface $dbHelper = null): SqlObject + public function build(DbFunctionsInterface|DbDriverInterface|null $dbDriverOrHelper = null): SqlObject { if (empty($this->fields)) { throw new OrmInvalidFieldsException('You must specify the fields for insert'); } + if ($dbDriverOrHelper instanceof DbDriverInterface) { + $dbDriverOrHelper = $dbDriverOrHelper->getDbHelper(); + } + $tableStr = $this->table; - if (!is_null($dbHelper)) { - $tableStr = $dbHelper->delimiterTable($tableStr); + if (!is_null($dbDriverOrHelper)) { + $tableStr = $dbDriverOrHelper->delimiterTable($tableStr); } // Extract column names @@ -96,7 +101,7 @@ public function build(DbFunctionsInterface $dbHelper = null): SqlObject } else { $value = str_replace("'", "''", $this->fields[$col][$i]); if (!is_numeric($value)) { - $value = $dbHelper?->delimiterField($value) ?? "'{$value}'"; + $value = $dbDriverOrHelper?->delimiterField($value) ?? "'{$value}'"; } $params[$paramKey] = new Literal($value); // Map parameter key to value } @@ -104,8 +109,8 @@ public function build(DbFunctionsInterface $dbHelper = null): SqlObject $placeholders[] = '(' . implode(', ', $rowPlaceholders) . ')'; // Add row placeholders to query } - if (!is_null($dbHelper)) { - $columns = $dbHelper->delimiterField($columns); + if (!is_null($dbDriverOrHelper)) { + $columns = $dbDriverOrHelper->delimiterField($columns); } // Construct the final SQL query diff --git a/src/InsertQuery.php b/src/InsertQuery.php index 908a771..08b305b 100644 --- a/src/InsertQuery.php +++ b/src/InsertQuery.php @@ -2,6 +2,7 @@ namespace ByJG\MicroOrm; +use ByJG\AnyDataset\Db\DbDriverInterface; use ByJG\AnyDataset\Db\DbFunctionsInterface; use ByJG\MicroOrm\Exception\InvalidArgumentException; use ByJG\MicroOrm\Exception\OrmInvalidFieldsException; @@ -65,24 +66,28 @@ public function defineFields(array $fields): static } /** - * @param DbFunctionsInterface|null $dbHelper + * @param DbDriverInterface|DbFunctionsInterface|null $dbDriverOrHelper * @return SqlObject * @throws OrmInvalidFieldsException */ - public function build(DbFunctionsInterface $dbHelper = null): SqlObject + public function build(DbFunctionsInterface|DbDriverInterface|null $dbDriverOrHelper = null): SqlObject { if (empty($this->values)) { throw new OrmInvalidFieldsException('You must specify the fields for insert'); } + if ($dbDriverOrHelper instanceof DbDriverInterface) { + $dbDriverOrHelper = $dbDriverOrHelper->getDbHelper(); + } + $fieldsStr = array_keys($this->values); // get the fields from the first element only - if (!is_null($dbHelper)) { - $fieldsStr = $dbHelper->delimiterField($fieldsStr); + if (!is_null($dbDriverOrHelper)) { + $fieldsStr = $dbDriverOrHelper->delimiterField($fieldsStr); } $tableStr = $this->table; - if (!is_null($dbHelper)) { - $tableStr = $dbHelper->delimiterTable($tableStr); + if (!is_null($dbDriverOrHelper)) { + $tableStr = $dbDriverOrHelper->delimiterTable($tableStr); } $sql = 'INSERT INTO ' diff --git a/src/InsertSelectQuery.php b/src/InsertSelectQuery.php index c5c0fba..b8f3270 100644 --- a/src/InsertSelectQuery.php +++ b/src/InsertSelectQuery.php @@ -2,6 +2,7 @@ namespace ByJG\MicroOrm; +use ByJG\AnyDataset\Db\DbDriverInterface; use ByJG\AnyDataset\Db\DbFunctionsInterface; use ByJG\MicroOrm\Exception\OrmInvalidFieldsException; use ByJG\MicroOrm\Interface\QueryBuilderInterface; @@ -49,16 +50,20 @@ public function fromSqlObject(SqlObject $sqlObject): static } /** - * @param DbFunctionsInterface|null $dbHelper + * @param DbDriverInterface|DbFunctionsInterface|null $dbDriverOrHelper * @return SqlObject * @throws OrmInvalidFieldsException */ - public function build(DbFunctionsInterface $dbHelper = null): SqlObject + public function build(DbFunctionsInterface|DbDriverInterface|null $dbDriverOrHelper = null): SqlObject { if (empty($this->fields)) { throw new OrmInvalidFieldsException('You must specify the fields for insert'); } + if ($dbDriverOrHelper instanceof DbDriverInterface) { + $dbDriverOrHelper = $dbDriverOrHelper->getDbHelper(); + } + if (empty($this->query) && empty($this->sqlObject)) { throw new OrmInvalidFieldsException('You must specify the query for insert'); } elseif (!empty($this->query) && !empty($this->sqlObject)) { @@ -66,13 +71,13 @@ public function build(DbFunctionsInterface $dbHelper = null): SqlObject } $fieldsStr = $this->fields; - if (!is_null($dbHelper)) { - $fieldsStr = $dbHelper->delimiterField($fieldsStr); + if (!is_null($dbDriverOrHelper)) { + $fieldsStr = $dbDriverOrHelper->delimiterField($fieldsStr); } $tableStr = $this->table; - if (!is_null($dbHelper)) { - $tableStr = $dbHelper->delimiterTable($tableStr); + if (!is_null($dbDriverOrHelper)) { + $tableStr = $dbDriverOrHelper->delimiterTable($tableStr); } $sql = 'INSERT INTO ' diff --git a/src/Interface/UpdateBuilderInterface.php b/src/Interface/UpdateBuilderInterface.php index 56c76a8..699abbd 100644 --- a/src/Interface/UpdateBuilderInterface.php +++ b/src/Interface/UpdateBuilderInterface.php @@ -8,7 +8,7 @@ interface UpdateBuilderInterface { - public function build(?DbFunctionsInterface $dbHelper = null): SqlObject; + public function build(DbFunctionsInterface|DbDriverInterface|null $dbDriverOrHelper = null): SqlObject; public function buildAndExecute(DbDriverInterface $dbDriver, $params = [], ?DbFunctionsInterface $dbHelper = null); diff --git a/src/QueryBasic.php b/src/QueryBasic.php index 8c458df..97ff974 100644 --- a/src/QueryBasic.php +++ b/src/QueryBasic.php @@ -272,7 +272,7 @@ public function build(?DbDriverInterface $dbDriver = null): SqlObject $sql .= "SELECT " . ($this->distinct ? "DISTINCT " : "") . $fieldList . - "FROM " . $tableList; + (!empty($tableList) ? "FROM " . $tableList : ""); $whereStr = $this->getWhere(); if (!is_null($whereStr)) { diff --git a/src/QueryRaw.php b/src/QueryRaw.php new file mode 100644 index 0000000..2a3adf8 --- /dev/null +++ b/src/QueryRaw.php @@ -0,0 +1,29 @@ +sql, $this->parameters); + } + + public function buildAndGetIterator(?DbDriverInterface $dbDriver = null, ?CacheQueryResult $cache = null): GenericIterator + { + return $dbDriver->getIterator($this->sql, $this->parameters); + } +} \ No newline at end of file diff --git a/src/Repository.php b/src/Repository.php index 909530b..5f85f5f 100644 --- a/src/Repository.php +++ b/src/Repository.php @@ -3,10 +3,13 @@ namespace ByJG\MicroOrm; use ByJG\AnyDataset\Core\Enum\Relation; +use ByJG\AnyDataset\Core\GenericIterator; use ByJG\AnyDataset\Core\IteratorFilter; use ByJG\AnyDataset\Db\DbDriverInterface; +use ByJG\AnyDataset\Db\IsolationLevelEnum; use ByJG\AnyDataset\Db\IteratorFilterSqlFormatter; use ByJG\AnyDataset\Db\SqlStatement; +use ByJG\AnyDataset\Lists\ArrayDataset; use ByJG\MicroOrm\Exception\InvalidArgumentException; use ByJG\MicroOrm\Exception\OrmBeforeInvalidException; use ByJG\MicroOrm\Exception\OrmInvalidFieldsException; @@ -20,8 +23,10 @@ use ByJG\Serializer\ObjectCopy; use ByJG\Serializer\Serialize; use Closure; +use Exception; use ReflectionException; use stdClass; +use Throwable; class Repository { @@ -188,6 +193,91 @@ public function delete(array|string|int|LiteralInterface $pkId): bool return $this->deleteByQuery($updatable); } + /** + * Execute multiple write queries (insert/update/delete) sequentially within a transaction. + * Invalid entries are ignored silently. If any execution fails, the transaction is rolled back. + * + * @param array $queries List of queries to be executed in bulk + * @param IsolationLevelEnum|null $isolationLevel + * @return GenericIterator|null + * @throws InvalidArgumentException + * @throws RepositoryReadOnlyException + * @throws Throwable + */ + public function bulkExecute(array $queries, ?IsolationLevelEnum $isolationLevel = null): ?GenericIterator + { + if (empty($queries)) { + throw new InvalidArgumentException('You pass an empty array to bulk'); + } + + $dbDriver = $this->getDbDriverWrite(); + + $bigSqlWrites = ''; + $selectSql = null; + $selectParams = []; + $bigParams = []; + + foreach ($queries as $i => $query) { + if (!($query instanceof QueryBuilderInterface) && !($query instanceof Updatable)) { + throw new InvalidArgumentException('Invalid query type. Expected QueryBuilderInterface or Updatable.'); + } + + // Build SQL object using the write driver to ensure correct helper/dialect + $sqlObject = $query->build($dbDriver); + $sql = $sqlObject->getSql(); + $params = $sqlObject->getParameters(); + $isSelect = str_starts_with(strtoupper(ltrim($sql)), 'SELECT'); + + if ($isSelect && $i === array_key_last($queries)) { + // Trailing SELECT: keep it separate with its own params + $selectSql = rtrim($sql, "; \t\n\r\0\x0B"); + $selectParams = $params; + continue; + } + + // For write statements, avoid parameter name collisions by uniquifying named params + foreach ($params as $key => $value) { + // Only process named parameters (string keys) + if (isset($bigParams[$key])) { + $uniqueKey = $key . '__b' . $i; + // Replace ":key" with ":key__b{i}" using a safe regex that avoids partial matches + $pattern = '/(?beginTransaction($isolationLevel, allowJoin: true); + try { + // First execute all writes (if any) in a single batch using direct PDO exec + if (trim($bigSqlWrites) !== '') { + // Use direct PDO to ensure multi-statement execution across drivers like SQLite + $dbDriver->execute($bigSqlWrites, $bigParams); + } + + // If there is a trailing SELECT, fetch it and return its iterator. Otherwise return an empty iterator + if (!empty($selectSql)) { + $it = $dbDriver->getIterator($selectSql, $selectParams); + } else { + $it = (new ArrayDataset([]))->getIterator(); + } + + $dbDriver->commitTransaction(); + + return $it; + } catch (Exception $ex) { + $dbDriver->rollbackTransaction(); + throw $ex; + } + } + /** * @param DeleteQuery $updatable * @return bool @@ -314,14 +404,80 @@ public function getByQueryRaw(QueryBuilderInterface $query): array * @param mixed $instance * @param UpdateConstraint|null $updateConstraint * @return mixed - * @throws Exception\InvalidArgumentException + * @throws InvalidArgumentException * @throws OrmBeforeInvalidException * @throws OrmInvalidFieldsException * @throws RepositoryReadOnlyException * @throws UpdateConstraintException - * @throws \ByJG\Serializer\Exception\InvalidArgumentException */ public function save(mixed $instance, UpdateConstraint $updateConstraint = null): mixed + { + // Build the updatable without executing + [$updatable, $array, $fieldToProperty, $isInsert, $oldInstance, $pkList] = $this->saveUpdatableInternal($instance); + + // Execute the Insert or Update + if ($isInsert) { + $keyGen = $this->getMapper()->generateKey($this->getDbDriver(), $instance) ?? []; + if (!empty($keyGen) && !is_array($keyGen)) { + $keyGen = [$keyGen]; + } + $position = 0; + foreach ($keyGen as $value) { + $array[$pkList[$position]] = $value; + $updatable->set($this->mapper->getPrimaryKey()[$position++], $value); + } + $keyReturned = $this->insert($updatable, $keyGen); + if (count($pkList) == 1 && !empty($keyReturned)) { + $array[$pkList[0]] = $keyReturned; + } + } else { + if (!empty($updateConstraint)) { + $updateConstraint->check($oldInstance, $this->getMapper()->getEntity($array)); + } + $this->update($updatable); + } + + ObjectCopy::copy($array, $instance, function ($sourcePropertyName) use ($fieldToProperty) { + return $fieldToProperty[$sourcePropertyName] ?? $sourcePropertyName; + }); + + ORMSubject::getInstance()->notify( + $this->mapper->getTable(), + $isInsert ? ORMSubject::EVENT_INSERT : ORMSubject::EVENT_UPDATE, + $instance, $oldInstance + ); + + return $instance; + } + + /** + * Build and return the updatable (InsertQuery or UpdateQuery) without executing it. + * This method mirrors the preparatory stage of save() and can be used to inspect or + * bulk-compose updates prior to execution. + * + * @param mixed $instance + * @return Updatable + * @throws InvalidArgumentException + * @throws OrmBeforeInvalidException + * @throws RepositoryReadOnlyException + */ + public function saveUpdatable(mixed $instance): Updatable + { + [$updatable] = $this->saveUpdatableInternal($instance); + return $updatable; + } + + /** + * Internal helper that prepares the updatable and returns additional context + * needed by save(). + * + * @param mixed $instance + * @return array [Updatable $updatable, array $array, array $fieldToProperty, bool $isInsert, mixed $oldInstance, array $pkList] + * @throws InvalidArgumentException + * @throws OrmBeforeInvalidException + * @throws RepositoryReadOnlyException + */ + protected function saveUpdatableInternal(mixed $instance): array { // Get all fields $array = Serialize::from($instance) @@ -358,9 +514,11 @@ function ($propName, $targetName, $value) use ($mapper, $instance) { } } else { $fields = array_map(function ($item) use ($array) { - return $array[$item]; + return $array[$item] ?? null; }, $pkList); - $oldInstance = $this->get($fields); + if (!in_array(null, $fields, true)) { + $oldInstance = $this->get($fields); + } } $isInsert = empty($oldInstance); @@ -368,10 +526,10 @@ function ($propName, $targetName, $value) use ($mapper, $instance) { if ($isInsert) { $closure = $this->beforeInsert; $array = $closure($array); - foreach ($this->getMapper()->getFieldMap() as $mapper) { - $fieldValue = $mapper->getInsertFunctionValue($array[$mapper->getFieldName()] ?? null, $instance, $this->getDbDriverWrite()->getDbHelper()); + foreach ($this->getMapper()->getFieldMap() as $mapItem) { + $fieldValue = $mapItem->getInsertFunctionValue($array[$mapItem->getFieldName()] ?? null, $instance, $this->getDbDriverWrite()->getDbHelper()); if ($fieldValue !== false) { - $array[$mapper->getFieldName()] = $fieldValue; + $array[$mapItem->getFieldName()] = $fieldValue; } } $updatable = InsertQuery::getInstance($this->mapper->getTable(), $array); @@ -386,42 +544,9 @@ function ($propName, $targetName, $value) use ($mapper, $instance) { throw new OrmBeforeInvalidException('Invalid Before Insert Closure'); } - // Execute the Insert or Update - if ($isInsert) { - $keyGen = $this->getMapper()->generateKey($this->getDbDriver(), $instance) ?? []; - if (!empty($keyGen) && !is_array($keyGen)) { - $keyGen = [$keyGen]; - } - $position = 0; - foreach ($keyGen as $value) { - $array[$pkList[$position]] = $value; - $updatable->set($this->mapper->getPrimaryKey()[$position++], $value); - } - $keyReturned = $this->insert($updatable, $keyGen); - if (count($pkList) == 1 && !empty($keyReturned)) { - $array[$pkList[0]] = $keyReturned; - } - } else { - if (!empty($updateConstraint)) { - $updateConstraint->check($oldInstance, $this->getMapper()->getEntity($array)); - } - $this->update($updatable); - } - - ObjectCopy::copy($array, $instance, function ($sourcePropertyName) use ($fieldToProperty) { - return $fieldToProperty[$sourcePropertyName] ?? $sourcePropertyName; - }); - - ORMSubject::getInstance()->notify( - $this->mapper->getTable(), - $isInsert ? ORMSubject::EVENT_INSERT : ORMSubject::EVENT_UPDATE, - $instance, $oldInstance - ); - - return $instance; + return [$updatable, $array, $fieldToProperty, $isInsert, $oldInstance, $pkList]; } - /** * @throws InvalidArgumentException */ @@ -440,7 +565,7 @@ public function addObserver(ObserverProcessorInterface $observerProcessor): void protected function insert(InsertQuery $updatable, mixed $keyGen): mixed { if (empty($keyGen)) { - return $this->insertWithAutoInc($updatable); + return $this->insertWithAutoinc($updatable); } else { $this->insertWithKeyGen($updatable); return null; diff --git a/src/UpdateQuery.php b/src/UpdateQuery.php index 24f0a0e..d68172f 100644 --- a/src/UpdateQuery.php +++ b/src/UpdateQuery.php @@ -2,7 +2,9 @@ namespace ByJG\MicroOrm; +use ByJG\AnyDataset\Db\DbDriverInterface; use ByJG\AnyDataset\Db\DbFunctionsInterface; +use ByJG\AnyDataset\Db\SqlStatement; use ByJG\MicroOrm\Exception\InvalidArgumentException; use ByJG\MicroOrm\Interface\QueryBuilderInterface; use ByJG\MicroOrm\Literal\Literal; @@ -65,34 +67,58 @@ public function setLiteral(string $field, mixed $value): UpdateQuery return $this; } - protected function getJoinTables(DbFunctionsInterface $dbHelper = null): array + protected function getJoinTables(DbFunctionsInterface|DbDriverInterface $dbDriverOrHelper = null): array { + $dbDriver = null; + if ($dbDriverOrHelper instanceof DbDriverInterface) { + $dbDriver = $dbDriverOrHelper; + $dbHelper = $dbDriverOrHelper->getDbHelper(); + } else { + $dbHelper = $dbDriverOrHelper; + } + if (is_null($dbHelper)) { if (!empty($this->joinTables)) { throw new InvalidArgumentException('You must specify a DbFunctionsInterface to use join tables'); } return ['sql' => '', 'position' => 'before_set']; } + foreach ($this->joinTables as $key => $joinTable) { + if ($this->joinTables[$key]['table'] instanceof QueryBasic) { + if (is_null($dbDriver)) { + Throw new InvalidArgumentException('You must specify a DbDriverInterface to use Query join tables'); + } + $this->joinTables[$key]['table'] = new SqlStatement($this->joinTables[$key]['table']->build($dbDriver)->getSql()); + } + } return $dbHelper->getJoinTablesUpdate($this->joinTables); } - public function join(string $table, string $joinCondition): UpdateQuery + public function join(QueryBasic|string $table, string $joinCondition, ?string $alias = null): UpdateQuery { - $this->joinTables[] = ["table" => $table, "condition" => $joinCondition]; + $this->joinTables[] = ["table" => $table, "condition" => $joinCondition, "alias" => $alias]; return $this; } /** - * @param DbFunctionsInterface|null $dbHelper + * @param DbDriverInterface|DbFunctionsInterface|null $dbDriverOrHelper * @return SqlObject * @throws InvalidArgumentException */ - public function build(DbFunctionsInterface $dbHelper = null): SqlObject + public function build(DbFunctionsInterface|DbDriverInterface|null $dbDriverOrHelper = null): SqlObject { if (empty($this->set)) { throw new InvalidArgumentException('You must specify the fields for update'); } + + $dbDriver = null; + if ($dbDriverOrHelper instanceof DbDriverInterface) { + $dbHelper = $dbDriverOrHelper->getDbHelper(); + $dbDriver = $dbDriverOrHelper; + } else { + $dbHelper = $dbDriverOrHelper; + } $fieldsStr = []; $params = []; @@ -120,7 +146,7 @@ public function build(DbFunctionsInterface $dbHelper = null): SqlObject $tableName = $dbHelper->delimiterTable($tableName); } - $joinTables = $this->getJoinTables($dbHelper); + $joinTables = $this->getJoinTables($dbDriverOrHelper); $joinBeforeSet = $joinTables['position'] === 'before_set' ? $joinTables['sql'] : ''; $joinAfterSet = $joinTables['position'] === 'after_set' ? $joinTables['sql'] : ''; diff --git a/tests/BulkTest.php b/tests/BulkTest.php new file mode 100644 index 0000000..4b838a9 --- /dev/null +++ b/tests/BulkTest.php @@ -0,0 +1,187 @@ +dbDriver = ConnectionUtil::getConnection('testmicroorm'); + + // Create table and seed data similar to RepositoryTest + $this->dbDriver->execute('create table users ( + id integer primary key auto_increment, + name varchar(45), + createdate datetime);' + ); + $insertBulk = InsertBulkQuery::getInstance('users', ['name', 'createdate']); + $insertBulk->values(['name' => 'John Doe', 'createdate' => '2017-01-02']); + $insertBulk->values(['name' => 'Jane Doe', 'createdate' => '2017-01-04']); + $insertBulk->values(['name' => 'JG', 'createdate' => '1974-01-26']); + $insertBulk->buildAndExecute($this->dbDriver); + + $mapper = new Mapper(Users::class, 'users', 'Id'); + $this->repository = new Repository($this->dbDriver, $mapper); + } + + protected function tearDown(): void + { + $this->dbDriver->execute('drop table users'); + } + + public function testBulkMixedQueriesWithParamCollision(): void + { + // Sanity check: count, specific rows + // 3 initial rows + $count = Query::getInstance()->fields(['count(*) as cnt'])->table('users'); + $res = $this->repository->getByQueryRaw($count); + $this->assertEquals(3, (int)$res[0]['cnt']); + + // id=1 should have name 'John Doe' + $row = $this->repository->get(1); + $this->assertEquals('John Doe', $row->getName()); + + // The 'Alice' should not exist + $rows = $this->repository->getByFilter('name = :name', ['name' => 'Alice']); + $this->assertCount(0, $rows); + + // The 'JG' should exist before bulk + $rows = $this->repository->getByFilter('name = :name', ['name' => 'JG']); + $this->assertCount(1, $rows); + + // Prepare queries with overlapping parameter names (:name used in multiple queries) + $insert = InsertQuery::getInstance('users', [ + 'name' => 'Alice', + 'createdate' => '2020-01-01' + ]); + + $update = UpdateQuery::getInstance() + ->table('users') + ->set('name', 'Bob') + ->where('id = :id', ['id' => 1]); + + $delete = DeleteQuery::getInstance() + ->table('users') + ->where('name = :name', ['name' => 'JG']); + + // Execute bulk + $it = $this->repository->bulkExecute([$insert, $update, $delete], null); + $this->assertEquals([], $it->toArray()); + + // Validate results: count, specific rows + // 3 initial + 1 insert - 1 delete = 3 rows + $count = Query::getInstance()->fields(['count(*) as cnt'])->table('users'); + $res = $this->repository->getByQueryRaw($count); + $this->assertEquals(3, (int)$res[0]['cnt']); + + // id=1 should now have name 'Bob' + $row = $this->repository->get(1); + $this->assertEquals('Bob', $row->getName()); + + // The newly inserted 'Alice' should exist + $rows = $this->repository->getByFilter('name = :name', ['name' => 'Alice']); + $this->assertCount(1, $rows); + + // The deleted 'JG' should be gone + $rows = $this->repository->getByFilter('name = :name', ['name' => 'JG']); + $this->assertCount(0, $rows); + } + + public function testBulkEmptyArray(): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('You pass an empty array to bulk'); + + // Should not throw and not change anything + $this->repository->bulkExecute([], null); + } + + public function testBulkMixedQueriesWithInvalidQuery(): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Invalid query type. Expected QueryBuilderInterface or Updatable.'); + + // Prepare queries with overlapping parameter names (:name used in multiple queries) + $insert = InsertQuery::getInstance('users', [ + 'name' => 'Alice', + 'createdate' => '2020-01-01' + ]); + + $update = UpdateQuery::getInstance() + ->table('users') + ->set('name', 'Bob') + ->where('id = :id', ['id' => 1]); + + $delete = DeleteQuery::getInstance() + ->table('users') + ->where('name = :name', ['name' => 'JG']); + + $invalid = "invalid"; + + // Execute bulk + /** @psalm-suppress InvalidArgument */ + $this->repository->bulkExecute([$insert, $invalid, $update, $delete], null); + } + + public function testBulkInsertAndSelectLastInsertedId(): void + { + // initial rows are 3; next autoincrement id should be 4 after insert + $insert = InsertQuery::getInstance('users', [ + 'name' => 'Charlie', + 'createdate' => '2025-01-01' + ]); + + // Outer query selects last_insert_rowid() from the single-row subquery + $selectLastId = QueryRaw::getInstance($this->repository->getDbDriver()->getDbHelper()->getSqlLastInsertId()); + + $it = $this->repository->bulkExecute([$insert, $selectLastId], null); + $result = $it->toArray(); + + $this->assertCount(1, $result); + // SQLite returns integer for last_insert_rowid(); expect 4 here + $this->assertEquals(4, (int)$result[0]['id']); + + // Also ensure the inserted row exists + $rows = $this->repository->getByFilter('name = :name', ['name' => 'Charlie']); + $this->assertCount(1, $rows); + $this->assertEquals('Charlie', $rows[0]->getName()); + } + + public function testBulkInsertNoParams(): void + { + // initial rows are 3; next autoincrement id should be 4 after insert + $insert = QueryRaw::getInstance("insert into users (name, createdate) values ('Charlie', '2025-01-01')"); + + // Outer query selects last_insert_rowid() from the single-row subquery + $selectLastId = QueryRaw::getInstance($this->repository->getDbDriver()->getDbHelper()->getSqlLastInsertId()); + + $it = $this->repository->bulkExecute([$insert, $selectLastId], null); + $result = $it->toArray(); + + $this->assertCount(1, $result); + // SQLite returns integer for last_insert_rowid(); expect 4 here + $this->assertEquals(4, (int)$result[0]['id']); + + // Also ensure the inserted row exists + $rows = $this->repository->getByFilter('name = :name', ['name' => 'Charlie']); + $this->assertCount(1, $rows); + $this->assertEquals('Charlie', $rows[0]->getName()); + } + +} diff --git a/tests/ConnectionUtil.php b/tests/ConnectionUtil.php new file mode 100644 index 0000000..08847de --- /dev/null +++ b/tests/ConnectionUtil.php @@ -0,0 +1,29 @@ +execute("create database if not exists $database;"); + return Factory::getDbInstance(ConnectionUtil::getUri($database)); + } + + public static function getUri(?string $database = null): Uri + { + $host = getenv('MYSQL_TEST_HOST') ? getenv('MYSQL_TEST_HOST') : '127.0.0.1'; + $uri = new Uri("mysql://root:password@$host"); + + if (empty($database)) { + return $uri; + } + + return $uri->withPath("/$database"); + } +} \ No newline at end of file diff --git a/tests/Model/UsersWithUuidKey.php b/tests/Model/UsersWithUuidKey.php index 1ac1911..76097c5 100644 --- a/tests/Model/UsersWithUuidKey.php +++ b/tests/Model/UsersWithUuidKey.php @@ -3,10 +3,10 @@ namespace Tests\Model; use ByJG\MicroOrm\Attributes\FieldAttribute; -use ByJG\MicroOrm\Attributes\TableSqliteUuidPKAttribute; +use ByJG\MicroOrm\Attributes\TableMySqlUuidPKAttribute; use ByJG\MicroOrm\Literal\Literal; -#[TableSqliteUuidPKAttribute(tableName: 'usersuuid')] +#[TableMySqlUuidPKAttribute(tableName: 'usersuuid')] class UsersWithUuidKey { #[FieldAttribute(primaryKey: true)] diff --git a/tests/RepositoryAliasTest.php b/tests/RepositoryAliasTest.php index 5fab58c..6b5552c 100644 --- a/tests/RepositoryAliasTest.php +++ b/tests/RepositoryAliasTest.php @@ -3,20 +3,15 @@ namespace Tests; use ByJG\AnyDataset\Db\DbDriverInterface; -use ByJG\AnyDataset\Db\Factory; use ByJG\MicroOrm\FieldMapping; use ByJG\MicroOrm\Mapper; use ByJG\MicroOrm\Query; use ByJG\MicroOrm\Repository; -use ByJG\Util\Uri; use PHPUnit\Framework\TestCase; use Tests\Model\Customer; class RepositoryAliasTest extends TestCase { - - const URI='sqlite:///tmp/teste.db'; - /** * @var Mapper */ @@ -34,10 +29,10 @@ class RepositoryAliasTest extends TestCase public function setUp(): void { - $this->dbDriver = Factory::getDbInstance(self::URI); + $this->dbDriver = ConnectionUtil::getConnection("testmicroorm"); $this->dbDriver->execute('create table customers ( - id integer primary key autoincrement, + id integer primary key auto_increment, customer_name varchar(45), customer_age int);' ); @@ -58,8 +53,7 @@ public function setUp(): void public function tearDown(): void { - $uri = new Uri(self::URI); - unlink($uri->getPath()); + $this->dbDriver->execute('drop table if exists customers;'); } public function testGet() diff --git a/tests/RepositoryPkListTest.php b/tests/RepositoryPkListTest.php index 9a3410d..40f5e11 100644 --- a/tests/RepositoryPkListTest.php +++ b/tests/RepositoryPkListTest.php @@ -3,18 +3,13 @@ namespace Tests; use ByJG\AnyDataset\Db\DbDriverInterface; -use ByJG\AnyDataset\Db\Factory; use ByJG\MicroOrm\Mapper; use ByJG\MicroOrm\Repository; -use ByJG\Util\Uri; use PHPUnit\Framework\TestCase; use Tests\Model\Items; class RepositoryPkListTest extends TestCase { - - const URI='sqlite:///tmp/teste.db'; - /** * @var Mapper */ @@ -32,7 +27,7 @@ class RepositoryPkListTest extends TestCase public function setUp(): void { - $this->dbDriver = Factory::getDbInstance(self::URI); + $this->dbDriver = ConnectionUtil::getConnection("testmicroorm"); $this->dbDriver->execute('CREATE TABLE items ( storeid INTEGER, @@ -52,8 +47,7 @@ public function setUp(): void public function tearDown(): void { - $uri = new Uri(self::URI); - unlink($uri->getPath()); + $this->dbDriver->execute('drop table if exists items;'); } public function testGet() diff --git a/tests/RepositoryTest.php b/tests/RepositoryTest.php index 627d825..3fb912c 100644 --- a/tests/RepositoryTest.php +++ b/tests/RepositoryTest.php @@ -5,7 +5,6 @@ use ByJG\AnyDataset\Core\Enum\Relation; use ByJG\AnyDataset\Core\IteratorFilter; use ByJG\AnyDataset\Db\DbDriverInterface; -use ByJG\AnyDataset\Db\Factory; use ByJG\Cache\Psr16\ArrayCacheEngine; use ByJG\MicroOrm\CacheQueryResult; use ByJG\MicroOrm\DeleteQuery; @@ -30,7 +29,6 @@ use ByJG\MicroOrm\Union; use ByJG\MicroOrm\UpdateConstraint; use ByJG\MicroOrm\UpdateQuery; -use ByJG\Util\Uri; use DateTime; use Exception; use PHPUnit\Framework\ExpectationFailedException; @@ -45,9 +43,6 @@ class RepositoryTest extends TestCase { - - const URI='sqlite:///tmp/test.db'; - /** * @var Mapper */ @@ -70,10 +65,10 @@ class RepositoryTest extends TestCase public function setUp(): void { - $this->dbDriver = Factory::getDbInstance(self::URI); + $this->dbDriver = ConnectionUtil::getConnection("testmicroorm"); $this->dbDriver->execute('create table users ( - id integer primary key autoincrement, + id integer primary key auto_increment, name varchar(45), createdate datetime);' ); @@ -86,11 +81,11 @@ public function setUp(): void $this->dbDriver->execute('create table info ( - id integer primary key autoincrement, + id integer primary key auto_increment, iduser INTEGER, - property number(10,2), - created_at datetime, - updated_at datetime, + property decimal(10, 2), + created_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, deleted_at datetime);' ); $insertMultiple = InsertBulkQuery::getInstance('info', ['iduser', 'property']); @@ -107,8 +102,8 @@ public function setUp(): void public function tearDown(): void { - $uri = new Uri(self::URI); - unlink($uri->getPath()); + $this->dbDriver->execute('drop table if exists users;'); + $this->dbDriver->execute('drop table if exists info;'); ORM::clearRelationships(); } @@ -117,17 +112,17 @@ public function testGet() $users = $this->repository->get(1); $this->assertEquals(1, $users->getId()); $this->assertEquals('John Doe', $users->getName()); - $this->assertEquals('2017-01-02', $users->getCreatedate()); + $this->assertEquals('2017-01-02 00:00:00', $users->getCreatedate()); $users = $this->repository->get("2"); $this->assertEquals(2, $users->getId()); $this->assertEquals('Jane Doe', $users->getName()); - $this->assertEquals('2017-01-04', $users->getCreatedate()); + $this->assertEquals('2017-01-04 00:00:00', $users->getCreatedate()); $users = $this->repository->get(new Literal(1)); $this->assertEquals(1, $users->getId()); $this->assertEquals('John Doe', $users->getName()); - $this->assertEquals('2017-01-02', $users->getCreatedate()); + $this->assertEquals('2017-01-02 00:00:00', $users->getCreatedate()); } public function testGetByFilter() @@ -136,7 +131,7 @@ public function testGetByFilter() $this->assertCount(1, $users); $this->assertEquals(1, $users[0]->getId()); $this->assertEquals('John Doe', $users[0]->getName()); - $this->assertEquals('2017-01-02', $users[0]->getCreatedate()); + $this->assertEquals('2017-01-02 00:00:00', $users[0]->getCreatedate()); $filter = new IteratorFilter(); $filter->and('id', Relation::EQUAL, 2); @@ -144,7 +139,7 @@ public function testGetByFilter() $this->assertCount(1, $users); $this->assertEquals(2, $users[0]->getId()); $this->assertEquals('Jane Doe', $users[0]->getName()); - $this->assertEquals('2017-01-04', $users[0]->getCreatedate()); + $this->assertEquals('2017-01-04 00:00:00', $users[0]->getCreatedate()); } public function testGetSelectFunction() @@ -168,14 +163,14 @@ public function testGetSelectFunction() $users = $this->repository->get(1); $this->assertEquals(1, $users->getId()); - $this->assertEquals('[JOHN DOE] - 2017-01-02', $users->getName()); - $this->assertEquals('2017-01-02', $users->getCreatedate()); + $this->assertEquals('[JOHN DOE] - 2017-01-02 00:00:00', $users->getName()); + $this->assertEquals('2017-01-02 00:00:00', $users->getCreatedate()); $this->assertEquals(2017, $users->getYear()); $users = $this->repository->get(2); $this->assertEquals(2, $users->getId()); - $this->assertEquals('[JANE DOE] - 2017-01-04', $users->getName()); - $this->assertEquals('2017-01-04', $users->getCreatedate()); + $this->assertEquals('[JANE DOE] - 2017-01-04 00:00:00', $users->getName()); + $this->assertEquals('2017-01-04 00:00:00', $users->getCreatedate()); $this->assertEquals(2017, $users->getYear()); } @@ -237,7 +232,7 @@ public function testInsert() $this->assertEquals(4, $users2->getId()); $this->assertEquals('Bla99991919', $users2->getName()); - $this->assertEquals('2015-08-09', $users2->getCreatedate()); + $this->assertEquals('2015-08-09 00:00:00', $users2->getCreatedate()); } public function testInsertReadOnly() @@ -272,7 +267,7 @@ public function testInsert_beforeInsert() $this->assertEquals(4, $users2->getId()); $this->assertEquals('Bla-add', $users2->getName()); - $this->assertEquals('2017-12-21', $users2->getCreatedate()); + $this->assertEquals('2017-12-21 00:00:00', $users2->getCreatedate()); } public function testInsertLiteral() @@ -280,7 +275,7 @@ public function testInsertLiteral() /** @var Users $users */ $users = $this->repository->entity([ "name" => new Literal("X'6565'"), - "createdate" => '2015-08-09' + "createdate" => '2015-08-09 12:13:14' ]); $this->assertEquals(null, $users->getId()); @@ -290,7 +285,7 @@ public function testInsertLiteral() $this->assertEquals(4, $users2->getId()); $this->assertEquals('ee', $users2->getName()); - $this->assertEquals('2015-08-09', $users2->getCreatedate()); + $this->assertEquals('2015-08-09 12:13:14', $users2->getCreatedate()); $this->assertEquals($users2->getId(), $users->getId()); $this->assertEquals($users2->getCreatedate(), $users->getCreatedate()); @@ -319,7 +314,7 @@ public function testInsertFromObject() $this->assertEquals(4, $users2->getId()); $this->assertEquals('ee', $users2->getName()); - $this->assertEquals('2015-08-09', $users2->getCreatedate()); + $this->assertEquals('2015-08-09 00:00:00', $users2->getCreatedate()); } public function testInsertKeyGen() @@ -345,7 +340,7 @@ public function testInsertKeyGen() $this->assertEquals(50, $users2->getId()); $this->assertEquals('Bla99991919', $users2->getName()); - $this->assertEquals('2015-08-09', $users2->getCreatedate()); + $this->assertEquals('2015-08-09 00:00:00', $users2->getCreatedate()); } public function testInsertUpdateFunction() @@ -379,7 +374,7 @@ public function testInsertUpdateFunction() $users2 = $this->repository->get(4); $this->assertEquals(4, $users2->getId()); - $this->assertEquals('2015-08-09', $users2->getCreatedate()); + $this->assertEquals('2015-08-09 00:00:00', $users2->getCreatedate()); $this->assertEquals(2015, $users2->getYear()); $this->assertEquals('Sr. John Doe - 2015-08-09', $users2->getName()); } @@ -395,12 +390,12 @@ public function testUpdate() $users2 = $this->repository->get(1); $this->assertEquals(1, $users2->getId()); $this->assertEquals('New Name', $users2->getName()); - $this->assertEquals('2016-01-09', $users2->getCreatedate()); + $this->assertEquals('2016-01-09 00:00:00', $users2->getCreatedate()); $users2 = $this->repository->get(2); $this->assertEquals(2, $users2->getId()); $this->assertEquals('Jane Doe', $users2->getName()); - $this->assertEquals('2017-01-04', $users2->getCreatedate()); + $this->assertEquals('2017-01-04 00:00:00', $users2->getCreatedate()); } public function testUpdateReadOnly() @@ -446,7 +441,7 @@ public function testInsertBuildAndExecute() $users = $this->repository->get(4); $this->assertEquals('inserted name', $users->getName()); - $this->assertEquals('2024-09-03', $users->getCreatedate()); + $this->assertEquals('2024-09-03 00:00:00', $users->getCreatedate()); } public function testInsertBuildAndExecute2() @@ -463,7 +458,7 @@ public function testInsertBuildAndExecute2() $users = $this->repository->get(4); $this->assertEquals('inserted name', $users->getName()); - $this->assertEquals('2024-09-03', $users->getCreatedate()); + $this->assertEquals('2024-09-03 00:00:00', $users->getCreatedate()); } public function testDeleteBuildAndExecute() @@ -497,12 +492,12 @@ public function testUpdate_beforeUpdate() $users2 = $this->repository->get(1); $this->assertEquals(1, $users2->getId()); $this->assertEquals('New Name-upd', $users2->getName()); - $this->assertEquals('2017-12-21', $users2->getCreatedate()); + $this->assertEquals('2017-12-21 00:00:00', $users2->getCreatedate()); $users2 = $this->repository->get(2); $this->assertEquals(2, $users2->getId()); $this->assertEquals('Jane Doe', $users2->getName()); - $this->assertEquals('2017-01-04', $users2->getCreatedate()); + $this->assertEquals('2017-01-04 00:00:00', $users2->getCreatedate()); } public function testUpdateLiteral() @@ -515,7 +510,7 @@ public function testUpdateLiteral() $this->assertEquals(1, $users2->getId()); $this->assertEquals('ee', $users2->getName()); - $this->assertEquals('2017-01-02', $users2->getCreatedate()); + $this->assertEquals('2017-01-02 00:00:00', $users2->getCreatedate()); } public function testUpdateObject() @@ -541,7 +536,7 @@ public function testUpdateObject() $this->assertEquals(1, $users2->getId()); $this->assertEquals('ee', $users2->getName()); - $this->assertEquals('2020-01-02', $users2->getCreatedate()); + $this->assertEquals('2020-01-02 00:00:00', $users2->getCreatedate()); } @@ -573,13 +568,13 @@ public function testUpdateFunction() $users2 = $this->repository->get(1); $this->assertEquals(1, $users2->getId()); $this->assertEquals('Sr. New Name', $users2->getName()); - $this->assertEquals('2016-01-09', $users2->getCreatedate()); + $this->assertEquals('2016-01-09 00:00:00', $users2->getCreatedate()); $this->assertEquals(2016, $users2->getYear()); $users2 = $this->repository->get(2); $this->assertEquals(2, $users2->getId()); $this->assertEquals('Jane Doe', $users2->getName()); - $this->assertEquals('2017-01-04', $users2->getCreatedate()); + $this->assertEquals('2017-01-04 00:00:00', $users2->getCreatedate()); $this->assertEquals(2017, $users2->getYear()); } @@ -592,7 +587,7 @@ public function testDelete() $users = $this->repository->get(2); $this->assertEquals(2, $users->getId()); $this->assertEquals('Jane Doe', $users->getName()); - $this->assertEquals('2017-01-04', $users->getCreatedate()); + $this->assertEquals('2017-01-04 00:00:00', $users->getCreatedate()); } public function testDeleteReadOnly() @@ -610,7 +605,7 @@ public function testDeleteLiteral() $users = $this->repository->get(2); $this->assertEquals(2, $users->getId()); $this->assertEquals('Jane Doe', $users->getName()); - $this->assertEquals('2017-01-04', $users->getCreatedate()); + $this->assertEquals('2017-01-04 00:00:00', $users->getCreatedate()); } public function testDelete2() @@ -624,7 +619,7 @@ public function testDelete2() $users = $this->repository->get(1); $this->assertEquals(1, $users->getId()); $this->assertEquals('John Doe', $users->getName()); - $this->assertEquals('2017-01-02', $users->getCreatedate()); + $this->assertEquals('2017-01-02 00:00:00', $users->getCreatedate()); $users = $this->repository->get(2); $this->assertEmpty($users); @@ -662,7 +657,7 @@ public function testGetByQueryOne() $infoRepository->save($result[0]); $result = $infoRepository->getByQuery($query); - $this->assertSame('0', (string)$result[0]->getValue()); + $this->assertSame('0.00', (string)$result[0]->getValue()); // Set Null $result[0]->setValue(null); @@ -687,7 +682,7 @@ public function testFilterInOne() $this->assertEquals(2, $result[0]->getId()); $this->assertEquals('Jane Doe', $result[0]->getName()); - $this->assertEquals('2017-01-04', $result[0]->getCreatedate()); + $this->assertEquals('2017-01-04 00:00:00', $result[0]->getCreatedate()); } public function testFilterInTwo() @@ -698,11 +693,11 @@ public function testFilterInTwo() $this->assertEquals(2, $result[0]->getId()); $this->assertEquals('Jane Doe', $result[0]->getName()); - $this->assertEquals('2017-01-04', $result[0]->getCreatedate()); + $this->assertEquals('2017-01-04 00:00:00', $result[0]->getCreatedate()); $this->assertEquals(3, $result[1]->getId()); $this->assertEquals('JG', $result[1]->getName()); - $this->assertEquals('1974-01-26', $result[1]->getCreatedate()); + $this->assertEquals('1974-01-26 00:00:00', $result[1]->getCreatedate()); } /** @@ -755,11 +750,11 @@ public function testJoin() $this->assertEquals(1, $result[0][0]->getId()); $this->assertEquals('John Doe', $result[0][0]->getName()); - $this->assertEquals('2017-01-02', $result[0][0]->getCreatedate()); + $this->assertEquals('2017-01-02 00:00:00', $result[0][0]->getCreatedate()); $this->assertEquals(1, $result[1][0]->getId()); $this->assertEquals('John Doe', $result[1][0]->getName()); - $this->assertEquals('2017-01-02', $result[1][0]->getCreatedate()); + $this->assertEquals('2017-01-02 00:00:00', $result[1][0]->getCreatedate()); // - ------------------ @@ -788,7 +783,7 @@ public function testLeftJoin() $this->assertEquals(2, $result[0][0]->getId()); $this->assertEquals('Jane Doe', $result[0][0]->getName()); - $this->assertEquals('2017-01-04', $result[0][0]->getCreatedate()); + $this->assertEquals('2017-01-04 00:00:00', $result[0][0]->getCreatedate()); // - ------------------ @@ -805,7 +800,7 @@ public function testTop() $this->assertEquals(1, $result[0]->getId()); $this->assertEquals('John Doe', $result[0]->getName()); - $this->assertEquals('2017-01-02', $result[0]->getCreatedate()); + $this->assertEquals('2017-01-02 00:00:00', $result[0]->getCreatedate()); $this->assertEquals(1, count($result)); } @@ -819,7 +814,7 @@ public function testLimit() $this->assertEquals(2, $result[0]->getId()); $this->assertEquals('Jane Doe', $result[0]->getName()); - $this->assertEquals('2017-01-04', $result[0]->getCreatedate()); + $this->assertEquals('2017-01-04 00:00:00', $result[0]->getCreatedate()); $this->assertEquals(1, count($result)); } @@ -829,7 +824,7 @@ public function testQueryRaw() $query = $this->repository->queryInstance() ->fields([ "name", - "julianday('2020-06-28') - julianday(createdate) as days" + "DATEDIFF('2020-06-28', createdate) AS days" ]) ->limit(1, 1); @@ -1234,15 +1229,17 @@ public function testMappingAttribute() $this->assertEquals(3, $result[0]->getPk()); $this->assertEquals(3, $result[0]->iduser); $this->assertEquals(3.5, $result[0]->value); - $this->assertNull($result[0]->getCreatedAt()); // Because it was not set in the initial insert outside the ORM - $this->assertNull($result[0]->getUpdatedAt()); // Because it was not set in the initial insert outside the ORM - $this->assertNull($result[0]->getDeletedAt()); // Because it was not set in the initial insert outside the ORM + $this->assertNotNull($result[0]->getCreatedAt()); + $this->assertNotNull($result[0]->getUpdatedAt()); + $this->assertNull($result[0]->getDeletedAt()); + $this->assertEquals($result[0]->getCreatedAt(), $result[0]->getUpdatedAt()); } public function testMappingAttributeInsert() { $infoRepository = new Repository($this->dbDriver, ModelWithAttributes::class); + // Sanity check $query = new Query(); $query->table($this->infoMapper->getTable()) ->where('iduser = :id', ['id' => 123]) @@ -1250,11 +1247,13 @@ public function testMappingAttributeInsert() $result = $infoRepository->getByQuery($query); $this->assertEmpty($result); + // Add a new record $info = new ModelWithAttributes(); $info->iduser = 123; $info->value = 98.5; $infoRepository->save($info); + // Get the Record and assert the values /** @var ModelWithAttributes[] $result */ $result = $infoRepository->getByQuery($query); $this->assertEquals(count($result), 1); @@ -1266,10 +1265,12 @@ public function testMappingAttributeInsert() $this->assertNotNull($result[0]->getUpdatedAt()); $this->assertNull($result[0]->getDeletedAt()); - // Check if the updated_at works + // save the record again and assert the values sleep(1); $info->value = 99.5; $infoRepository->save($info); + + // Get directly from the databae /** @var ModelWithAttributes[] $result2 */ $result2 = $infoRepository->getByQuery($query); $this->assertEquals(count($result2), 1); @@ -1296,14 +1297,19 @@ public function testMappingAttributeSoftDeleteAndGetByQuery() $this->assertEquals(3, $result[0]->getPk()); $this->assertEquals(3, $result[0]->iduser); $this->assertEquals(3.5, $result[0]->value); - $this->assertNull($result[0]->getCreatedAt()); - $this->assertNull($result[0]->getUpdatedAt()); + $this->assertNotNull($result[0]->getCreatedAt()); + $this->assertNotNull($result[0]->getUpdatedAt()); $this->assertNull($result[0]->getDeletedAt()); $infoRepository->delete(3); $result = $infoRepository->getByQuery($query); $this->assertCount(0, $result); + + $query->unsafe(); + $result = $infoRepository->getByQuery($query); + $this->assertCount(1, $result); + $this->assertNotNull($result[0]->getDeletedAt()); } public function testMappingAttributeSoftDeleteAndGetByFilter() @@ -1317,8 +1323,8 @@ public function testMappingAttributeSoftDeleteAndGetByFilter() $this->assertEquals(3, $result[0]->getPk()); $this->assertEquals(3, $result[0]->iduser); $this->assertEquals(3.5, $result[0]->value); - $this->assertNull($result[0]->getCreatedAt()); - $this->assertNull($result[0]->getUpdatedAt()); + $this->assertNotNull($result[0]->getCreatedAt()); + $this->assertNotNull($result[0]->getUpdatedAt()); $this->assertNull($result[0]->getDeletedAt()); $infoRepository->delete(3); @@ -1336,8 +1342,8 @@ public function testMappingAttributeSoftDeleteAndGetByPK() $this->assertEquals(3, $result->getPk()); $this->assertEquals(3, $result->iduser); $this->assertEquals(3.5, $result->value); - $this->assertNull($result->getCreatedAt()); - $this->assertNull($result->getUpdatedAt()); + $this->assertNotNull($result->getCreatedAt()); + $this->assertNotNull($result->getUpdatedAt()); $this->assertNull($result->getDeletedAt()); $infoRepository->delete(3); @@ -1360,7 +1366,7 @@ public function testQueryInstanceWithModel() $this->assertEquals(3, $result[0]->getId()); $this->assertEquals('JG', $result[0]->getName()); - $this->assertEquals('1974-01-26', $result[0]->getCreatedate()); + $this->assertEquals('1974-01-26 00:00:00', $result[0]->getCreatedate()); } @@ -1449,9 +1455,9 @@ public function testActiveRecordGet() $this->assertEquals(3, $model->getPk()); $this->assertEquals(3, $model->iduser); $this->assertEquals(3.5, $model->value); - $this->assertNull($model->getCreatedAt()); // Because it was not set in the initial insert outside the ORM - $this->assertNull($model->getUpdatedAt()); // Because it was not set in the initial insert outside the ORM - $this->assertNull($model->getDeletedAt()); // Because it was not set in the initial insert outside the ORM + $this->assertNotNull($model->getCreatedAt()); + $this->assertNotNull($model->getUpdatedAt()); + $this->assertNull($model->getDeletedAt()); } public function testActiveRecordRefresh() @@ -1472,9 +1478,9 @@ public function testActiveRecordRefresh() $this->assertEquals(3, $model->getPk()); $this->assertEquals(3, $model->iduser); $this->assertEquals(3.5, $model->value); - $this->assertNull($createdAt); // Because it was not set in the initial insert outside the ORM - $this->assertNull($updatedAt); // Because it was not set in the initial insert outside the ORM - $this->assertNull($deletedAt); // Because it was not set in the initial insert outside the ORM + $this->assertNotNull($createdAt); + $this->assertNotNull($updatedAt); + $this->assertNull($deletedAt); // Update the record OUTSIDE the Active Record $this->dbDriver->execute("UPDATE info SET iduser = 4, property = 44.44 WHERE id = 3"); @@ -1483,9 +1489,9 @@ public function testActiveRecordRefresh() $this->assertEquals(3, $model->getPk()); $this->assertEquals(3, $model->iduser); $this->assertEquals(3.5, $model->value); - $this->assertEquals($createdAt, $model->getCreatedAt()); // Because it was not set in the initial insert outside the ORM - $this->assertEquals($updatedAt, $model->getUpdatedAt()); // Because it was not set in the initial insert outside the ORM - $this->assertEquals($deletedAt, $model->getDeletedAt()); // Because it was not set in the initial insert outside the ORM + $this->assertEquals($createdAt, $model->getCreatedAt()); + $this->assertEquals($updatedAt, $model->getUpdatedAt()); + $this->assertEquals($deletedAt, $model->getDeletedAt()); // Refresh the model $model->refresh(); @@ -1494,9 +1500,9 @@ public function testActiveRecordRefresh() $this->assertEquals(3, $model->getPk()); $this->assertEquals(4, $model->iduser); $this->assertEquals(44.44, $model->value); - $this->assertEquals($createdAt, $model->getCreatedAt()); // Because it was not set in the initial insert outside the ORM - $this->assertEquals($updatedAt, $model->getUpdatedAt()); // Because it was not set in the initial insert outside the ORM - $this->assertEquals($deletedAt, $model->getDeletedAt()); // Because it was not set in the initial insert outside the ORM + $this->assertEquals($createdAt, $model->getCreatedAt()); + $this->assertEquals($updatedAt, $model->getUpdatedAt()); + $this->assertEquals($deletedAt, $model->getDeletedAt()); } public function testActiveRecordRefreshError() @@ -1519,9 +1525,9 @@ public function testActiveRecordFill() $this->assertEquals(3, $model->getPk()); $this->assertEquals(3, $model->iduser); $this->assertEquals(3.5, $model->value); - $this->assertNull($model->getCreatedAt()); // Because it was not set in the initial insert outside the ORM - $this->assertNull($model->getUpdatedAt()); // Because it was not set in the initial insert outside the ORM - $this->assertNull($model->getDeletedAt()); // Because it was not set in the initial insert outside the ORM + $this->assertNotNull($model->getCreatedAt()); + $this->assertNotNull($model->getUpdatedAt()); + $this->assertNull($model->getDeletedAt()); $model->fill([ 'iduser' => 4, @@ -1531,9 +1537,9 @@ public function testActiveRecordFill() $this->assertEquals(3, $model->getPk()); $this->assertEquals(4, $model->iduser); $this->assertEquals(44.44, $model->value); - $this->assertNull($model->getCreatedAt()); // Because it was not set in the initial insert outside the ORM - $this->assertNull($model->getUpdatedAt()); // Because it was not set in the initial insert outside the ORM - $this->assertNull($model->getDeletedAt()); // Because it was not set in the initial insert outside the ORM + $this->assertNotNull($model->getCreatedAt()); + $this->assertNotNull($model->getUpdatedAt()); + $this->assertNull($model->getDeletedAt()); } @@ -1547,16 +1553,16 @@ public function testActiveRecordFilter() $this->assertEquals(1, $model[0]->getPk()); $this->assertEquals(1, $model[0]->iduser); $this->assertEquals(30.4, $model[0]->value); - $this->assertNull($model[0]->getCreatedAt()); // Because it was not set in the initial insert outside the ORM - $this->assertNull($model[0]->getUpdatedAt()); // Because it was not set in the initial insert outside the ORM - $this->assertNull($model[0]->getDeletedAt()); // Because it was not set in the initial insert outside the ORM + $this->assertNotNull($model[0]->getCreatedAt()); + $this->assertNotNull($model[0]->getUpdatedAt()); + $this->assertNull($model[0]->getDeletedAt()); $this->assertEquals(2, $model[1]->getPk()); $this->assertEquals(1, $model[1]->iduser); $this->assertEquals(1250.96, $model[1]->value); - $this->assertNull($model[1]->getCreatedAt()); // Because it was not set in the initial insert outside the ORM - $this->assertNull($model[1]->getUpdatedAt()); // Because it was not set in the initial insert outside the ORM - $this->assertNull($model[1]->getDeletedAt()); // Because it was not set in the initial insert outside the ORM + $this->assertNotNull($model[1]->getCreatedAt()); + $this->assertNotNull($model[1]->getUpdatedAt()); + $this->assertNull($model[1]->getDeletedAt()); } public function testActiveRecordEmptyFilter() @@ -1594,17 +1600,17 @@ public function testActiveRecordNew() $this->assertEquals(4, $model->getPk()); $this->assertEquals(5, $model->iduser); $this->assertEquals(55.8, $model->value); - $this->assertNotNull($model->getCreatedAt()); // Because it was not set in the initial insert outside the ORM - $this->assertNotNull($model->getUpdatedAt()); // Because it was not set in the initial insert outside the ORM - $this->assertNull($model->getDeletedAt()); // Because it was not set in the initial insert outside the ORM + $this->assertNotNull($model->getCreatedAt()); + $this->assertNotNull($model->getUpdatedAt()); + $this->assertNull($model->getDeletedAt()); $model = ActiveRecordModel::get(4); $this->assertEquals(4, $model->getPk()); $this->assertEquals(5, $model->iduser); $this->assertEquals(55.8, $model->value); - $this->assertNotNull($model->getCreatedAt()); // Because it was not set in the initial insert outside the ORM - $this->assertNotNull($model->getUpdatedAt()); // Because it was not set in the initial insert outside the ORM - $this->assertNull($model->getDeletedAt()); // Because it was not set in the initial insert outside the ORM + $this->assertNotNull($model->getCreatedAt()); + $this->assertNotNull($model->getUpdatedAt()); + $this->assertNull($model->getDeletedAt()); } public function testActiveRecordUpdate() @@ -1620,17 +1626,17 @@ public function testActiveRecordUpdate() $this->assertEquals(3, $model->getPk()); $this->assertEquals(3, $model->iduser); $this->assertEquals(99.1, $model->value); - $this->assertNull($model->getCreatedAt()); // Because it was not set in the initial insert outside the ORM - $this->assertNotNull($model->getUpdatedAt()); // Because it was not set in the initial insert outside the ORM - $this->assertNull($model->getDeletedAt()); // Because it was not set in the initial insert outside the ORM + $this->assertNotNull($model->getCreatedAt()); + $this->assertNotNull($model->getUpdatedAt()); + $this->assertNull($model->getDeletedAt()); $model = ActiveRecordModel::get(3); $this->assertEquals(3, $model->getPk()); $this->assertEquals(3, $model->iduser); $this->assertEquals(99.1, $model->value); - $this->assertNull($model->getCreatedAt()); // Because it was not set in the initial insert outside the ORM - $this->assertNotNull($model->getUpdatedAt()); // Because it was not set in the initial insert outside the ORM - $this->assertNull($model->getDeletedAt()); // Because it was not set in the initial insert outside the ORM + $this->assertNotNull($model->getCreatedAt()); + $this->assertNotNull($model->getUpdatedAt()); + $this->assertNull($model->getDeletedAt()); } public function testActiveRecordDelete() diff --git a/tests/RepositoryUuidTest.php b/tests/RepositoryUuidTest.php index 93988a2..bf2bce2 100644 --- a/tests/RepositoryUuidTest.php +++ b/tests/RepositoryUuidTest.php @@ -3,18 +3,13 @@ namespace Tests; use ByJG\AnyDataset\Db\DbDriverInterface; -use ByJG\AnyDataset\Db\Factory; use ByJG\MicroOrm\Literal\HexUuidLiteral; use ByJG\MicroOrm\Repository; -use ByJG\Util\Uri; use PHPUnit\Framework\TestCase; use Tests\Model\UsersWithUuidKey; class RepositoryUuidTest extends TestCase { - - const URI='sqlite:///tmp/test.db'; - /** * @var DbDriverInterface */ @@ -27,7 +22,7 @@ class RepositoryUuidTest extends TestCase public function setUp(): void { - $this->dbDriver = Factory::getDbInstance(self::URI); + $this->dbDriver = ConnectionUtil::getConnection("testmicroorm"); $this->dbDriver->execute('create table usersuuid ( id binary(16) primary key, @@ -38,8 +33,7 @@ public function setUp(): void public function tearDown(): void { - $uri = new Uri(self::URI); - unlink($uri->getPath()); + $this->dbDriver->execute('drop table if exists usersuuid;'); } public function testGet() diff --git a/tests/TransactionManagerTest.php b/tests/TransactionManagerTest.php index b95328a..fc5fa4e 100644 --- a/tests/TransactionManagerTest.php +++ b/tests/TransactionManagerTest.php @@ -7,6 +7,7 @@ use ByJG\MicroOrm\Mapper; use ByJG\MicroOrm\Repository; use ByJG\MicroOrm\TransactionManager; +use InvalidArgumentException; use PHPUnit\Framework\TestCase; use Tests\Model\Users; @@ -20,41 +21,41 @@ class TransactionManagerTest extends TestCase public function setUp(): void { $this->object = new TransactionManager(); + + ConnectionUtil::getConnection("a"); + ConnectionUtil::getConnection("b"); + ConnectionUtil::getConnection("c"); + ConnectionUtil::getConnection("d"); } public function tearDown(): void { $this->object->destroy(); $this->object = null; - if (file_exists("/tmp/a.db")) { - unlink("/tmp/a.db"); - } - if (file_exists("/tmp/b.db")) { - unlink("/tmp/b.db"); - } - if (file_exists("/tmp/c.db")) { - unlink("/tmp/c.db"); - } - if (file_exists("/tmp/d.db")) { - unlink("/tmp/d.db"); - } + + $dbDriver = ConnectionUtil::getConnection("a"); + $dbDriver->execute('drop table if exists users;'); + $dbDriver->execute('drop table if exists users1;'); + + $dbDriver = ConnectionUtil::getConnection("b"); + $dbDriver->execute('drop table if exists users2;'); } public function testAddConnectionError() { - $this->expectException(\InvalidArgumentException::class); + $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage("The connection already exists with a different instance"); - $dbDrive1 = $this->object->addConnection("sqlite:///tmp/a.db"); - $dbDrive2 = $this->object->addConnection("sqlite:///tmp/b.db"); - $dbDrive3 = $this->object->addConnection("sqlite:///tmp/a.db"); - $dbDrive4 = $this->object->addConnection("sqlite:///tmp/b.db"); + $dbDrive1 = $this->object->addConnection(ConnectionUtil::getUri("a")->__toString()); + $dbDrive2 = $this->object->addConnection(ConnectionUtil::getUri("b")->__toString()); + $dbDrive3 = $this->object->addConnection(ConnectionUtil::getUri("a")->__toString()); + $dbDrive4 = $this->object->addConnection(ConnectionUtil::getUri("b")->__toString()); } public function testAddConnection() { - $dbDrive1 = $this->object->addConnection("sqlite:///tmp/a.db"); - $dbDrive2 = $this->object->addConnection("sqlite:///tmp/b.db"); + $dbDrive1 = $this->object->addConnection(ConnectionUtil::getUri("a")->__toString()); + $dbDrive2 = $this->object->addConnection(ConnectionUtil::getUri("b")->__toString()); $this->object->addDbDriver($dbDrive1); $this->object->addDbDriver($dbDrive2); @@ -65,13 +66,13 @@ public function testAddConnection() public function testAddDbDriverError() { - $this->expectException(\InvalidArgumentException::class); + $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage("The connection already exists with a different instance"); - $dbDrive1 = Factory::getDbInstance("sqlite:///tmp/a.db"); - $dbDrive2 = Factory::getDbInstance("sqlite:///tmp/b.db"); - $dbDrive3 = Factory::getDbInstance("sqlite:///tmp/a.db"); - $dbDrive4 = Factory::getDbInstance("sqlite:///tmp/b.db"); + $dbDrive1 = Factory::getDbInstance(ConnectionUtil::getUri("a")); + $dbDrive2 = Factory::getDbInstance(ConnectionUtil::getUri("b")); + $dbDrive3 = Factory::getDbInstance(ConnectionUtil::getUri("a")); + $dbDrive4 = Factory::getDbInstance(ConnectionUtil::getUri("b")); $this->object->addDbDriver($dbDrive1); $this->object->addDbDriver($dbDrive2); @@ -81,8 +82,8 @@ public function testAddDbDriverError() public function testAddDbDriver() { - $dbDrive1 = Factory::getDbInstance("sqlite:///tmp/a.db"); - $dbDrive2 = Factory::getDbInstance("sqlite:///tmp/b.db"); + $dbDrive1 = ConnectionUtil::getConnection("a"); + $dbDrive2 = ConnectionUtil::getConnection("b"); $this->object->addDbDriver($dbDrive1); $this->object->addDbDriver($dbDrive2); @@ -96,10 +97,10 @@ public function testAddDbDriver() public function testAddRepository() { - $dbDriver = Factory::getDbInstance("sqlite:///tmp/a.db"); + $dbDriver = ConnectionUtil::getConnection("a"); $dbDriver->execute('create table users ( - id integer primary key autoincrement, + id integer primary key auto_increment, name varchar(45), createdate datetime);' ); @@ -118,8 +119,8 @@ public function testAddRepository() public function testBeginTransaction() { - $this->object->addConnection("sqlite:///tmp/c.db"); - $this->object->addConnection("sqlite:///tmp/d.db"); + $this->object->addConnection(ConnectionUtil::getUri("c")->__toString()); + $this->object->addConnection(ConnectionUtil::getUri("d")->__toString()); $this->object->beginTransaction(); $this->object->commitTransaction(); @@ -132,8 +133,8 @@ public function testBeginTransactionTwice() $this->expectException(TransactionException::class); $this->expectExceptionMessage("Transaction Already Started"); - $this->object->addConnection("sqlite:///tmp/a.db"); - $this->object->addConnection("sqlite:///tmp/d.db"); + $this->object->addConnection(ConnectionUtil::getUri("a")->__toString()); + $this->object->addConnection(ConnectionUtil::getUri("d")->__toString()); $this->assertEquals(2, $this->object->count()); @@ -146,7 +147,7 @@ public function testRollbackWithNoTransaction() $this->expectException(TransactionException::class); $this->expectExceptionMessage("There is no Active Transaction"); - $this->object->addConnection("sqlite:///tmp/c.db"); + $this->object->addConnection(ConnectionUtil::getUri("c")->__toString()); $this->assertEquals(1, $this->object->count()); $this->object->rollbackTransaction(); } @@ -156,22 +157,22 @@ public function testCommitWithNoTransaction() $this->expectException(TransactionException::class); $this->expectExceptionMessage("There is no Active Transaction"); - $this->object->addConnection("sqlite:///tmp/d.db"); + $this->object->addConnection(ConnectionUtil::getUri("d")->__toString()); $this->assertEquals(1, $this->object->count()); $this->object->commitTransaction(); } public function testTransaction() { - $dbDrive1 = Factory::getDbInstance("sqlite:///tmp/a.db"); - $dbDrive2 = Factory::getDbInstance("sqlite:///tmp/b.db"); + $dbDrive1 = ConnectionUtil::getConnection("a"); + $dbDrive2 = ConnectionUtil::getConnection("b"); $dbDrive1->execute('create table users1 ( - id integer primary key autoincrement, + id integer primary key auto_increment, name varchar(45));' ); $dbDrive2->execute('create table users2 ( - id integer primary key autoincrement, + id integer primary key auto_increment, name varchar(45));' ); diff --git a/tests/UpdateQueryTest.php b/tests/UpdateQueryTest.php index 5e1be53..09a76c1 100644 --- a/tests/UpdateQueryTest.php +++ b/tests/UpdateQueryTest.php @@ -2,13 +2,18 @@ namespace Tests; +use ByJG\AnyDataset\Db\Factory; use ByJG\AnyDataset\Db\Helpers\DbMysqlFunctions; use ByJG\AnyDataset\Db\Helpers\DbPgsqlFunctions; use ByJG\AnyDataset\Db\Helpers\DbSqliteFunctions; +use ByJG\AnyDataset\Db\PdoMysql; +use ByJG\AnyDataset\Db\PdoObj; use ByJG\MicroOrm\Exception\InvalidArgumentException; +use ByJG\MicroOrm\Query; use ByJG\MicroOrm\SqlObject; use ByJG\MicroOrm\SqlObjectEnum; use ByJG\MicroOrm\UpdateQuery; +use ByJG\Util\Uri; use PHPUnit\Framework\TestCase; class UpdateQueryTest extends TestCase @@ -144,6 +149,58 @@ public function testUpdateJoinMySQl() ); } + public function testUpdateJoinMySQlAlias() + { + $this->object->table('test'); + $this->object->join('table2', 't2.id = test.id', 't2'); + $this->object->set('fld1', 'A'); + $this->object->set('fld2', 'B'); + $this->object->set('fld3', 'C'); + $this->object->where('fld1 = :id', ['id' => 10]); + + $sqlObject = $this->object->build(new DbMysqlFunctions()); + $this->assertEquals( + new SqlObject( + 'UPDATE `test` INNER JOIN `table2` AS t2 ON t2.id = test.id SET `fld1` = :fld1 , `fld2` = :fld2 , `fld3` = :fld3 WHERE fld1 = :id', + ['id' => 10, 'fld1' => 'A', 'fld2' => 'B', 'fld3' => 'C'], + SqlObjectEnum::UPDATE + ), + $sqlObject + ); + } + + public function testUpdateJoinMySQlSubqueryAlias() + { + $query = Query::getInstance() + ->table('table2') + ->where('id = 10'); + $this->object->table('test'); + $this->object->join($query, 't2.id = test.id', 't2'); + $this->object->set('fld1', 'A'); + $this->object->set('fld2', 'B'); + $this->object->set('fld3', 'C'); + $this->object->where('fld1 = :id', ['id' => 10]); + + + $dbDriver = new class extends PdoMysql + { + public function __construct() + { + $this->pdoObj = new PdoObj(new Uri('mysql://root:root@localhost/test')); + } + }; + + $sqlObject = $this->object->build($dbDriver); + $this->assertEquals( + new SqlObject( + 'UPDATE `test` INNER JOIN (SELECT * FROM table2 WHERE id = 10) AS t2 ON t2.id = test.id SET `fld1` = :fld1 , `fld2` = :fld2 , `fld3` = :fld3 WHERE fld1 = :id', + ['id' => 10, 'fld1' => 'A', 'fld2' => 'B', 'fld3' => 'C'], + SqlObjectEnum::UPDATE + ), + $sqlObject + ); + } + public function testUpdateJoinPostgres() { $this->object->table('test');