Revision history for DBIx-Fast

0.21	2026-10-06
	- Connection registry: configure(name => {...}) + connect(name), one
	  instance per name per process (rebuilt after fork)
	- stream(): query() without loading the result set into memory
	  (MariaDB/MySQL use_result; 200k rows: +45 MB -> +0 MB)
	- cached(): tag invalidation shared across processes with an external
	  backend (tag versions in the backend); DBIx::Fast::Cache::Redis
	  backend over Redis::Fast. With an external backend, invalidate()
	  now returns 1 instead of a per-process key count
	- after_write => sub {...}: hook called after every CRUD write (audit)
	- DBIx::Fast::Test::Mock: unit tests without a server (canned answers,
	  real SQL of the chosen dialect, recorded statements)
	- on_slow_query => { ms, cb }: report slow statements
	- metrics => 1, metrics(), metrics_text(): statement counters, duration
	  histogram and errors by class in Prometheus format
	- configure_file(): registry connections from a JSON file
	- Writes 32-42% faster than 0.20 (insert/update/delete/upsert) and
	  all() 16-19% faster on SQLite; 5-11% on MariaDB: no per-write
	  closures, fewer method calls, validated-identifier memo, rows built
	  from the driver's C fetchall
	- Pg: insert/insert_ignore return the id with INSERT ... RETURNING (one
	  round trip; last_insert_id cost another: insert ~2x faster on Pg)
	- val()/hash()/all() 31/24/9% faster than the first 0.21 build on SQLite:
	  fast path for cached statements, and Connector::dbh's common case in
	  a method touching 3 fields instead of 11 (Object::Pad binds every
	  field a method mentions): 428 -> 282 ns per query
	- Bounded caches (statements, CRUD SQL, identifiers) shed a quarter of
	  their entries when full instead of emptying (no burst of misses)
	- json_columns: write column sets memoized per table; reads decode only
	  the configured columns the result actually has
	- DBD::SQLite, Test::More, Test::Exception are test-only prerequisites
	  (TEST_REQUIRES) - no longer installed for a runtime install

0.20	2026-10-06
	- Reads ~10-15% faster: own statement cache instead of DBI's (Perl)
	  prepare_cached, and no per-call closure when no tracker is active
	- query() iterator: no warning when it outlives a closed connection
	- IN (?) with an arrayref expands to one placeholder per value
	- where(): WHERE clause from filters (undef skipped; IN, CONTAINS...)
	- page(): rows + total + pages, ORDER BY from a whitelist
	- like(): literal LIKE patterns (ESCAPE '!'); in_chunks() for long lists
	- count()/exists(): IN, NOT IN, CONTAINS, PREFIX, SUFFIX operators
	- upsert/upsert_many: update modes 'new', '+', { '+' => N }, \'SQL'
	- insert_ignore(); find_or_create() (race-safe via the UNIQUE index)
	- insert_many/upsert_many: chunk => N; insert_many: ignore => 1
	- timestamps => {...}: automatic created/updated columns per table
	- json_columns => {...}: encode on write, decode on read
	- Fix: rows after a write uses execute()'s count (DBD::Pg ON CONFLICT)

0.19	2026-09-28
	- Typed exceptions (DBIx::Fast::X::Duplicate, ::Deadlock, ...); same text
	- Every module has $VERSION; META provides (kwalitee all green)
	- Changes in CPAN::Changes format; README regenerated from the POD
	- examples/errors-and-reads.pl: 0.18 error handling and read helpers
	- Every module: =encoding utf8, SYNOPSIS and SEE ALSO (enforced by a test)
	- META homepage and license URL; CONTRIBUTING.md
	- examples/benchmark-json.pl: Cpanel::JSON::XS vs JSON::PP
	- Fork safety: a forked child no longer drops the parent's MariaDB
	  connection (DBD::MariaDB 1.24 disconnect_all bug, upstream PR #175)
	- Optional POSIX::AtFork: fork check without getpid() (reads ~10% faster)
	- Fix: MariaDB/MySQL analyze_indexes() never ran on a real server (reserved
	  alias `reads`, ONLY_FULL_GROUP_BY, missing sql_text column)

0.18	2026-09-28
	- CRUD: stricter WHERE/data validation (SQL::Abstract raw-SQL operators)
	- CRUD: statement cache for insert/update/delete/upsert (~10x faster)
	- upsert/insert_many/upsert_many: reject unbindable references
	- txn(): works with AutoCommit => 0
	- DSN: keep user without password; percent-decoding; reject ';' in parts
	- load_extension(): only accepts module names
	- MariaDB profiler: EXPLAIN runs with max_statement_time
	- Pg schema: table_info/get_indexes are schema-aware
	- Docs: insert_many last_id is driver-dependent
	- Errors record the failing sql and binds (redacted with errors_redact)
	- on_error callback; error_mark/errors_since to scope errors
	- Exception text without timestamp (groupable in logs)
	- strict_reads: failed reads throw even with RaiseError => 0
	- RaiseError => 0: a read whose prepare fails returns empty (no crash)
	- rows: affected rows of the last write
	- exists(), kv(), all_by() helpers
	- on_connect_sql: session SQL re-applied on every reconnect

0.17	2026-08-20
	- DSN parsing is more accepting (strictly additive - no DSN that
	  worked before changes): the native "dbi:..." prefix is
	  case-insensitive; "dbi:SQLite::memory:" and other DSNs whose
	  driver-specific part contains colons are no longer rejected;
	  and a credential-less URI ("mariadb://localhost/db", socket /
	  peer auth) is accepted. Verified byte-identical output for every
	  previously-valid DSN.
	- The vestigial "quote" constructor option no longer runs a dead
	  $dbh->quote() no-op on connect; it is still accepted (and stored
	  in args) for backward compatibility but has no effect - identifier
	  quoting is automatic and driver-aware.
	- Security: the CRUD operator whitelist now covers non-hash WHERE
	  specs. update()/delete() accept SQL::Abstract's arrayref
	  (OR-of-conditions) and scalarref (literal SQL) WHERE shapes;
	  _safe_columns bailed out silently on anything that was not a
	  hash, letting an injected operator or literal SQL through when
	  the WHERE was built as an arrayref/scalarref from untrusted
	  input. Now validated recursively; scalarref WHERE is rejected.
	  Regression test in t/security.t.
	- Fix: read methods (all/flat/hash/val/array/count) use the 3-arg
	  prepare_cached form, so a live query() iterator on the same SQL
	  is no longer finished out from under the caller (silent
	  truncation, a 0.17 regression).
	- Fix: execute() with no bind argument clears any $p left from a
	  previous query instead of binding it to the new statement.
	- Fix: qualified table names ("schema.table") work in count(),
	  upsert(), insert_many(), upsert_many() and Schema introspection
	  (were quoted as one identifier via _quote_id; now per-segment
	  via _quote_ident_path, matching insert/update/delete).
	- Fix: Schema tables cache is a hashref when the database has no
	  tables (tn => 1 on an empty DB gave a misleading crash).
	- Fix: cache => 0 (or '') no longer switches caching on; the guard
	  is truthiness, not defined-ness.
	- Fix: a transaction clears its savepoints on successful commit
	  too, so a later rollback_to() cannot fire on a stale name.
	- Fix: t/security.t guards its LRU::Cache require (was bare, would
	  fail on smokers without the optional backend); Result.pm POD
	  link to query() points at DBIx::Fast::SQL; META repository url
	  is a valid https URI (an SSH spec was silently dropped).

	[ New ]
	- DBIx::Fast::Connector: fork-safe lazy connections, throttled
	  ping, automatic reconnect (replaces DBIx::Connector).
	- query() row-by-row iterator via DBIx::Fast::Result.
	- Query cache (DBIx::Fast::Cache / Cached): TTL, tag invalidation,
	  LRU or CHI backends, CRUD auto-invalidation.
	- upsert() / insert_many() / upsert_many(), driver-aware.
	- txn(isolation => ...), whitelist-validated and restored on exit.
	- DBIx::Fast::Profile::Pg: native PostgreSQL diagnostics.
	- DBIx::Fast::Profile::mysql: native MySQL diagnostics (inherits
	  the MariaDB profile; overrides EXPLAIN, removed-in-8.0 EXTENDED).
	- DBIx::Fast::Output: unified profiler output - text (default),
	  JSON lines or callback; profile_output / $obj->output(...).
	- now_utc(); examples/profiler-pg.pl.

	[ Security ]
	- SQL injection hardening: operator whitelists for all CRUD
	  WHERE/SET specs; identifiers validated AND quoted everywhere.
	- Credentials never echoed: connect/DSN errors redacted;
	  errors_redact also scrubs backticks.
	- Fork safety: AutoInactiveDestroy + pid-guarded disconnect.
	- Cache keys include method + connection identity.
	- Deadlock retry gated on driver error codes, not message text;
	  isolation-restore failure closes the connection (fail closed).
	- EXPLAIN allowlists (MariaDB/Pg) resist literal-quoting tricks,
	  file access and DoS functions; terminal escapes stripped.

	[ Fixes ]
	- Transaction retry/rollback state, savepoint sequencing, memory
	  leak (weakened back-refs), val() first-column contract.
	- DSN parsing: host/port, ':' in passwords, postgres:// schemes,
	  driver-correct utf8 flags; Pg isolation and last_insert_id.
	- Cache: bounded tag index, TTL pushed down to CHI, honest stats.
	- Profiler text output degrades gracefully on bare installs:
	  Cpanel::JSON::XS / Term::ANSIColor are recommends, so print_query
	  falls back to core JSON::PP and colorless output when absent.
	- Profile::MariaDB information_schema queries carry explicit
	  lowercase aliases (MySQL 8 uppercases unaliased columns) and
	  get_processes no longer uses the MariaDB-only time_ms column.
	- Exceptions: Exception() honors errors_redact, attaches last_error
	  only when recent, POD documents the real always-croak contract;
	  a failed transaction records its error once, not three times.

	[ Performance ]
	- fetchall_arrayref C-loop fetches, prepare_cached everywhere,
	  lazy SQL::Abstract, cheaper profiler/cache internals.

	[ Packaging ]
	- Query/CRUD methods live in the DBIx::Fast::SQL role, composed
	  into DBIx::Fast - identical API, the main module is half the
	  size and each file documents its own methods.
	- Test suite coherence pass: the committed fixture t/db/test.db is
	  read-only (writing suites use tempdirs; regenerated from
	  t/db/schema.sql, 455KB of stale rows dropped), duplicate suites
	  merged (02-schema/dbix-fast/driver MariaDB.t), live tests skip
	  cleanly when the server is down, tables are per-file prefixed
	  and cleaned up, and the whole suite passes under prove -j.
	  TableName() format-validates unconditionally (insert/update had
	  accepted names count/upsert rejected).
	- Optional deps as recommends; whole distro on Object::Pad; full
	  POD with coverage tests; 500+ tests on SQLite/MariaDB/Pg/MySQL
	  (CI service containers); LICENSE, SECURITY.md, README.md.

0.161	2026-03-21
	- Connection mode: ping → fixup (eliminates ping overhead per query)

0.16	2026-03-20
	- Migration from Moo to Object::Pad (requires Perl v5.38+)
	- New: tracker() - query profiling across all methods (all/hash/val/flat/array/exec/insert/update/delete)
	- New: Profile::Base, Profile::MariaDB, Profile::SQLite (driver-specific diagnostics)
	- New: Transaction module with nested support, savepoints, deadlock retry
	- Removed: up2() (unused), Query.pm (unused), execute_prepare2 (duplicate), make_sen old (buggy)
	- Renamed: make_senN → make_sen (correct implementation)
	- Performance: statement caching (prepare_cached), inlined query methods, _make_where with join
	- Bug fixes: Schema typos, HandleError return 0, Time::HiRes import, `rows` keyword MariaDB 11+
	- Bug fixes: Profile::MariaDB init (execute→exec), session stats columns, getrusage optional
	- Tests: 147 SQLite + 34 MariaDB + 14 profiler
	- Updated CI: Perl 5.38/5.40, bookworm, Object::Pad
	- Full POD documentation on all 8 modules
	- Added examples/profiler-mariadb.pl

0.15	2024-09-06
	- args : SQLite
	
0.14	2024-09-03
	- POD
	
0.13	2024-09-01
	- t/exception.t
	- Clean
	
0.12	2024-09-01
	- Test
	- DSN / URI
	- _check_dsn / _make_dsn_dbi
	
0.11	2024-09-01
	- SQL::Abstract
	
0.10	2024-09-01
	- _make_dsn
	- _check_dsn
	- driver && dbd
	- t/functions.t
	
0.09	2020-01-01
	
0.08	2016-01-26
	- Force UTF8
	
0.07	2016-01-24
	
0.06	2015-03-23

0.05	2015-03-18T03:30:38+01:00
	- More test
	- Method : array

0.04	2015-03-18T00:59:19+01:00
	- Test

0.03	2015-03-17T00:50:31+01:00
	- New methods : value & count
	- Dereference hash reference for using 'keys'

0.02	2015-03-17T00:20:48+01:00
	- extra_args -> time NOW()

0.01	Unknown Release Date