From 894047b244813a1bcc38e96fb7ba1d0477eadcb4 Mon Sep 17 00:00:00 2001 From: Belisoful Date: Thu, 22 Oct 2026 14:44:51 -0700 Subject: [PATCH] Add CHANGELOG and migration guide; match framework master where divergence was unneeded --- CHANGELOG.md | 31 +++++++++ README.md | 2 + docs/README.md | 1 + docs/migration.md | 68 +++++++++++++++++++ .../SqlMap/Configuration/TSqlMapStatement.php | 2 +- src/Data/SqlMap/DataMapper/TSqlMapCache.php | 7 +- .../SqlMap/Statements/IMappedStatement.php | 4 +- .../SqlMap/Statements/TMappedStatement.php | 4 +- src/Data/SqlMap/TSqlMapConfig.php | 2 +- src/Data/SqlMap/TSqlMapGateway.php | 8 +-- tests/unit/Data/SqlMap/SqlMapCacheTest.php | 12 ++-- 11 files changed, 118 insertions(+), 23 deletions(-) create mode 100644 CHANGELOG.md create mode 100644 docs/migration.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..508fc86 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,31 @@ +# Changelog + +All notable changes to this package are documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). + +## [Unreleased] + +First standalone release. SqlMap moves out of the PRADO framework (`framework/Data/SqlMap`) into this package. Namespaces and class names are unchanged. The baseline is the framework copy in PRADO `master` at commit `2c7e90da` (2026-10-21), including every SqlMap fix made there through pradosoft/prado#1330. The entries below are differences from that baseline. See the [migration guide](docs/migration.md) for upgrade steps. + +### Added + +- `composer.json` declares `extra.prado` for PRADO 4.4: `bootstrap` (`TSqlMapConfig`), `error-messages` (`src/Data/SqlMap/DataMapper/messages.txt`), and `class-map` (`config/classes.json`). The legacy top-level `extra.bootstrap` remains for PRADO 4.3. +- `config/classes.json` maps the 55 SqlMap short class names (`TSqlMapConfig`, `TSqlMapFifoCache`, ...) to their FQNs. It replaces the SqlMap entries of the framework's `classes.php`. +- Message text for five codes that were thrown without a definition: `sqlmap_can_not_extend_select_key`, `sqlmap_configfile_invalid`, `sqlmap_must_enable_custom_paging`, `sqlmap_property_must_be_list`, `sqlmap_use_set_to_store_cache`. +- `TSqlMapException` accepts the `TException` argument forms: an optional integer error code first, and a trailing `Throwable` as the previous exception. + +### Changed + +- `TSqlMapException::getErrorCode()` returns the message code (for example `sqlmap_contains_no_statement`). It previously returned the translated message text. + +### Fixed + +- Result mapping falls back to a case-insensitive column match when the exact column name is absent. `pdo_oci` returns upper-case names and `pdo_pgsql` returns lower-case names. This applies to `` lookups, implicit `resultClass` mapping, `groupBy` keys, and lazy-load (`select`) keys. +- IBM DB2: the `PRADO_ROWNUM` column added by the paging subquery is removed from each row before mapping. +- IBM DB2: a null parameter without a `dbType` binds as `PDO::PARAM_STR`. `pdo_ibm` rejects `PDO::PARAM_NULL`. +- Firebird: a boolean parameter binds as `0` or `1`. `pdo_firebird` binds a PHP boolean only to a `BOOLEAN` column. +- `TCachingStatement` builds cache keys from the SQL text. It previously serialized the prepared command, which threw "Serialization of 'PDOStatement' is not allowed" for any cached statement with bound parameters. +- `TCachingStatement::executeInsert()` delegates to the wrapped statement. It previously called itself until the stack overflowed. +- `TParameterProperty::__sleep()` and `TResultProperty::__sleep()` name private properties by their namespaced class, so properties at their default values are left out of the serialized form. `TResultProperty` also misspelled `_propertyName`. +- `TResultProperty::getPropertyValueType()` no longer calls a method on `null` when no reflection class is found for the type. diff --git a/README.md b/README.md index a84efa9..cf30e72 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,8 @@ Within a PRADO Application: composer require pradosoft/prado-sqlmap ``` +Applications upgrading from the SqlMap bundled with the PRADO framework: see the [migration guide](docs/migration.md). Release notes are in the [changelog](CHANGELOG.md). + ## Supported Databases Any database with a PDO driver. The test suite covers: diff --git a/docs/README.md b/docs/README.md index 7c1c927..085d046 100644 --- a/docs/README.md +++ b/docs/README.md @@ -8,6 +8,7 @@ SqlMap is a data-mapper layer that maps SQL statements defined in XML files to P | Section | Description | |---|---| +| [Migration](migration.md) | Moving from the framework-bundled SqlMap to this package | | [Prado Integration](integration.md) | Registering `TSqlMapConfig` as a PRADO application module, connection configuration, and caching the parsed manager | | [Quick Start](quickstart.md) | Minimal working example: config file, mapping file, and PHP calls | | [Configuration](configuration/README.md) | `sqlMapConfig` root file reference — properties, type aliases, type handlers, transaction manager, data source, and `sqlMaps` includes | diff --git a/docs/migration.md b/docs/migration.md new file mode 100644 index 0000000..99ffd43 --- /dev/null +++ b/docs/migration.md @@ -0,0 +1,68 @@ +[prado-sqlmap](../README.md) -> [SqlMap Docs](README.md) -> Migration + +# Migration guide + +SqlMap shipped inside the PRADO framework from PRADO 3.1. PRADO 4.4 extracts it into this package. The class names and namespaces are unchanged, so most applications need only add the dependency and clear the application cache. + +## Install the package + +```sh +composer require pradosoft/prado-sqlmap +``` + +PRADO reads the package's `extra.prado` section and registers its class map and error messages system-wide. Existing module definitions keep working without changes. + +## What is unchanged + +- **Namespaces**: every class keeps its `Prado\Data\SqlMap\...` namespace, for example `Prado\Data\SqlMap\TSqlMapConfig` and `Prado\Data\SqlMap\DataMapper\TSqlMapFifoCache`. +- **Short names** still resolve. `config/classes.json` maps them to their FQNs, so `` and `Prado::createComponent('TSqlMapFifoCache')` work as before. +- **Module configuration**: `TSqlMapConfig` properties (`ConfigFile`, `ConnectionID`, `EnableCache`) and the inline `` element are unchanged. +- **XML formats**: `sqlMapConfig` and mapping files use the same elements and attributes. +- **API**: `TSqlMapGateway` methods and their arguments are unchanged. +- **Error codes**: existing `sqlmap_*` codes keep their text. + +## What changed + +### `TSqlMapException::getErrorCode()` returns the code + +`getErrorCode()` returns the message code, matching other PRADO exceptions. It previously returned the translated message. + +```php +try { + $gateway->queryForObject('missing'); +} catch (TSqlMapUndefinedException $e) { + $e->getErrorCode(); // before: "Unable to find SQLMap statement 'missing'." + // after: "sqlmap_contains_no_statement" + $e->getMessage(); // unchanged: "Unable to find SQLMap statement 'missing'." +} +``` + +Code that compares `getErrorCode()` with message text must compare `getMessage()` instead. + +### Column names match regardless of case + +When a row has no column with the exact mapped name, a case-insensitive match is used. Oracle (`pdo_oci`) and PostgreSQL (`pdo_pgsql`) results now fill properties that previously stayed empty. Queries that alias columns to work around the folding continue to work. + +### Driver-specific parameter binding + +| Driver | Value | Before | After | +|---|---|---|---| +| Firebird | PHP `bool` | bound as boolean; rejected by integer columns | bound as `0` or `1` | +| IBM DB2 | `null` without `dbType` | bound as `PDO::PARAM_NULL`; rejected by `pdo_ibm` | bound as `PDO::PARAM_STR` (SQL `NULL`) | + +### Cached statements + +Statements with a `cacheModel` build their cache key from the SQL text. Cached statements with bound parameters previously failed with "Serialization of 'PDOStatement' is not allowed" and now cache as configured. Keys for statements without parameters differ from before, so earlier entries are not reused. + +### Serialized managers + +With `EnableCache="true"`, `TSqlMapConfig` stores the parsed `TSqlMapManager` in the application cache. The serialized form of several classes differs from the framework copy. Clear the application cache once after upgrading so the manager is rebuilt. + +## Verifying the move + +After installing the package: + +1. Confirm `class="TSqlMapConfig"` module definitions still resolve. +2. Clear the application cache, or delete the cache files under `protected/runtime`. +3. Replace any comparison of `TSqlMapException::getErrorCode()` with message text. +4. Run the application's own tests against its mapped statements. diff --git a/src/Data/SqlMap/Configuration/TSqlMapStatement.php b/src/Data/SqlMap/Configuration/TSqlMapStatement.php index ef193bb..fb36815 100644 --- a/src/Data/SqlMap/Configuration/TSqlMapStatement.php +++ b/src/Data/SqlMap/Configuration/TSqlMapStatement.php @@ -259,7 +259,7 @@ public function initialize($manager) /** * @param TSqlMapTypeHandlerRegistry $registry type handler registry - * @return \ArrayAccess new instance of list class. + * @return array|\ArrayAccess new instance of list class. */ public function createInstanceOfListClass($registry) { diff --git a/src/Data/SqlMap/DataMapper/TSqlMapCache.php b/src/Data/SqlMap/DataMapper/TSqlMapCache.php index f34072a..b246659 100644 --- a/src/Data/SqlMap/DataMapper/TSqlMapCache.php +++ b/src/Data/SqlMap/DataMapper/TSqlMapCache.php @@ -87,16 +87,15 @@ public function add($id, $value, $expire = 0, $dependency = null) } /** - * Deletes a value with the specified key from cache and returns the removed object. + * Deletes a value with the specified key from cache * @param string $key the key of the value to be deleted - * @return mixed the object that was cached under $key, or null if not found + * @return bool if no error happens during deletion */ public function delete($key) { - $object = $this->get($key); $this->_cache->remove($key); $this->_keyList->remove($key); - return $object; + return true; } /** diff --git a/src/Data/SqlMap/Statements/IMappedStatement.php b/src/Data/SqlMap/Statements/IMappedStatement.php index 20428be..4762fb7 100644 --- a/src/Data/SqlMap/Statements/IMappedStatement.php +++ b/src/Data/SqlMap/Statements/IMappedStatement.php @@ -44,7 +44,7 @@ public function getManager(); * @param int $skip The number of rows to skip over. * @param int $max The maximum number of rows to return. * @param null|mixed $delegate - * @return \Prado\Collections\TMap A map of object containing the rows keyed by $keyProperty. + * @return array A map of object containing the rows keyed by $keyProperty. */ public function executeQueryForMap($connection, $parameter, $keyProperty, $valueProperty = null, $skip = -1, $max = -1, $delegate = null); @@ -66,7 +66,7 @@ public function executeUpdate($connection, $parameter); * @param int $skip The number of rows to skip over. * @param int $max The maximum number of rows to return. * @param null|mixed $delegate - * @return \Prado\Collections\TList A TList of result objects. + * @return array|\ArrayAccess A TList of result objects. */ public function executeQueryForList($connection, $parameter, $result = null, $skip = -1, $max = -1, $delegate = null); diff --git a/src/Data/SqlMap/Statements/TMappedStatement.php b/src/Data/SqlMap/Statements/TMappedStatement.php index 604d59f..f028e65 100644 --- a/src/Data/SqlMap/Statements/TMappedStatement.php +++ b/src/Data/SqlMap/Statements/TMappedStatement.php @@ -293,9 +293,7 @@ public function runQueryForMap($connection, $parameter, $command, $keyProperty, */ protected function raiseRowDelegate($handler, $param) { - if ($handler instanceof \Closure) { - $handler($this, $param); - } elseif (is_string($handler) || is_object($handler) && is_callable($handler)) { + if (is_string($handler) || is_object($handler) && is_callable($handler)) { call_user_func($handler, $this, $param); } elseif (is_array($handler) && is_callable($handler, true)) { // an array: 0 - object, 1 - method name/path diff --git a/src/Data/SqlMap/TSqlMapConfig.php b/src/Data/SqlMap/TSqlMapConfig.php index 8e322ad..899b7cc 100644 --- a/src/Data/SqlMap/TSqlMapConfig.php +++ b/src/Data/SqlMap/TSqlMapConfig.php @@ -158,7 +158,7 @@ protected function cacheSqlMapManager($manager) /** * Loads SqlMap manager from cache. - * @return TSqlMapManager SqlMap manager intance if load was successful, null otherwise. + * @return ?TSqlMapManager SqlMap manager intance if load was successful, null otherwise. */ protected function loadCachedSqlMapManager() { diff --git a/src/Data/SqlMap/TSqlMapGateway.php b/src/Data/SqlMap/TSqlMapGateway.php index 2b3ddc3..9751f66 100644 --- a/src/Data/SqlMap/TSqlMapGateway.php +++ b/src/Data/SqlMap/TSqlMapGateway.php @@ -89,7 +89,7 @@ public function queryForObject($statementName, $parameter = null, $result = null * pass in null if want to return a list instead. * @param int $skip The number of rows to skip over. * @param int $max The maximum number of rows to return. - * @return TList A List of result objects. + * @return array|\ArrayAccess A List of result objects. */ public function queryForList($statementName, $parameter = null, $result = null, $skip = -1, $max = -1) { @@ -110,7 +110,7 @@ public function queryForList($statementName, $parameter = null, $result = null, * pass in null if want to return a list instead. * @param int $skip The number of rows to skip over. * @param int $max The maximum number of rows to return. - * @return TList A List of result objects. + * @return array|\ArrayAccess A List of result objects. */ public function queryWithRowDelegate($statementName, $delegate, $parameter = null, $result = null, $skip = -1, $max = -1) { @@ -168,7 +168,7 @@ public function queryForPagedListWithRowDelegate($statementName, $delegate, $par * @param null|string $valueProperty The property of the result object to be used as the value. * @param int $skip The number of rows to skip over. * @param int $max The maximum number of rows to return. - * @return TMap Array object containing the rows keyed by keyProperty. + * @return array Array object containing the rows keyed by keyProperty. */ public function queryForMap($statementName, $parameter = null, $keyProperty = null, $valueProperty = null, $skip = -1, $max = -1) { @@ -189,7 +189,7 @@ public function queryForMap($statementName, $parameter = null, $keyProperty = nu * @param null|string $valueProperty The property of the result object to be used as the value. * @param int $skip The number of rows to skip over. * @param int $max The maximum number of rows to return. - * @return TMap Array object containing the rows keyed by keyProperty. + * @return array Array object containing the rows keyed by keyProperty. */ public function queryForMapWithRowDelegate($statementName, $delegate, $parameter = null, $keyProperty = null, $valueProperty = null, $skip = -1, $max = -1) { diff --git a/tests/unit/Data/SqlMap/SqlMapCacheTest.php b/tests/unit/Data/SqlMap/SqlMapCacheTest.php index d187e94..c85ff68 100644 --- a/tests/unit/Data/SqlMap/SqlMapCacheTest.php +++ b/tests/unit/Data/SqlMap/SqlMapCacheTest.php @@ -78,9 +78,7 @@ public function testFifoDelete() $fifo->set('k1', $object1); $fifo->set('k2', $object2); - // delete returns the removed object - $removed = $fifo->delete('k1'); - $this->assertSame($object1, $removed); + $this->assertTrue($fifo->delete('k1')); // key is gone $this->assertNull($fifo->get('k1')); @@ -96,20 +94,18 @@ public function testLruDelete() $lru->set('k1', $object1); $lru->set('k2', $object2); - $removed = $lru->delete('k1'); - $this->assertSame($object1, $removed); + $this->assertTrue($lru->delete('k1')); $this->assertNull($lru->get('k1')); $this->assertSame($object2, $lru->get('k2')); } - public function testDeleteExistingReturnsObjectAndRemovesIt() + public function testDeleteExistingReturnsTrueAndRemovesIt() { $fifo = new TSqlMapFifoCache(); $obj = new TComponent(); $fifo->set('k', $obj); - $removed = $fifo->delete('k'); - $this->assertSame($obj, $removed); + $this->assertTrue($fifo->delete('k')); $this->assertNull($fifo->get('k')); }