diff --git a/src/Database/PicoDatabasePersistence.php b/src/Database/PicoDatabasePersistence.php index ab8dbef..61368b2 100644 --- a/src/Database/PicoDatabasePersistence.php +++ b/src/Database/PicoDatabasePersistence.php @@ -1124,6 +1124,26 @@ private function addGeneratedValue($info, $firstCall) return $this; } + /** + * Determines whether a value should be generated for the given property + * during the first invocation of the generation process. + * + * A value will be generated only if: + * - This is the first call of the generation lifecycle + * - The specified property does not yet have a value (null or empty string) + * - No generated value has been assigned previously + * + * @param bool $firstCall Indicates whether this is the first call in the generation lifecycle. + * @param string $prop The property name to be evaluated. + * @return bool Returns true if a new value should be generated; false otherwise. + */ + private function shouldGenerateValueOnFirstCall($firstCall, $prop) + { + return $firstCall + && ($this->object->get($prop) === null || $this->object->get($prop) === '') + && !$this->generatedValue; + } + /** * Set a generated value for a specified property based on its generation strategy. * @@ -1134,40 +1154,43 @@ private function addGeneratedValue($info, $firstCall) */ private function setGeneratedValue($prop, $strategy, $firstCall) { - if(stripos($strategy, "UUID") !== false) - { - if($firstCall && ($this->object->get($prop) == null || $this->object->get($prop) == "") && !$this->generatedValue) - { - $generatedValue = $this->database->generateNewId(); - $this->object->set($prop, $generatedValue); - $this->generatedValue = true; + // Handle IDENTITY strategy separately (DB-driven) + if (stripos($strategy, 'IDENTITY') !== false) { + if ($firstCall) { + $this->requireDbAutoincrement = true; + } elseif ($this->requireDbAutoincrement && !$this->dbAutoinrementCompleted) { + $this->object->set( + $prop, + $this->database->getDatabaseConnection()->lastInsertId() + ); + $this->dbAutoinrementCompleted = true; } + + return $this; } - else if(stripos($strategy, "TIMEBASED") !== false) - { - if($firstCall && ($this->object->get($prop) == null || $this->object->get($prop) == "") && !$this->generatedValue) - { - $generatedValue = $this->database->generateTimeBasedId(); - $this->object->set($prop, $generatedValue); + + // Map strategy to generator method + $generatorMap = [ + 'UUID' => 'generateUUID', + 'TIMEBASED' => 'generateTimeBasedId', + 'LEGACY_TIMEBASED' => 'generateNewId', + ]; + + foreach ($generatorMap as $key => $method) { + if ( + stripos($strategy, $key) !== false && + $this->shouldGenerateValueOnFirstCall($firstCall, $prop) + ) { + $this->object->set($prop, $this->database->{$method}()); $this->generatedValue = true; + break; } } - else if(stripos($strategy, "IDENTITY") !== false) - { - if($firstCall) - { - $this->requireDbAutoincrement = true; - } - else if($this->requireDbAutoincrement && !$this->dbAutoinrementCompleted) - { - $generatedValue = $this->database->getDatabaseConnection()->lastInsertId(); - $this->object->set($prop, $generatedValue); - $this->dbAutoinrementCompleted = true; - } - } + return $this; } + /** * Insert the current object's data into the database. * @@ -1275,11 +1298,29 @@ private function fixInsertableValues($values, $info = null) } /** - * 1. TABLE - Indicates that the persistence provider must assign primary keys for the entity using an underlying database table to ensure uniqueness. - * 2. SEQUENCE - Indicates that the persistence provider must assign primary keys for the entity using a database sequence. - * 3. IDENTITY - Indicates that the persistence provider must assign primary keys for the entity using a database identity column. - * 4. AUTO - Indicates that the persistence provider should pick an appropriate strategy for the particular database. The AUTO generation strategy may expect a database resource to exist, or it may attempt to create one. A vendor may provide documentation on how to create such resources in the event that it does not support schema generation or cannot create the schema resource at runtime. - * 5. UUID - Indicates that the persistence provider must assign primary keys for the entity with a UUID value. + * 1. TABLE - Indicates that the persistence provider must assign primary keys for the entity + * using an underlying database table to ensure uniqueness. + * + * 2. SEQUENCE - Indicates that the persistence provider must assign primary keys for the entity + * using a database sequence. + * + * 3. IDENTITY - Indicates that the persistence provider must assign primary keys for the entity + * using a database identity column. + * + * 4. AUTO - Indicates that the persistence provider should select an appropriate strategy for + * the specific database. The AUTO generation strategy may expect a database resource to + * already exist, or it may attempt to create one. A vendor may provide documentation on how + * to create such resources if schema generation is not supported or if the resource cannot + * be created at runtime. + * + * 5. UUID - Indicates that the persistence provider must assign primary keys for the entity + * using a standard UUID value. + * + * 6. TIME_BASED - Indicates that the persistence provider must assign primary keys for the entity + * using the current epoch time in nanoseconds, combined with a 3-hex-digit random suffix. + * + * 7. LEGACY_TIMEBASED - Indicates that the persistence provider must assign primary keys for the + * entity using the legacy, non-standard UUID-based strategy from earlier versions. */ if($info->getAutoIncrementKeys() != null) @@ -1311,7 +1352,7 @@ private function fixInsertableValues($values, $info = null) */ private function isRequireGenerateValue($strategy, $propertyName) { - return stripos($strategy, "UUID") !== false + return (stripos($strategy, "UUID") !== false || stripos($strategy, "TIMEBASED") !== false || stripos($strategy, "LEGACY_TIMEBASED") !== false) && ($this->object->get($propertyName) == null || $this->object->get($propertyName) == "") && !$this->generatedValue; } diff --git a/src/Database/PicoDatabaseQueryBuilder.php b/src/Database/PicoDatabaseQueryBuilder.php index ef433af..6c71a57 100644 --- a/src/Database/PicoDatabaseQueryBuilder.php +++ b/src/Database/PicoDatabaseQueryBuilder.php @@ -711,12 +711,10 @@ public function rollback() */ public function escapeSQL($query) // NOSONAR { - // Escape carriage return and newline for all - $query = str_replace(["\r", "\n"], ["\\r", "\\n"], $query); - if (stripos($this->databaseType, PicoDatabaseType::DATABASE_TYPE_MYSQL) !== false || stripos($this->databaseType, PicoDatabaseType::DATABASE_TYPE_MARIADB) !== false) { - // MySQL/MariaDB: escape both backslash and single quote + + // MySQL / MariaDB return str_replace( ["\\", "'"], ["\\\\", "\\'"], @@ -725,25 +723,28 @@ public function escapeSQL($query) // NOSONAR } if (stripos($this->databaseType, PicoDatabaseType::DATABASE_TYPE_PGSQL) !== false) { - // PostgreSQL: double single quotes and backslashes (E'' required at usage site) + + // PostgreSQL (without E'') return str_replace( - ["\\", "'"], - ["\\\\", "''"], + ["'", "\\"], + ["''", "\\\\"], $query ); } if (stripos($this->databaseType, PicoDatabaseType::DATABASE_TYPE_SQLITE) !== false) { - // SQLite: only escape single quote + + // SQLite return str_replace("'", "''", $query); } if (stripos($this->databaseType, PicoDatabaseType::DATABASE_TYPE_SQLSERVER) !== false) { - // SQL Server: only escape single quote + + // SQL Server return str_replace("'", "''", $query); } - // Default fallback: treat like MySQL + // Default fallback return str_replace( ["\\", "'"], ["\\\\", "\\'"],