Revision history for DBIx::Class::Async

v1.0.5   2026-07-13
         [POD CHANGES]
         - Used App:genstopwords v0.0.1 to generate .stopwords
         - Tidied up Makefile.PL script.
         - Cosmetic Changes to pod document.

v1.0.4   2026-05-31
         [BUG FIXES]
         - Fixed a SEGV (signal 11) during Perl global destruction when
           using two or more workers. The root cause was an incorrect
           teardown order in DBIx::Class::Async::disconnect(): calling
           $loop->remove() after $instance->stop() causes IO::Async to
           set an internal {stopping} flag that silently prevents remove()
           from deregistering the notifier. The orphaned Stream, Handle,
           and Process child notifiers remained in the loop's internal
           registry, and the loop's XS destructor accessed freed memory
           during global destruction. Fixed by calling $loop->remove()
           before $instance->stop(), then awaiting the returned Future so
           workers complete their shutdown sequence before cleanup proceeds.
           (Reported during Debian packaging; triggered by make -j32 test.)

         - Fixed DBIx::Class::Async::Schema::disconnect() which was
           performing its own broken worker cleanup instead of delegating
           to DBIx::Class::Async::disconnect(). The original code called
           stop() on the worker hashref ({ instance => .., healthy => .. })
           rather than on the IO::Async::Function object it contained,
           meaning workers were never actually stopped. It then wiped
           %{$self->{_async_db}} = (), destroying all state including the
           loop reference before any cleanup could happen.
           Schema::disconnect() now delegates entirely to
           DBIx::Class::Async::disconnect() for correct, ordered teardown.

         - Fixed clone() creating a full independent worker pool (new forked
           processes) for every call, including in contexts where no
           isolation was needed. With many clones sharing the same
           IO::Async::Loop, the concurrent teardown of dozens of
           IO::Async::Function instances caused the loop's XS destructor to
           SEGV during global destruction. clone() now shares the parent's
           worker pool by default. An independent pool is only created when
           workers => $count is explicitly passed.

         - Fixed the async_loop option not being recognised in
           create_async_db(). Schema::connect() passes the loop via
           async_loop => but create_async_db() checked only for loop =>,
           causing _owns_loop to be incorrectly set to true for all
           user-supplied loops. create_async_db() now consistently uses
           async_loop => as the canonical parameter name throughout.

         - Added DESTROY() to DBIx::Class::Async::Schema. Schema objects
           that go out of scope during the RUN phase now automatically call
           disconnect() to stop their worker processes. DESTROY() is a no-op
           during global destruction (${^GLOBAL_PHASE} eq 'DESTRUCT') where
           IO::Async's XS internals may already be partially freed, and is
           also a no-op for shared clones which don't own their worker pool.

         [BEHAVIOUR CHANGE]
         - clone() now shares the parent's worker pool by default instead of
           spawning a new pool of processes. This is a safe change for the
           common case (clones connect to the same database and don't require
           process isolation), and eliminates a class of SEGV that occurred
           when many clones were created and destroyed within the same event
           loop.

           To obtain an independent worker pool (the previous behaviour),
           pass workers => $count explicitly:

               # Before (always independent - now only when workers given):
               my $clone = $schema->clone;

               # After (shared pool, auto-cleanup, safe default):
               my $clone = $schema->clone;

               # Independent pool (e.g. dedicated reporting workers):
               my $reporting = $schema->clone( workers => 8 );
               # ... use $reporting ...
               $reporting->disconnect;   # caller must disconnect

           Independent clones (created with workers =>) own their worker
           pool. Callers are responsible for calling disconnect() on them
           before they go out of scope. Shared clones (no workers =>) are
           cleaned up automatically.

         [DOCUMENTATION]
         - Updated POD for clone() in DBIx::Class::Async::Schema to document
           the shared vs. independent pool distinction and the disconnect()
           requirement for independent clones.

         - Updated POD for create_async_db() in DBIx::Class::Async to
           document all options in a structured list, including the
           async_loop alias for loop and the ownership semantics (who stops
           the loop on disconnect).

         - Updated POD for disconnect() in DBIx::Class::Async to document
           the required remove()-before-stop() ordering and the reason it
           matters for XS safety during global destruction.

         [TESTS]
         - Updated t/001-schema.t and t/027-schema-class.t to reflect the
           new clone() behaviour: clones now share _async_db with the parent
           rather than holding an independent instance.

         - t/103-clone.t continues to pass without modification: clone()
           called with workers => $count still creates an independent pool
           with the requested worker count and a distinct _async_db.

v1.0.3   2026-05-06
         [IMPROVEMENTS]
         - Fix non-deterministic failures in t/020-slice.t by forcing
           'name' sort order, ensuring consistent results across
           asynchronous worker insertions.
         - Improve reliability of caching tests (t/148-caching.t and
           t/149-scalar-ref-char-pk.t) by using high-precision fractional
           seconds with SQLite's strftime.
         - Explicitly disable Resultset caching in t/148-caching.t using
           'cache_ttl => 0' to verify cache-bypass logic regardless of
           execution speed.
         - Fix incorrect HashRefInflator accessors in test suites.

v1.0.2   2026-05-06
         [IMPROVEMENTS]
         - Tidied up unit test for Oracle and MySQL to use deploy().

v1.0.1   2026-05-06
         [IMPROVEMENTS]
         - Added MySQL database support and verified the following features
           work correctly against MySQL 8.0.45 (Ubuntu 24.04):
           CRUD operations, search/count/all, belongs_to and has_many
           relationships, transactions via txn_do(), and concurrent async
           queries across multiple workers.

         - Added xt/002-mysql.t as an author integration test for MySQL.
           Requires DBD::mysql. Connection details are configurable via
           environment variables:
           DBIC_ASYNC_MYSQL_DSN,
           DBIC_ASYNC_MYSQL_USER,
           DBIC_ASYNC_MYSQL_PASS.

         - Updated POD documenting MySQL compatibility and instructions for
           running the MySQL test suite.

v1.0.0   2026-05-01
         [IMPROVEMENTS]
         - PAUSE didn't like the v-string move:
                Status: Decreasing version number
           So, bumped the version to v1.0.0.

v0.66.0  2026-05-01
         [IMPROVEMENTS]
         - Switched to v-string versioning (semantic versioning going forward).

         - Added Oracle database support and verified the following features
           work correctly against Oracle AI Database 23ai Free (via Docker):
           CRUD operations, search/count/all, belongs_to and has_many
           relationships, transactions via txn_do(), and concurrent async
           queries across multiple workers.

         - Added xt/001-oracle.t as an author integration test for Oracle.
           Requires DBD::Oracle and Math::Base36 >= 0.07. Connection details
           are configurable via environment variables:
           DBIC_ASYNC_ORACLE_DSN,
           DBIC_ASYNC_ORACLE_USER,
           DBIC_ASYNC_ORACLE_PASS.

         - Added DATABASE SUPPORT section to main POD documenting Oracle
           compatibility and instructions for running the Oracle test suite.

0.65 2026-03-15
     [IMPROVEMENTS]
     - Added security document SECURITY.md as recommended in the issue #11.

0.64 2026-03-02
     [BUG FIXES]
     - Fix InflateColumn::DateTime inflation on async worker path (PR#138)
       Workers correctly inflate datetime columns but deserialise results
       back to the parent as raw strings. The parent holds no active DB
       connection so ->storage returns nothing; formatter detection now
       reads the DSN once at connect time and stores it on _async_db
       as _datetime_formatter.

     - _inflate_row re-inflates datetime columns in the parent using this
       formatter, and sets it on the resulting DateTime object so that
       stringification produces database-native output rather than
       DateTime's bare ISO8601 format.

       The fix is gated behind isa('DBIx::Class::InflateColumn::DateTime')
       and is a no-op for schemas that do not load that component.

     [IMPROVEMENTS]
     - Updated pod spelling test script to use Test::Spelling::Stopwords.

0.63 2026-02-27
     [BUG FIXES]
     - delete() on a ResultSet with group_by and/or having
       would cause DBIx::Class to generate a DELETE subquery with
       GROUP BY silently stripped, producing invalid SQL that most
       databases reject. Fixed by detecting group_by/having,
       building a clean copy of the attrs with those keys removed,
       and re-fetching matching PKs via a plain WHERE search before
       issuing delete_all().

     - Fixed delete() attribute routing: the previous guard
       triggered the two-query delete_all() path whenever the
       ResultSet had any attribute at all, including harmless ones
       such as result_class, order_by, and prefetch, causing
       unnecessary extra queries. The check is now restricted to
       the six attributes that genuinely make a direct DELETE unsafe:
       rows, offset, and page (LIMIT/OFFSET), join (multi-table DELETE),
       and group_by/having (see above). ResultSets carrying only
       ordering or prefetch attributes now issue a direct single-query
       DELETE.

     [ENHANCEMENTS]
     - SelectNormaliser is now applied automatically inside search()
       after attribute merging and before the new ResultSet is constructed.
       Any { -ident => $col, -as => $alias } items in the select list
       are rewritten to bare column strings at this point, so every
       downstream operation flowing through search() -- all, count,
       count_total, update, and so on -- receives clean attributes without
       requiring individual normalisation calls at each call site.

     [TESTS]
     - Added t/154-grouped-delete.t covering delete() on ResultSets with
       group_by, having, rows (LIMIT), and join, plus a regression guard
       for plain ungrouped delete.  Tests use a live SQLite database via
       the standard TestSchema fixture.

     - Added t/155-select-normaliser.t covering _is_ident_hashref
       classification, all normalise() input forms (bare strings, -ident
       with and without -as, function hashrefs, literal SQL refs, mixed
       lists, scalar and hashref non-array inputs), as/select length
       mismatches, normalise_attrs() key preservation and no-mutation
       guarantee, and a structural proof that the normalised output
       contains no -ident hashrefs. No live database required.

0.62 2026-02-25
     [NEW FEATURES]
     - Added as_subselect_rs() method for wrapping queries as subselects.
       Correctly preserves column lists from the original ResultSet.
       When you select a subset of columns, the subselect will only include
       those columns, not all columns from the table. This enables complex
       query patterns with derived tables, limited subqueries, and
       multi-stage aggregations.

     - Added support for empty SELECT lists (select => []). Generates
       database-appropriate SQL: "SELECT FROM table" for PostgreSQL,
       "SELECT 1 FROM table" for other databases. This addresses
       compatibility with PostgreSQL's column-less SELECT syntax and
       resolves the limitation inherited from DBIx::Class core.

     - Added DBIx::Class::Async::Exception, a structured exception class
       hierarchy consistent with the DBIx::Class::Exception interface.
       Exceptions stringify to their message, support boolean overload,
       and provide throw/rethrow.

     - Added DBIx::Class::Async::Exception::Factory to translate raw
       DBIx::Class error strings into typed exception objects automatically,
       including improved diagnostics (relationship name passed as undef
       column value). Loading Factory is sufficient for all internal
       use -- it loads the base class and all subclasses as part of its own
       initialisation.

     - Each exception subclass lives in its own file, individually loadable
       via use/require and properly indexed on MetaCPAN:
       - DBIx::Class::Async::Exception::RelationshipAsColumn
       - DBIx::Class::Async::Exception::NotInStorage
       - DBIx::Class::Async::Exception::MissingColumn
       - DBIx::Class::Async::Exception::NoSuchRelationship
       - DBIx::Class::Async::Exception::AmbiguousColumn

     - Added DBIx::Class::Async::SelectNormaliser, a pre-processing module
       that allows { -ident => $col, -as => $alias } clauses in the ResultSet
       select attribute. DBIx::Class passes select attrs directly to
       SQL::Abstract's _select_field path, which does not recognise -ident
       as a special sigil and instead renders it as a function call
       (-IDENT($col)), producing invalid SQL. SelectNormaliser rewrites
       -ident hashrefs to bare column strings before they reach
       SQL::Abstract, requiring no upstream changes.  Function hashrefs
       ({ count => 'me.id', -as => 'total' }), bare strings, and literal
       SQL refs (\[]) are left untouched.  The as[] array is populated from
       inline -as values where not already specified by the caller, with
       caller-supplied as[] entries taking priority. Called automatically
       from ResultSet::search(); search_rs() benefits transitively as it
       delegates to search().  All downstream operations that build on a
       searched ResultSet (all, count, update, delete, as_query, etc.)
       see clean attrs without any change to call sites.

     [ENHANCEMENTS]
     - Enhanced as_query() to detect and handle empty column selection,
       generating optimal SQL for each database backend.

     - _call_worker now derives result_class from the payload's source_name
       at the top of the method, making it available to both the pre-flight
       validation and all three post-worker error paths without any change
       to the call signature. Pre-flight validation in _call_worker catches
       (undef relationship key in create/update hashref) before the request
       reaches the worker pool, returning a typed Future->fail immediately.

     [TESTS]
     - Added comprehensive tests for empty select list SQL generation,
       condition handling, and execution in t/152-sql-consistency.t.

     - Added t/exception.t covering throw/rethrow/overload behaviour for the
       base class and all subclasses, Factory pattern matching, and passthrough
       of unrecognised errors. Tests use mock schema/source objects and
       require no live database connection.

     [DOCUMENTATION]
     - Documented empty SELECT list support with usage examples and
       database-specific behavior in search() method POD.

0.61 2026-02-24
     [NEW FEATURES]
     - Added update_query() method to DBIx::Class::Async::ResultSet for
       previewing UPDATE SQL without execution. Useful for debugging,
       audit logging, and testing. (GH#XXX)

     - Added delete_query() method to DBIx::Class::Async::ResultSet for
       previewing DELETE SQL without execution. Enables safe validation
       of destructive operations before execution.

     - Added insert_query() method to DBIx::Class::Async::Row for
       previewing INSERT SQL without execution. Allows inspection of
       insert operations for testing and validation.

0.60 2026-02-24
     [ENHANCEMENTS]
     - Added debugobj() and debugfh() methods to DBIx::Class::Async::Storage::DBI
       for full API compatibility with DBIx::Class::Storage::DBI. These
       methods enable SQL debugging and logging with custom debug objects
       or filehandles.

     - Enhanced worker processes to respect debug flag from parent process.
       When storage->debug(1) is called, SQL statements are now logged to
       STDERR in worker processes, enabling comprehensive query debugging
       in async mode.

     - Improved SQL generation consistency in DBIx::Class::Async::ResultSet.
       Column ordering and attribute merging are now fully deterministic
       across multiple invocations of the same query, improving query cache
       effectiveness and debugging reliability.

     - Added attribute deduplication in ResultSet->search() chaining.
       Accumulating attributes (join, prefetch, columns, select, as,
       order_by, group_by, having) are now automatically deduplicated using
       stable serialization, preventing duplicate JOINs and maintaining
       consistent SQL structure.

     [TESTS]
     - Added t/152-sql-consistency.t to verify deterministic SQL generation
       across multiple query invocations, including tests for column order
       stability, attribute merging, and deduplication behaviour.

     - Added t/spelling.t to perform British spell check for AUTHOR_TESTING.

     [DOCUMENTATION]
     - Added comprehensive DEBUGGING section to DBIx::Class::Async::Storage::DBI
       documenting debug(), debugobj(), and debugfh() methods with usage
       examples.

     - Enhanced debug() method documentation to clarify integration with
       debugobj and worker process behavior.

     - Added detailed POD for SQL generation consistency guarantees and their
       benefits for query caching, performance analysis, and testing.

     [INTERNAL]
     - Refactored _merge_attrs() in ResultSet.pm with clearer logic and
       improved deduplication using Data::Dumper for nested structure
       comparison.

     - Extracted _merge_cond(), _is_empty_cond(), and _dedup_key() helper
       methods for better code organization and reusability.

     - Modified _call_worker() in Async.pm to pass debug flag to worker
       processes, enabling consistent debug behavior across process
       boundaries.

0.59 2026-02-21
     [Bug Fixes]
     - Fixed issue silent drop of relationship 'where' conditions.
       Relationships defined with a 'where' containing raw SQL scalar refs
       (e.g. datetime functions) were silently ignored when traversing via
       a row accessor or prefetch, returning all related rows instead of
       the filtered subset.
       Fixed in _ensure_accessors(), _fetch_relationship_async(), and
       related_resultset() in DBIx::Class::Async::Row.
       Relationships with raw SQL in their 'where' are now excluded from
       the '_related' cache, ensuring dynamic expressions are evaluated
       fresh on every call.
     [TESTS]
     - t/151-has-many-datetime-filter.t: to cover the bug fix.
     - t/150-multi-column-relationship.t: new test covering multi-column
       relationships spanning two tables, demonstrating both the DBIC
       coderef relationship approach (join/prefetch) and the async-safe
       helper method pattern for cases where join conditions depend on
       columns from related tables rather than self.

0.58 2026-02-20
     [BUG FIX]
     - ResultSet::create() now detects scalar-ref values targeting primary
       key columns and croaks immediately with a descriptive error message.
       Previously, scalar refs used to embed inline SQL expressions
       (e.g. \'UUID()') were silently stringified during IPC serialisation
       to the worker process, causing DBIC to fall back to last_insert_id()
       on a non-autoincrement char column and return a wrong value ('1' on
       SQLite, undef on MySQL). Callers should generate PK values in Perl
       before calling create(), e.g. Data::UUID->new->create_str.
     [TEST SUITE]
     - t/149-scalar-ref-char-pk.t: new test covering the scalar-ref char PK
       guard and confirming that Perl-side UUID generation works correctly
       as the fix.
     - t/lib/TestSchema/Result/Event.pm: new test fixture.
     - t/003-basic.t, t/004-simple-get.t: updated source count from 3 to 4
       to reflect the addition of the Event fixture to TestSchema.

0.57 2026-02-10
     - [FEATURE] Changed caching to be disabled by default (TTL set to 0).
     - [FEATURE] Added automatic detection of non-deterministic SQL functions
       (NOW, RAND, etc.) to automatically bypass cache for safety.
     - [FIX] Improved cache invalidation on update/delete operations to use
       specific primary key keys, preventing stale data scenarios.
     - [FIX] count() queries are no longer cached by default due to high
       risk of stale data, ensuring accurate row counts.

0.56 2026-02-08
     [TEST SUITE]
     - Fixed t/030-row-copy.t to deal in system with -Duselongdouble as
       reported in the issue #4 by @eserte.
     - Fixed t/145-mojo-integration.t to skip test unless Mojo::IOLoop and
       IO::Async::Loop::Mojois are found as reported in the issue #5
       by @eserte.

0.55 2026-02-06
     [NEW FEATURES]
     - Added run_parallel() method to DBIx::Class::Async::Schema. This method
       accepts a list of code references, executes them concurrently using
       the asynchronous schema connection, and returns a Future that
       resolves when all tasks are complete.
     - Added await_all() method to DBIx::Class::Async::Schema. This method
       takes one or more Future objects and synchronously blocks until all
       of them have completed, returning the list of results.
     [DOCUMENTATION]
     - Added comprehensive POD for run_parallel() and await_all() in Schema.pm,
       including usage examples for maximizing query performance.
     [TEST SUITE]
     - Added t/146-await-all.t to verify synchronous waiting and error
       propagation for await_all().
     - Added t/147-run-parallel.t to verify concurrent execution, aggregation
       of results, and error propagation for run_parallel().

0.54 2026-02-04
     [TEST SUITE]
     - Fixed t/145-mojo-integration.t to prevent compile-time failures on
       environments where Mojo::IOLoop is not installed.
     [DOCUMENTATION]
     - Formalised Event Loop Agnosticism: Explicitly documented the
       ability to inject external IO::Async::Loop instances.

0.53 2026-02-04
     [TEST SUITE]
     - Added t/144-loop-engine.t: Validates "Smart Default" auto-initialisation
       and verified "Injection Pattern" for custom IO::Async::Loop instances.
     - Added t/145-mojo-integration.t: Established formal support for
       Mojo::IOLoop environments, proving non-blocking co-existence
       between DBIC workers and Mojo timers.

     [DOCUMENTATION]
     - Updated DBI.pm/Storage.pm: Clearly defined the "Master-Worker"
       architecture and the separation of the Parent process from the
       DBI Handle (Ghost DBH pattern).
     - Enhanced CAVEATS: Documented why traditional txn_begin/commit
       blocks are replaced by the atomic instruction-set logic of txn_do.

0.52 2026-02-04
     - Proposed patch for issue #4 (-Duselongdouble), thanks @eserte.
     - Proposed patch for issue #9 (seg fault), thanks @eserte.
     - Proposed patch for issue #7 (missing prereq). thanks @szabgab.
     - Merged pull request #8 (add more versions of Perl to CI), thanks @szabgab.

0.51 2026-02-03
     - Bumped version as PAUSE was unhappy with previous tar ball.

0.50 2026-02-03
     [MAJOR ARCHITECTURAL OVERHAUL]
     - Complete rewrite of the Storage and Persistence layer to use a
       decoupled "Bridge & Worker" architecture.
     - Implementation of DBIx::Class::Async::Row lifecycle management:
        * Introduced "Dirty" column tracking to minimise SQL UPDATE payloads.
        * Added shadow-key optimisation for high-speed attribute access.
        * Robust AUTOLOAD mechanism for transparent ResultSet/Row interaction.
     - Enhanced Race Condition Recovery:
        * find_or_create() now handles unique constraint collisions
          automatically via a catch-and-retry strategy.
     - Improved Memory & Process Management:
        * Weakened schema references in Storage to prevent worker leaks.
        * Refined worker pool lifecycle (connect/disconnect) for cleaner
          shutdowns in event-loop environments.
     - Streaming Support:
        * Introduced DBIx::Class::Async::Storage::DBI::Cursor for
          non-blocking, memory-efficient iteration over large result sets.
     - Persistence Integrity:
        * Added strict Primary Key validation in find(), update(), and delete()
          to prevent ambiguous database operations.
     - Documentation:
        * Full POD refresh for Row, ResultSet, and Storage classes reflecting
          the new async design patterns.

0.49 2026-01-22
     - [IMPROVEMENT] Fixed ResultSet->slice to correctly return a ResultSet
       in scalar context.
     - [IMPROVEMENT] Fixed ResultSet->count to respect slices (LIMIT/OFFSET)
       by using subqueries in the async worker.
     - [IMPROVEMENT] Ensure result_class persists across chained search() calls.
     - [IMPROVEMENT] Fixed constructor (new) to correctly store and inherit
        result_class, rows, and pager attributes.
     - [IMPROVEMENT] Fixed argument passing in Async.pm to ensure attributes
       ($attrs) reach the background worker for count operations.
     - [TEST] Fixed t/43-result-class.t to verify result_class persistence.
     - [TEST] Fixed t/26-slice.t to test comprehensive slicing and chaining
       behaviour against a real SQLite database.
     - [TEST] Added t/57-count-performance.t to benchmark and verify the
       overhead of asynchronous subquery counting.

0.48 2026-01-22
     - [ENHANCE] Hardened _merge_result_data to support multiple database
       return patterns (HASH, ARRAY, and SCALAR).
     - [FIX] Added support for positional Primary Key mapping from ARRAY
       refs, improving compatibility with specialised DBI drivers.
     - [FIX] Improved Composite Primary Key safety during row creation,
       ensuring multi-column keys are correctly populated from database
       returns (e.g., PostgreSQL RETURNING clauses).
     - [TEST] Added comprehensive test suite for result data merging
       logic (t/56-merge-result-data.t).

0.47 2026-01-22
     - [REFACTOR] Overhauled Row state management to use authoritative
       'in_storage' flags instead of guessing based on Primary Key presence.
     - [FIX] Resolved "Cannot update row: not in storage" errors in
       complex workflows by propagating storage state through recursive
       relationship inflation.
     - [REFACTOR] Centralised row inflation logic into Async.pm bridge to
       ensure consistent object creation across all ResultSet fetch paths.
     - [FIX] Updated find_or_new to correctly distinguish between persistent
       and transient objects.
     - [IMPROVE] Hardened Row and ResultSet against Mock objects in test
       suites by adding defensive metadata checks (can('columns')).
     - [OPTIMISE] Improved search performance by only generating cache
       keys when caching is explicitly enabled.
     - [FIX] Ensure single-column primary keys are correctly merged during
       create() operations across all bridge drivers.

0.46 2026-01-22
     - [DOCS] Major documentation overhaul across the entire distribution.
     - [DOCS] Modernised SYNOPSIS for all core packages to utilise Future-based
       chaining (->then/->catch) instead of legacy blocking patterns:
        * DBIx::Class::Async
        * DBIx::Class::Async::Schema
        * DBIx::Class::Async::ResultSet
        * DBIx::Class::Async::Row
        * DBIx::Class::Async::Storage
        * DBIx::Class::Async::Storage::DBI
        * DBIx::Class::Async::Storage::DBI::Cursor
        * DBIx::Class::Async::Pager
        * DBIx::Class::Async::ResultComponent
     - [DOCS] Added practical examples for high-concurrency operations, including
       race-condition safe find_or_create and update_or_create.
     - [DOCS] Documented the "Smart Discovery" logic for unique constraints.
     - [DOCS] Improved technical clarity regarding synchronous metadata vs.
       asynchronous data retrieval in Pager and Row objects.
     - [DOCS] Demonstrated standard async patterns for recursive cursor iteration.

0.45 2026-01-22
     - [REFACTOR] Extracted unique constraint discovery into internal helper
       _extract_unique_lookup to unify logic across ResultSet operations.
     - [FEATURE] Refactored find_or_new to use Smart Discovery for lookups,
       improving search accuracy.
     - [FIX] Enhanced find_or_new to correctly strip "me." prefixes from
       ResultSet conditions when instantiating new result objects.
     - [FIX] Cleaned up find_or_create logic by utilising the new lookup
       helper and streamlining the race-condition recovery path.
     - [DOCS] Added POD documentation for internal _extract_unique_lookup method.
     - [FIX] Refactored update_or_create to utilise _extract_unique_lookup and
      implement atomic race-condition recovery.
    - [FIX] Corrected update_or_new behavior to properly return a volatile
      new_result object instead of persisting via create() on find failure.
    - [IMPROVEMENT] Standardised prefix stripping (me/self/foreign) in
      update_or_new to prevent invalid column crashes on filtered ResultSets.
    - [REFACTOR] Streamlined identity discovery across all upsert-style
      methods to ensure consistent unique key prioritisation.

0.44 2026-01-22
     - [FEATURE] Refactor find_or_create to be race-condition safe in
       asynchronous environments.
     - [IMPROVEMENT] Implement "Smart Discovery" for unique constraints;
       automatically identifies lookup columns if 'key' is not provided.
     - [FIX] Enhance Async.pm error propagation to correctly stringify
       DBIx::Class::Exception objects for the parent process.
     - [FIX] Ensure find_or_create handles the "Gap" between initial find
       and create by performing an atomic recovery find on conflict.
     - [FIX] Add missing $attrs propagation to find() in Async.pm to
       support complex recovery searches.

0.43  2026-01-22
      - Minor test cleanup.

0.42  2026-01-22
      - [MAJOR] Added DBIx::Class::Async::ResultComponent to provide
        non-blocking row-level update() and delete() methods.
      - [OPTIMISATION] Re-engineered ResultSet update() and delete() to use
        high-performance bulk SQL ("WHERE PK IN (...)") instead of
        individual N-queries.
      - [FEATURE] Added support for bulk update_bulk() and delete_all()
        respecting LIMIT and OFFSET attributes via ID-mapping.
      - [FIX] Silenced "Query returned more than one row" deprecation
        warnings by utilising search()->first() with explicit row limits
        in worker operations.
      - [FIX] Hardened the Bridge update() method to support hybrid return
        types: returns Row objects for single updates and row-counts
        for bulk updates.
      - [STABILITY] Improved IO::Async cleanup during Global Destruction
        to prevent "Notifier does not exist" warnings.
      - [INTERNAL] Added _check_response() and _merge_result_data()
        helpers to standardise error handling and row inflation.
      - [TESTS] Added 8 new test files (t/48 through t/55) covering
        bulk operations, row-level components, and internal safety guards.

0.41  2026-01-19
      - Added support for inflate column.

0.40  2026-01-19
      - Added test to cover the use of CHI with DBIx::Class::Async.

0.39  2026-01-18
      - Added sub is_ordered() to DBIx::Class::Async::ResultSet.

0.38  2026-01-18
      - Added new package DBIx::Class::Async::ResultSet::Pager.
      - Added the following subs to DBIx::Class::Async::ResultSet.
        - count_total
        - is_paged
        - page
        - pager
        - search_with_pager

0.37  2026-01-18
      - Documented the removal of support for txn_scope_guard() in DBIx::Class::Async::Schema.

0.36  2026-01-18
      - Added sub txn_batch() to DBIx::Class::Async::Schema.
      - Updated DBIx::Class::Async::Schema::deploy() to return Future.

0.35  2026-01-18
      - Updated pod for txn_batch() and added unit test.

0.34  2026-01-18
      - Added sub search_with_prefetch() to DBIx::Class::Async.
      - Added sub result_class() to DBIx::Class::Async::ResultSet.

0.33  2026-01-17
      - Added the following methods to DBIx::Class::Async::ResultSet
        - get_cache
        - set_cache
        - clear_cache

0.32  2026-01-17
      - Added the following methods to DBIx::Class::Async::ResultSet
        - count_literal
        - count_rs
        - search_literal

0.31  2026-01-16
      - Added deploy() to DBIx::Class::Async and DBIx::Class::Async::Schema.

0.30  2026-01-16
      - Added the following methods to DBIx::Class::Async::Schema
        - schema_version
        - unregister_source

0.29  2026-01-16
      - Added the following methods to DBIx::Class::Async::Row
        - is_column_dirty
        - update_or_insert
        - insert_or_update (alias for update_or_insert)

0.28  2026-01-13
      - Added sub copy() to DBIx::Class::Async::Row.
      - Added sub register_[class|source] to DBIx::Class::Async::Schema.

0.27  2026-01-12
      - Tidied up pod in general.

0.26  2026-01-12
      - Re-organise Cursor package in sync with DBIx::Class.
        Before: DBIx::Class::Async::Cursor
        After: DBIx::Class::Async::Storage::DBI::Cursor
      - Added new package DBIx::Class::Async::Storage::DBI.

0.25  2026-01-12
      - Added sub DBIx::Class::Async::Row::id().

0.24  2026-01-11
      - Added the following methods to DBIx::Class::Async::Row
        - is_column_changed
        - make_columm_dirty
        - get_dirty_columns
        - set_column
        - set_columns

0.23  2026-01-11
      - Improved DBIx::Class::Async::Schema::clone().
      - Added dedicated unit test to cover the clone().

0.22  2026-01-11
      - Added sub class() and populate() to DBIx::Class::Async::Schema.

0.21  2026-01-09
      - Updated Makefile.PL to pull version automatically.
      - Tidied up pod document.

0.20  2026-01-08
      - Updated Makefile.PL w.r.t test requisite thanks Gabor Szabo.
      - Added CI workflow thanks Gabor Szabo.
      - Tidied up pod document.

0.19  2026-01-08
      - Added sub search_related[_rs] to DBIx::Class::Async::ResultSet.

0.18  2026-01-08
      - Added sub delete_all() to DBIx::Class::Async::ResultSet.

0.17  2026-01-08
      - Added sub update_or_[new|create]() to DBIx::Class::Async::ResultSet.

0.16  2026-01-08
      - Added sub find_or_[new|create]() to DBIx::Class::Async::ResultSet.

0.15  2026-01-08
      - Added sub populate() and populate_bulk() to DBIx::Class::Async::ResultSet.

0.14  2026-01-08
      - Added sub slice() to DBIx::Class::Async::ResultSet.

0.13  2026-01-07
      - Added sub related_resultset() to DBIx::Class::Async::ResultSet.

0.12  2026-01-06
      - Updated pod about N+1 issue.

0.11  2026-01-06
      - Search with prefix now supported by DBIx::Class::Async::Schema.

0.10  2026-01-06
      - POD cleanup in general.

0.09  2026-01-05
      - Added support for Cursor.

0.08  2026-01-05
      - Added sub create_related() to DBIx::Class::Async::Row.

0.07  2026-01-05
      - Fixed sub prefetch() in DBIx::Class::Async::ResultSet.

0.06  2026-01-05
      - Fixed sub next() in DBIx::Class::Async::ResultSet.

0.05  2026-01-04
      - Removed redundant documented attributes.

0.04  2026-01-04
      - Updated pod document.

0.03  2026-01-04
      - Tidied up unit tests.

0.02  2026-01-04
      - Added initial draft to support following async operations:
        - DBIx::Class::Async::Row
        - DBIx::Class::Async::ResultSet
        - DBIx::Class::Async::Schema
        - DBIx::Class::Async::Storage
     - Also added template for the following:
        - DBIx::Class::Async::TxnGuard

0.01  2026-01-01
      - Module Created.
