Skip to content

Commit 505d3b3

Browse files
committed
chore: prepare release 2.7.0
Complete changelog with all features added since 2.6.2: - Full-text search with cross-database support - Schema introspection (indexes, foreign keys, constraints) - Query result caching (PSR-16) for 10-1000x speedup - Advanced pagination (full, simple, cursor-based) - Export helpers (JSON, CSV, XML) - JSON file loading (JSON, JSONL) - Enhanced orderBy with array and string syntax - Read/Write connection splitting with load balancing - 57 comprehensive documentation files - Memory-efficient generators for file loading - Code architecture improvements Technical: 533 tests (+55), 2397 assertions, 93 examples
1 parent 5fa0f78 commit 505d3b3

File tree

2 files changed

+187
-11
lines changed

2 files changed

+187
-11
lines changed

CHANGELOG.md

Lines changed: 173 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,177 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
---
1111

12+
## [2.7.0] - 2025-10-28
13+
14+
### Added
15+
- **Full-Text Search** - Cross-database full-text search with `Db::fulltextMatch()` helper:
16+
- **MySQL**: `MATCH AGAINST` with FULLTEXT indexes
17+
- **PostgreSQL**: `@@` operator with `to_tsquery()` and text search vectors
18+
- **SQLite**: FTS5 virtual tables support
19+
- Support for multiple columns, search modes (natural language, boolean), and query expansion
20+
- `FulltextMatchValue` class and `FulltextSearchHelpersTrait` for implementation
21+
- Complete documentation in `documentation/03-query-builder/fulltext-search.md`
22+
- Examples in `examples/12-fulltext-search/` working on all three dialects
23+
- 12 new tests across MySQL, PostgreSQL, and SQLite
24+
25+
- **Schema Introspection** - Query database structure programmatically:
26+
- `PdoDb::getIndexes(string $table)` - Get all indexes for a table
27+
- `PdoDb::getForeignKeys(string $table)` - Get foreign key constraints
28+
- `PdoDb::getConstraints(string $table)` - Get all constraints (PRIMARY KEY, UNIQUE, FOREIGN KEY, CHECK)
29+
- `QueryBuilder::getIndexes()`, `getForeignKeys()`, `getConstraints()` - Same methods via QueryBuilder
30+
- Cross-database compatible across MySQL, PostgreSQL, and SQLite
31+
- Complete documentation in `documentation/03-query-builder/schema-introspection.md`
32+
- Tests in all dialect-specific test files
33+
34+
- **Query Result Caching** - PSR-16 (Simple Cache) integration for dramatic performance improvements:
35+
- **10-1000x faster** for repeated queries with caching enabled
36+
- `QueryBuilder::cache(?int $ttl = null, ?string $key = null)` - Enable caching for query
37+
- `QueryBuilder::noCache()` - Disable caching for specific query
38+
- `PdoDb::enableCache(CacheInterface $cache, ?CacheConfig $config = null)` - Enable caching globally
39+
- `PdoDb::disableCache()` - Disable caching globally
40+
- Support for all PSR-16 compatible cache implementations:
41+
- Symfony Cache, Redis, APCu, Memcached, Filesystem adapters
42+
- Built-in `ArrayCache` for testing
43+
- Automatic cache key generation based on SQL and parameters
44+
- Custom cache keys and TTL support
45+
- `CacheManager`, `CacheConfig`, `QueryCacheKey` infrastructure classes
46+
- Complete documentation in `documentation/05-advanced-features/query-caching.md`
47+
- Examples in `examples/14-caching/` demonstrating real-world usage
48+
- Comprehensive test coverage in `SharedCoverageTest`
49+
50+
- **Advanced Pagination** - Three pagination types for different use cases:
51+
- **Full pagination** (`paginate(int $page, int $perPage)`): Complete pagination with total count and page numbers
52+
- Returns `PaginationResult` with items, total, current page, last page, per page
53+
- Best for UI with page numbers and "showing X of Y results"
54+
- **Simple pagination** (`simplePaginate(int $page, int $perPage)`): Fast pagination without total count
55+
- Returns `SimplePaginationResult` with items, has more pages flag
56+
- No COUNT query overhead - ideal for large datasets
57+
- **Cursor-based pagination** (`cursorPaginate(?string $cursor, int $perPage, string $column = 'id')`): Scalable pagination for real-time feeds
58+
- Returns `CursorPaginationResult` with items, next cursor, previous cursor
59+
- Consistent results even with new data
60+
- Perfect for infinite scroll and API endpoints
61+
- All results implement `JsonSerializable` for easy API responses
62+
- URL generation support with query parameters
63+
- Complete documentation in `documentation/05-advanced-features/pagination.md`
64+
- Examples in `examples/13-pagination/` working on all dialects
65+
- Comprehensive test coverage for all pagination types
66+
67+
- **Export Helpers** - Export query results to various formats:
68+
- `Db::toJson($data, int $options = 0)` - Export to JSON with customizable encoding options
69+
- Support for pretty printing, unescaped unicode, unescaped slashes
70+
- `Db::toCsv($data, string $delimiter = ',', string $enclosure = '"')` - Export to CSV format
71+
- Custom delimiter and enclosure character support
72+
- `Db::toXml($data, string $rootElement = 'root', string $itemElement = 'item')` - Export to XML format
73+
- Customizable root and item element names
74+
- Support for nested arrays and complex data structures
75+
- SOLID architecture with separate exporter classes (`JsonExporter`, `CsvExporter`, `XmlExporter`)
76+
- Complete documentation in `documentation/07-helper-functions/export-helpers.md`
77+
- Examples in `examples/11-export-helpers/` demonstrating all export formats
78+
- Comprehensive test coverage in `SharedCoverageTest`
79+
80+
- **JSON File Loading** - Bulk load JSON data directly into database tables:
81+
- `QueryBuilder::loadJson(string $filename, bool $update = false, ?array $updateFields = null)` - Load JSON array of objects
82+
- `QueryBuilder::loadJsonLines(string $filename, bool $update = false, ?array $updateFields = null)` - Load newline-delimited JSON (JSONL/NDJSON)
83+
- Support for nested JSON with automatic encoding
84+
- Memory-efficient batch processing for large files
85+
- Works across all three database dialects (MySQL, PostgreSQL, SQLite)
86+
- UPSERT support with optional update fields
87+
- Complete documentation in `documentation/05-advanced-features/file-loading.md`
88+
- Examples in `examples/05-file-loading/` with practical scenarios
89+
90+
- **Enhanced orderBy()** - Flexible sorting with multiple syntax options:
91+
- **Array syntax with directions**: `orderBy(['name' => 'ASC', 'created_at' => 'DESC'])`
92+
- **Array with default direction**: `orderBy(['name', 'email'], 'DESC')`
93+
- **Comma-separated string**: `orderBy('name ASC, email DESC, id')`
94+
- **Original syntax still works**: `orderBy('name', 'DESC')`
95+
- Full backward compatibility with existing code
96+
- Complete documentation and examples in `documentation/03-query-builder/ordering-pagination.md`
97+
- New example file `examples/01-basic/05-ordering.php` demonstrating all syntax variants
98+
- Comprehensive test coverage in `SharedCoverageTest`
99+
100+
- **Read/Write Connection Splitting** - Master-replica architecture for horizontal scaling:
101+
- **Automatic query routing**: SELECT queries → read replicas, DML operations → write master
102+
- **Three load balancing strategies**:
103+
- `RoundRobinLoadBalancer` - Even distribution across replicas
104+
- `RandomLoadBalancer` - Random replica selection
105+
- `WeightedLoadBalancer` - Weight-based distribution (e.g., 70% replica-1, 30% replica-2)
106+
- **Sticky writes**: Read-after-write consistency (reads from master for N seconds after write)
107+
- **Force write mode**: `QueryBuilder::forceWrite()` to explicitly read from master
108+
- **Transaction support**: All operations in transactions use write connection
109+
- **Health checks and failover**: Automatic exclusion of failed connections
110+
- Type-safe with `ConnectionType` enum (`READ`, `WRITE`)
111+
- New classes: `ConnectionRouter`, `LoadBalancerInterface` + 3 implementations
112+
- New methods: `enableReadWriteSplitting()`, `disableReadWriteSplitting()`, `enableStickyWrites()`, `disableStickyWrites()`, `getConnectionRouter()`
113+
- Complete documentation in `documentation/05-advanced-features/read-write-splitting.md`
114+
- Three examples in `examples/15-read-write-splitting/` demonstrating all features
115+
- 13 new tests with 24 assertions in `SharedCoverageTest`
116+
117+
- **Comprehensive Documentation** - 57 new documentation files (~12,600 lines):
118+
- **Getting Started**: Installation, configuration, first connection, hello world
119+
- **Core Concepts**: Connection management, dialect support, parameter binding, query builder basics
120+
- **Query Builder**: All operations (SELECT, INSERT, UPDATE, DELETE, JOINs, aggregations, subqueries, etc.)
121+
- **JSON Operations**: Complete JSON support across all dialects
122+
- **Advanced Features**: Transactions, bulk operations, batch processing, file loading, pagination, query caching, connection retry, read/write splitting
123+
- **Error Handling**: Exception hierarchy, error codes, logging, monitoring, retry logic
124+
- **Helper Functions**: 80+ helpers organized by category (string, math, date, JSON, aggregate, comparison, etc.)
125+
- **Best Practices**: Security, performance, memory management, common pitfalls, code organization
126+
- **Reference**: Complete API reference, dialect differences, method documentation
127+
- **Cookbook**: Real-world examples, migration guide, troubleshooting, common patterns
128+
- README.md files added to all example subdirectories for better navigation
129+
130+
### Changed
131+
- **Memory-efficient file loading**: Refactored `loadCsv()` and `loadXml()` to use PHP generators
132+
- Dramatically reduced memory consumption for large files
133+
- Added `loadFromCsvGenerator()` and `loadFromXmlGenerator()` in `FileLoader` class
134+
- Batched processing to handle files larger than available RAM
135+
- MySQL and PostgreSQL keep native commands but wrapped as generators
136+
- All 478+ tests passing with improved performance
137+
138+
- **Code architecture improvements**:
139+
- Removed `ParameterManager` class (functionality integrated into other components)
140+
- Changed method/property visibility from `private` to `protected` for better extensibility
141+
- Renamed schema introspection methods for consistency
142+
- Updated return types in `TableManagementTrait` from `static` to `self`
143+
144+
- **Test refactoring for best practices**:
145+
- Replaced direct `$db->connection` calls with public API methods
146+
- Use `$db->rawQuery()` instead of `$db->connection->query()`
147+
- Use `$db->startTransaction()`, `$db->commit()`, `$db->rollback()` instead of direct connection calls
148+
- Direct connection access only in tests specifically testing `ConnectionInterface`
149+
150+
- **Enhanced README.md**:
151+
- Restructured feature list with bold categories and detailed descriptions
152+
- Added all new features with performance metrics (e.g., "10-1000x faster queries")
153+
- More scannable and impressive presentation
154+
- Complete table of contents with all sections
155+
156+
- **QueryBuilder enhancements**:
157+
- Dynamic connection switching for read/write splitting
158+
- Cache-aware query execution
159+
- Enhanced ordering with multiple syntax options
160+
161+
### Fixed
162+
- **Invalid release date**: Fixed incorrect date in CHANGELOG.md for v2.6.1
163+
- **Cross-dialect compatibility**: Exception handling examples now work correctly on MySQL, PostgreSQL, and SQLite
164+
- Added dialect-specific CREATE TABLE statements
165+
- Added DROP TABLE IF EXISTS for idempotency
166+
- All examples pass on all three dialects
167+
168+
### Technical Details
169+
- **All tests passing**: 533 tests, 2397 assertions (+55 tests from 2.6.2)
170+
- **All examples passing**: 93/93 examples (31 files × 3 dialects each)
171+
- SQLite: 31/31 ✅
172+
- MySQL: 31/31 ✅
173+
- PostgreSQL: 31/31 ✅
174+
- **PHPStan Level 8**: Zero errors across entire codebase
175+
- **PHP-CS-Fixer**: All code complies with PSR-12 standards
176+
- **Full backward compatibility**: 100% maintained - all existing code continues to work
177+
- **Code quality**: Follows KISS, SOLID, DRY, YAGNI principles
178+
- **Documentation**: 57 comprehensive documentation files covering all features
179+
- **Performance**: Memory-efficient generators, optional query caching for 10-1000x speedup
180+
181+
---
182+
12183
## [2.6.2] - 2025-10-25
13184

14185
### Added
@@ -542,7 +713,8 @@ Initial tagged release with basic PDO database abstraction functionality.
542713

543714
---
544715

545-
[Unreleased]: https://github.com/tommyknocker/pdo-database-class/compare/v2.6.2...HEAD
716+
[Unreleased]: https://github.com/tommyknocker/pdo-database-class/compare/v2.7.0...HEAD
717+
[2.7.0]: https://github.com/tommyknocker/pdo-database-class/compare/v2.6.2...v2.7.0
546718
[2.6.2]: https://github.com/tommyknocker/pdo-database-class/compare/v2.6.1...v2.6.2
547719
[2.6.1]: https://github.com/tommyknocker/pdo-database-class/compare/v2.6.0...v2.6.1
548720
[2.6.0]: https://github.com/tommyknocker/pdo-database-class/compare/v2.5.1...v2.6.0

tests/SharedCoverageTest.php

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@
1515
use RuntimeException;
1616
use tommyknocker\pdodb\connection\Connection;
1717
use tommyknocker\pdodb\connection\ConnectionInterface;
18+
use tommyknocker\pdodb\connection\ConnectionType;
19+
use tommyknocker\pdodb\connection\loadbalancer\RandomLoadBalancer;
20+
use tommyknocker\pdodb\connection\loadbalancer\RoundRobinLoadBalancer;
21+
use tommyknocker\pdodb\connection\loadbalancer\WeightedLoadBalancer;
1822
use tommyknocker\pdodb\connection\RetryableConnection;
1923
use tommyknocker\pdodb\dialects\DialectInterface;
2024
use tommyknocker\pdodb\exceptions\AuthenticationException;
@@ -3375,32 +3379,32 @@ public function testLoadBalancers(): void
33753379
$db = new PdoDb();
33763380

33773381
// Test with RoundRobin
3378-
$db->enableReadWriteSplitting(new \tommyknocker\pdodb\connection\loadbalancer\RoundRobinLoadBalancer());
3382+
$db->enableReadWriteSplitting(new RoundRobinLoadBalancer());
33793383
$this->assertInstanceOf(
3380-
\tommyknocker\pdodb\connection\loadbalancer\RoundRobinLoadBalancer::class,
3384+
RoundRobinLoadBalancer::class,
33813385
$db->getConnectionRouter()->getLoadBalancer()
33823386
);
33833387

33843388
// Test with Random
3385-
$db->enableReadWriteSplitting(new \tommyknocker\pdodb\connection\loadbalancer\RandomLoadBalancer());
3389+
$db->enableReadWriteSplitting(new RandomLoadBalancer());
33863390
$this->assertInstanceOf(
3387-
\tommyknocker\pdodb\connection\loadbalancer\RandomLoadBalancer::class,
3391+
RandomLoadBalancer::class,
33883392
$db->getConnectionRouter()->getLoadBalancer()
33893393
);
33903394

33913395
// Test with Weighted
3392-
$weighted = new \tommyknocker\pdodb\connection\loadbalancer\WeightedLoadBalancer();
3396+
$weighted = new WeightedLoadBalancer();
33933397
$weighted->setWeights(['read-1' => 2, 'read-2' => 1]);
33943398
$db->enableReadWriteSplitting($weighted);
33953399
$this->assertInstanceOf(
3396-
\tommyknocker\pdodb\connection\loadbalancer\WeightedLoadBalancer::class,
3400+
WeightedLoadBalancer::class,
33973401
$db->getConnectionRouter()->getLoadBalancer()
33983402
);
33993403
}
34003404

34013405
public function testLoadBalancerMarkFailedAndHealthy(): void
34023406
{
3403-
$balancer = new \tommyknocker\pdodb\connection\loadbalancer\RoundRobinLoadBalancer();
3407+
$balancer = new RoundRobinLoadBalancer();
34043408

34053409
$balancer->markFailed('read-1');
34063410
$balancer->markHealthy('read-1');
@@ -3411,7 +3415,7 @@ public function testLoadBalancerMarkFailedAndHealthy(): void
34113415

34123416
public function testLoadBalancerReset(): void
34133417
{
3414-
$balancer = new \tommyknocker\pdodb\connection\loadbalancer\RoundRobinLoadBalancer();
3418+
$balancer = new RoundRobinLoadBalancer();
34153419

34163420
$balancer->markFailed('read-1');
34173421
$balancer->reset();
@@ -3422,8 +3426,8 @@ public function testLoadBalancerReset(): void
34223426

34233427
public function testConnectionTypeEnum(): void
34243428
{
3425-
$readType = \tommyknocker\pdodb\connection\ConnectionType::READ;
3426-
$writeType = \tommyknocker\pdodb\connection\ConnectionType::WRITE;
3429+
$readType = ConnectionType::READ;
3430+
$writeType = ConnectionType::WRITE;
34273431

34283432
$this->assertEquals('read', $readType->value);
34293433
$this->assertEquals('write', $writeType->value);

0 commit comments

Comments
 (0)