Skip to content

feat(DBI): test-suite parity — Exporter, driver architecture, +12× passing subtests#546

Open
fglock wants to merge 11 commits intomasterfrom
fix/dbi-test-parity
Open

feat(DBI): test-suite parity — Exporter, driver architecture, +12× passing subtests#546
fglock wants to merge 11 commits intomasterfrom
fix/dbi-test-parity

Conversation

@fglock
Copy link
Copy Markdown
Owner

@fglock fglock commented Apr 22, 2026

Summary

Unified PR bringing the bundled DBI test suite (jcpan -t DBI, 200 test files) from 308/562 passing on master → 3978/5610 passing — roughly 13× more passing subtests, zero PerlOnJava regressions.

Four stacked commits, each addressing a distinct layer of the gap. They were originally opened as #540/#542/#544/#545 and are now consolidated here on a single branch against master.

1. fix(DBI): wire Exporter + :sql_types/:sql_cursor_types/:utils tags

The bundled DBI.pm defined SQL_* / DBIstcf_* constants but never set up @EXPORT_OK / %EXPORT_TAGS, so use DBI qw(:sql_types ...) (which almost every DBI test starts with) failed at compile time with "Bareword SQL_GUID not allowed while strict subs in use".

  • Made DBI inherit from Exporter and registered :sql_types, :sql_cursor_types, :utils, :profile tags.
  • Added missing constants (SQL_INTERVAL_*, SQL_ARRAY_LOCATOR, SQL_CURSOR_*, DBIstcf_*).
  • Ported utility subs (neat, neat_list, looks_like_number, data_string_diff / _desc, data_diff, dump_results, sql_type_cast, dbi_time) into a sibling src/main/perl/lib/DBI/_Utils.pm, required by DBI.pm. The utils live in a separate .pm because combining everything into DBI.pm tripped a per-method bytecode limit in PerlOnJava's backend.

2. fix(backend): interpreter fallback on runtime VerifyError

The existing compile-time interpreter fallback in PerlLanguageProvider.compileToExecutable only catches VerifyError / ClassFormatError thrown while the JVM class is being defined, instantiated, and looked up. HotSpot, however, defers per-method bytecode verification to the first invocation, so a class that the generator produced with inconsistent stack map frames sails past compileToExecutable and only crashes later when executeCode invokes runtimeCode.apply().

t/01basics.t's main body (~200 top-level cmp_ok/is calls) hits this routinely:

Bad local variable type
  Reason: Type top (current frame, locals[203]) is not assignable to reference type
  Location: org/perlonjava/anon1762.apply(...) @25039: aload

This commit adds a second try/catch at the apply() call site in executeCode, reusing needsInterpreterFallback. On catch we recompile the AST via BytecodeCompiler and retry apply() on the resulting InterpretedCode. BEGIN/CHECK/INIT have already run by this point and the main body has not, so retry is safe.

JPERL_SHOW_FALLBACK=1 now also prints Note: Using interpreter fallback (verify error at first call). when this new path fires.

3. feat(DBI): driver-architecture + pure-Perl DBD support

Previously use DBI; DBI->install_driver("NullP") died with Undefined subroutine &DBI::install_driver. The bundled DBI.pm talked to the Java DBI backend only, bypassing the DBI driver architecture, which is fine for JDBC drivers (SQLite, H2, ...) but not for the pure-Perl DBDs bundled with upstream DBI — DBD::NullP, DBD::ExampleP, DBD::Sponge, DBD::Mem, DBD::File, DBD::DBM — which the self-tests lean on heavily.

New src/main/perl/lib/DBI/_Handles.pm:

  • DBI->install_driver / installed_drivers / data_sources / available_drivers / setup_driver
  • DBI::_new_drh / _new_dbh / _new_sth (plain blessed hashrefs, no tie)
  • DBI::_get_imp_data
  • DBD::_::common / dr / db / st base classes with FETCH, STORE, err, errstr, state, set_err, trace, trace_msg, parse_trace_flag(s), func, dump_handle, visit_child_handles, default connect / connect_cached, quote, quote_identifier, data_sources, disconnect, commit, rollback, ping, finish, fetchrow_array, fetchrow_hashref, rows, bind_col(s), bind_param(_array), execute_array, _set_fbav
  • Stub DBI::dr / DBI::db / DBI::st packages so isa('DBI::dr') succeeds; DBD::_::<suffix> inherits from them.

DBI.pm's connect wrapper now detects a pure-Perl DBD (has driver() but no _dsn_to_jdbc) and routes through install_driver($name)->connect(...) instead of the JDBC backend.

4. feat(DBI): fill in more DBI internals used by test suite

Everything else the self-tests and DBI::DBD::SqlEngine-based DBDs (DBD::File, DBD::DBM) look up on DBI and on handles: DBI->internal, DBI->parse_dsn, DBI->driver_prefix, DBI->dbixs_revision, DBI->install_method, DBI::hash, DBI::_concat_hash_sorted, DBI::dbi_profile / _merge / _merge_nodes. Plus base-class methods: do, prepare_cached, 5 select* variants, type_info, fetchall_arrayref, fetchall_hashref, _get_fbav, FETCH_many, debug, and a DBD::_::st::FETCH override computing NAME_lc / NAME_uc / NAME_hash / NAME_lc_hash / NAME_uc_hash from NAME.

Also fixed DBI.pm's trace / trace_msg to work as class methods (previously crashed on strict refs when the invocant was "DBI").

Cumulative effect on jcpan -t DBI

Stage Subtests Passing Failing Files failing
master 562 308 254 180/200
+ Exporter 638 368 270 180/200
+ verifier fallback 946 676 270 180/200
+ install_driver 1600 1240 360 170/200
+ more internals 5610 3978 1632 166/200

The remaining 166 failing files are dominated by two things this PR does not try to solve:

  • Tied-hash semantics. Real DBI's handles are tied hashes, so $sth->{NAME_lc} transparently runs FETCH('NAME_lc'). Our handles are plain blessed hashrefs; many tests exercise attributes via direct hash access and so skip our computed FETCH.
  • DBI::DBD::SqlEngine's install_method injection. DBD::File / DBD::DBM rely on DBI::DBD::SqlEngine::driver dynamically compiling f_versions / dbm_versions / … into the driver's ::db package. Our simplified install_method stub doesn't fully drive that machinery yet.

See dev/modules/dbi_test_parity.md for the complete plan and status.

Test plan

  • make (full unit tests) passes after each of the four commits and on the final tip.
  • ./jperl -e 'use DBI qw(:sql_types :utils); print SQL_GUID, neat("x")' works.
  • ./jperl -e 'use DBI; my $dbh = DBI->connect("dbi:NullP:", "", ""); my $sth = $dbh->prepare("SELECT 1"); $sth->execute' works.
  • ./jperl ~/.cpan/build/DBI-1.647-5/t/01basics.t prints 94+ ok lines before hitting a non-blocker (was: ok 1 then VerifyError).
  • ./jperl ~/.cpan/build/DBI-1.647-5/t/02dbidrv.t runs all 54 subtests (was: died on line 155 with Can't locate install_driver).
  • ./jperl ~/.cpan/build/DBI-1.647-5/t/48dbi_dbd_sqlengine.t: 21/22 pass.
  • jcpan -t DBI matches the baseline table above on this branch.

Design doc

dev/modules/dbi_test_parity.md (added in commit 2, extended by later commits) tracks the phased plan and records every step with before/after numbers.

Supersedes

All four commits are preserved here for reviewability; I'm happy to squash on merge or leave them split, whichever you prefer.

Generated with Devin

fglock and others added 11 commits April 22, 2026 15:49
The bundled DBI.pm declared SQL_* constants but never set up
@EXPORT_OK / %EXPORT_TAGS, so `use DBI qw(:sql_types ...)` (which
essentially every DBI test uses) pulled nothing into the caller's
namespace and failed at compile time with "Bareword SQL_GUID not
allowed while strict subs in use".

This change:
  - makes DBI inherit from Exporter and registers the four standard
    export tags (sql_types, sql_cursor_types, utils, profile);
  - adds the constants that were missing (SQL_INTERVAL_*,
    SQL_ARRAY_LOCATOR, SQL_MULTISET_LOCATOR, SQL_CURSOR_*,
    DBIstcf_STRICT/DISCARD_STRING);
  - ports the small utility functions from DBI / DBI::PurePerl
    (neat, neat_list, looks_like_number, data_string_diff,
    data_string_desc, data_diff, dump_results, sql_type_cast,
    dbi_time) into a sibling DBI::_Utils module, required by
    DBI.pm. The utils live in a separate .pm so PerlOnJava
    compiles them to their own JVM class; combining everything
    into a single DBI.pm tripped a per-method bytecode limit
    in our backend.

Effect on `jcpan -t DBI`:
  before: 200 files, 562 subtests, 308 passing, 254 failing
  after:  200 files, 638 subtests, 368 passing, 270 failing

The remaining failures are unrelated issues (missing
DBI::install_driver / DBI::_new_drh, DBD::File / DBD::DBM /
gofer / DBI::PurePerl not implemented, plus a separate bytecode
verifier bug on very large flat test scripts).

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
The existing compile-time interpreter fallback in
PerlLanguageProvider.compileToExecutable only catches VerifyError /
ClassFormatError thrown while the JVM class is being defined,
instantiated, and looked up. HotSpot, however, defers per-method
bytecode verification to the first invocation of each method, so a
compiled class that the generator produced with inconsistent stack
map frames sails past compileToExecutable and only crashes later
when executeCode invokes runtimeCode.apply(). The DBI test suite
hits this routinely: t/01basics.t's main body has 200+ top-level
statements and blows up with
"Type top (locals[203]) is not assignable to reference type" at
first invocation, even though class loading appeared to succeed.

This change adds a second try/catch at the apply() call site in
executeCode. On a recoverable error (same needsInterpreterFallback
predicate used by the compile-time path), we recompile the AST via
BytecodeCompiler and retry apply() on the resulting InterpretedCode.
BEGIN / CHECK / INIT blocks have already run by this point and the
main body has not, so re-executing apply() on the interpreted form
is safe.

JPERL_SHOW_FALLBACK=1 now also prints
"Note: Using interpreter fallback (verify error at first call)."
when this new path fires.

Effect on the bundled DBI test suite (`jcpan -t DBI`):
  before: 638 subtests, 368 passing, 270 failing
  after:  946 subtests, 676 passing, 270 failing
  => 308 additional subtests now execute successfully. The same 270
     still fail; those are DBI-level issues (missing install_driver /
     _new_drh, DBD::File, gofer, ...) that were previously hidden
     behind the verifier crash.

See dev/modules/dbi_test_parity.md for the rest of the plan.

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Previously `use DBI; DBI->install_driver("NullP")` died with
"Undefined subroutine &DBI::install_driver". The bundled DBI.pm
talked to the Java DBI backend only, bypassing the DBI driver
architecture (DBD::<name>::driver factories, DBI::_new_drh/dbh/sth,
DBD::_::common / dr / db / st base classes). That path covers JDBC
drivers (SQLite, H2, ...) but not the pure-Perl DBDs bundled with
upstream DBI — DBD::NullP, DBD::ExampleP, DBD::Sponge, DBD::Mem,
DBD::File, DBD::DBM — which the DBI self-tests rely on extensively.

This change adds the minimum driver-architecture pieces needed by
those DBDs, in a new file src/main/perl/lib/DBI/_Handles.pm:

  * DBI->install_driver / installed_drivers / data_sources /
    available_drivers / setup_driver;
  * DBI::_new_drh / _new_dbh / _new_sth (handle factories,
    returning plain blessed hashrefs — no tie magic);
  * DBI::_get_imp_data (stub);
  * DBD::_::common / dr / db / st base classes with FETCH, STORE,
    err, errstr, state, set_err, trace, trace_msg,
    parse_trace_flag(s), func, dump_handle, visit_child_handles,
    default connect / connect_cached, quote, quote_identifier,
    data_sources, disconnect, commit, rollback, ping, finish,
    fetchrow_array, fetchrow_hashref, rows, bind_col(s),
    bind_param(_array), execute_array, _set_fbav;
  * Stub DBI::dr / DBI::db / DBI::st packages so `isa('DBI::dr')`
    succeeds; DBD::_::<suffix> inherits from DBI::<suffix>.

DBI.pm's connect wrapper now detects a pure-Perl DBD (has `driver()`
but no `_dsn_to_jdbc`) and routes through
`install_driver($name)->connect(...)` instead of the JDBC backend.

Lives in a separate .pm for the same per-method bytecode-size
reason as DBI/_Utils.pm from PR #540.

Effect on `jcpan -t DBI` (stacked on PR #542):
  before: 200 files, 946 subtests, 676 passing, 270 failing
  after:  200 files, 1600 subtests, 1240 passing, 360 failing
  => +564 subtests now pass (+654 newly executed). 10 fewer test
     files fail overall; the remaining failures are real DBI-level
     issues (DBI::PurePerl, DBD::File/DBM/Gofer, a handful of
     handle-tracking edge cases) tracked as Phase 3 in the plan.

See dev/modules/dbi_test_parity.md.

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Phase 3 first batch of the DBI test-parity plan: add the methods
the DBI self-tests and the bundled pure-Perl DBDs
(DBD::File / DBD::DBM / DBD::Sponge / DBD::Mem / DBI::DBD::SqlEngine)
call on DBI and on handles.

Top-level DBI methods / helpers:
  * DBI->internal      (fake DBD::Switch::dr drh, isa('DBI::dr'))
  * DBI->parse_dsn
  * DBI->driver_prefix (accepts both 'File' and 'DBD::File')
  * DBI->dbixs_revision
  * DBI->install_method / DBI->_install_method
  * DBI::hash          (ported from DBI::PurePerl)
  * DBI::_concat_hash_sorted
  * DBI::dbi_profile / dbi_profile_merge / dbi_profile_merge_nodes
  * DBI->data_sources now accepts "dbi:DRIVER:" form.

Trace fix in DBI.pm:
  * DBI->trace / DBI->trace_msg now work as class methods
    (previously crashed on strict refs when the invocant was "DBI").

DBD::_::db base class:
  * do, prepare_cached
  * selectrow_array / _arrayref / _hashref
  * selectall_arrayref / _hashref
  * selectcol_arrayref
  * type_info stub

DBD::_::st base class:
  * fetchall_arrayref (plain / slice / hash)
  * fetchall_hashref
  * _get_fbav
  * FETCH override computing NAME_lc / NAME_uc / NAME_hash /
    NAME_lc_hash / NAME_uc_hash from NAME when called via
    $sth->FETCH(...). Direct $sth->{NAME_lc} access still requires
    tied-hash semantics, which we do not provide.

DBD::_::common base class:
  * FETCH_many, debug, dbixs_revision, install_method, dump_handle.

Effect on `jcpan -t DBI` (stacked on #544):
  before: 200 files, 1600 subtests, 1240 passing, 360 failing
  after:  200 files, 5610 subtests, 3978 passing, 1632 failing
  => +2738 subtests now pass (+4010 more executed). 4 fewer test
     files fail overall. The remaining 166 files are dominated by
     (a) DBD::File / DBD::DBM-specific methods not yet wired and
     (b) tied-hash-dependent attribute access.

Cumulative across the four stacked PRs (#540, #542, #544, this one):
  master:  562 subtests, 308 passing
  now:     5610 subtests, 3978 passing  (~13× more passes)

See dev/modules/dbi_test_parity.md.

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Next slice of the DBI test-parity plan. Two improvements:

1. Handles are now "two-headed", matching real DBI:

     - INNER handle: a plain blessed hashref (DBD::NullP::db etc.)
       holding the actual state.
     - OUTER handle: a reference to an anon hash, tied to DBI::_::Tie,
       and blessed into DBI::dr / DBI::db / DBI::st.

   The outer is what user code receives. `ref($dbh) eq 'DBI::db'`
   now holds (matching real DBI), many self-tests that check for
   this invariant now pass, and `$dbh->{NAME_lc}` (direct hash
   access) triggers FETCH on the tie class, which delegates to
   DBD::_::st::FETCH where computed NAME_lc / NAME_uc / NAME_hash
   etc. get produced on demand. Previously those only worked via
   explicit `$dbh->FETCH('NAME_lc')`.

2. Outer-handle method dispatch goes through DBI::_::OuterHandle's
   AUTOLOAD. Look-up order:

     - the inner handle's implementor class (driver-specific
       methods like prepare, execute, f_versions);
     - the DBI package (Java-registered methods, for the JDBC path
       where the Java `connect()` returns an untied DBI::db);
     - DBD::_::<suffix> base classes (common methods like errstr,
       set_err, etc.).

   `can` and `isa` overrides inspect the same fallback chain so
   introspection works from either side.

Also in this commit:
  - Populate ChildHandles on parent handles as children are
    created; visit_child_handles now walks something real.
  - Add begin_work and clone stubs on DBD::_::db.
  - Add a default fetch() on DBD::_::st aliased to fetchrow_arrayref.
  - `use Carp ()` at the top of _Handles.pm (previously used
    Carp::croak without importing).

Effect on `jcpan -t DBI`:
  before: 200 files, 5610 subtests, 3978 passing, 1632 failing
  after:  200 files, 5862 subtests, 4116 passing, 1746 failing
  => +138 more subtests pass, +252 more are executed. 166 files
     still fail (same as before); the remaining failures are
     mostly driver-specific logic (flock-deadlock under DBD::DBM,
     DBD::Sponge missing methods, DBI::Profile flush_to_disk,
     etc.).

See dev/modules/dbi_test_parity.md.

Cumulative: master was 562/308 passing; now 4116/5862 (~13.4x more
passing subtests).

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Small follow-up to the tied-handle commit:
  - `use Scalar::Util ()` at the top of _Handles.pm.
  - ChildHandles is now pushed onto parent handles in _new_dbh /
    _new_sth (drh -> dbh, dbh -> sth) using strong refs.
  - We deliberately do NOT weaken the ChildHandles entries: weak
    refs to tied handles currently get cleared too eagerly on
    PerlOnJava, which breaks the immediately-subsequent dispatch
    on the outer. Real DBI uses weak refs there + an XS destroy
    path to clean up stale entries; our stand-in is to keep strong
    refs. This makes t/72childhandles.t fail the "weak ref clears
    when out of scope" subtests (4 subtests) but lets the earlier
    and later subtests run cleanly. TODO: switch back to weak refs
    when the PerlOnJava side of weaken + tied hashes is ironed
    out.

No change to `jcpan -t DBI` numbers over the previous commit; this
commit is about correctness of the ChildHandles path.

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…entinels

Third round of Phase 3 improvements on top of the tied-handle
foundation from the previous commit:

Profile attribute upgrade
  When a user passes `Profile => "2/DBI::ProfileDumper/File:/tmp/x"`
  to DBI->connect, real DBI parses that spec and produces a
  DBI::ProfileDumper object. Now we do the same in
    - DBD::_::common::STORE (for `$dbh->{Profile} = "spec"`),
    - _new_dbh (for the spec arriving in the connect attr hash),
    - _new_sth (which inherits Profile from the parent dbh).

Transaction helpers
  begin_work / commit / rollback now round-trip AutoCommit and
  BegunWork so `$dbh->{AutoCommit}` and `$dbh->{BegunWork}` reflect
  reality after each call. Previously commit/rollback were no-ops.

AutoCommit sentinel translation
  Pure-Perl DBDs (DBD::NullP, DBD::ExampleP) signal to DBI "I've
  handled the AutoCommit attribute myself" by STOREing the magic
  values -900 / -901. Real DBI's XS translates those back to
  0 / 1 on FETCH; we now do the same in DBD::_::common::FETCH so
  user code sees the expected boolean.

DBI->visit_handles
  Walks %installed_drh and recurses via visit_child_handles,
  exposing the handle tree to tests/tools.

DBI.pm connect wrapper
  Re-applies the user's attr hash on the returned dbh (Profile,
  RaiseError, PrintError, HandleError, ...) so driver connect()
  implementations that ignore most of the attr hash still get
  those attributes set.

Effect on `jcpan -t DBI`:
  before: 200 files, 5862 subtests, 4116 passing, 1746 failing (166 files failing)
  after:  200 files, 5878 subtests, 4156 passing, 1722 failing (164 files failing)
  => +40 subtests pass, 2 fewer files fail overall.

Cumulative: master was 562/308 passing; now 4156/5878
(~13.5x more subtests passing).

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
While working on Phase 3 I hit a PerlOnJava interpreter/backend bug
that's independent of DBI but blocks DBI's profile-related tests
(t/41prof_dump.t, t/42prof_data.t, t/43prof_env.t, plus their
zv*_4[1-3]* wrappers — ~20 test files total).

Minimal repro:

    tie %$h, 'Tie', { obj => bless { Path => [1,2,3] }, 'Foo' };
    { local $h->{obj}->{Path} = undef;
      $h->{obj}->meth;   # <-- "Can't locate method via package Foo=HASH(0x...)"
    }

After the `local`, `ref($h->{obj})` still reports "Foo", but method
dispatch on `$h->{obj}->meth` goes via the stringified form (the
literal "Foo=HASH(0x...)" as package name). Without the tie, the
same shape works fine. Without the `local`, the same shape works
fine. Only the combination trips it.

This is a latent correctness issue that will keep surfacing in any
CPAN module that uses this idiom, not just DBI. So it's now the
single highest-leverage fix on the DBI plan: promoting it to
Phase 4 (priority 1) above Phase 1's "finish Phase 3 polish".

Updated documents:
  - dev/modules/dbi_test_parity.md: new Phase 4 section with
    repro, impact analysis, investigation plan, acceptance criteria,
    and pointers to the interpreter / JVM / runtime subtrees where
    the fix is most likely to live. Renumbered the old priorities
    and pointed Next Steps at Phase 4.
  - dev/modules/README.md: updated the index line to reflect the
    new state.

No code changes.

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
`$tied_hash{key}->method(...)` crashed with "Can't locate object
method ... via package Foo=HASH(0x...)" when the hash was tied,
even though ref() on the same expression returned "Foo".

Root cause: RuntimeHash.get() for a tied hash returns a
TIED_SCALAR proxy (lazily backed by tiedFetch()) instead of the
actual fetched value. This is deliberate — it's how lvalue
semantics and STORE dispatch work — but the method-dispatch
code in RuntimeCode.callCached / RuntimeCode.call only unwrapped
READONLY_SCALAR, not TIED_SCALAR. The TIED_SCALAR shell hit
`isReference(invocant) -> false` and fell through to
`perlClassName = invocant.toString()`, which stringified the
blessed ref to "Foo=HASH(0x...)" and then treated that as a
package name.

Minimal repro:

    package Tie;
    sub TIEHASH { bless \$_[1], $_[0] }
    sub FETCH   { ${$_[0]}->{$_[1]} }

    package Foo;
    sub meth { print "in meth\n" }

    package main;
    my $obj = bless {}, 'Foo';
    my %h;
    tie %h, 'Tie', { obj => $obj };
    $h{obj}->meth;   # died with "Can't locate ... via package Foo=HASH(0x...)"

Fix: mirror the existing TIED_SCALAR handling already present in
apply() (RuntimeCode.java lines ~2378 / ~2659 / ~2846) in the
method-dispatch entry points callCached() and call(). Unwrap the
TIED_SCALAR to its fetched value at the top of each, before any
isReference / blessId check or `toString` fallback.

Effect:
  - Minimal repro now passes on both `jperl` and
    `jperl --interpreter`.
  - `t/41prof_dump.t` runs 9 subtests (was 7), `t/42prof_data.t`
    runs 4 (was 3). Remaining failures are unrelated
    DBI::Profile-on-disk issues.
  - `jcpan -t DBI`: 200 files, 5886→5890 subtests, 4156→4160
    passing, 164/200 files failing (unchanged).
  - `make` still green.

Relevant to any CPAN module that does direct method calls on
values retrieved via tied hash access — DBI itself,
DBIx::Class, Catalyst-style dispatch tables, etc.

Updates dev/modules/dbi_test_parity.md with the refined diagnosis,
marks Phase 4 done, and lines up the remaining Phase 3 polish items
(profile-on-disk, HandleError, trace file, destroy) as the next
steps.

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Phase 5 of the DBI test-parity plan:

1. set_err rewrite (DBD::_::common)
   Matches real DBI's three severity levels:
     - err undef    : clear Err/Errstr/State, no alerts
     - err ""       : info — just stored, no handlers, no alerts
     - err 0 or "0" : warning — fires HandleError if RaiseWarn or
                      PrintWarn is set; if RaiseWarn dies, if
                      PrintWarn warns
     - err truthy   : error — fires HandleError unconditionally;
                      then RaiseError dies / PrintError warns
   Error message format matches real DBI's
   "IMPL_CLASS METHOD failed|warning: errstr" shape that the
   self-tests regex against.

2. Trace-to-file support (DBI.pm + DBD::_::common)
   - DBI->trace($level, $file) now opens and installs a
     process-global $DBI::tfh filehandle.
   - DBI->trace(0, undef) closes it and reverts to STDERR.
   - Introduced DBI::_trace_fh() helper used by DBI::trace_msg,
     DBD::_::common::trace_msg, and DBD::_::common::dump_handle.
   - Accepts an already-opened filehandle (GLOB / IO) in addition
     to a filename.

Effect on `jcpan -t DBI`:
  before: 200 files, 5890 subtests, 4160 passing, 164/200 files failing
  after:  200 files, 6294 subtests, 4504 passing, 156/200 files failing

  +344 subtests pass; +404 more subtests now execute (trace and
  error-handler tests that used to abort mid-run now run to
  completion). 8 fewer test files fail overall.

  Per-file:
   - t/17handle_error.t:   2 → 84  (all passing)
   - t/09trace.t:         82 → 83
   - t/19fhtrace.t:       11 → 19
  Plus cascade improvements in the zv*_ wrappers that wrap the
  above.

Cumulative across PR #546 (master → now):
  562 subtests / 308 passing  -->  6294 subtests / 4504 passing
  (~14.6x more passing subtests).

See dev/modules/dbi_test_parity.md.

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Phase 6 of the DBI test-parity plan. Four related improvements in
DBI/_Handles.pm:

1. HandleSetErr callback
   set_err now runs $h->{HandleSetErr}->($h, $err, $errstr, $state, $method)
   first. If it returns a true value the rest of set_err is
   short-circuited. The callback can mutate $_[1..3] in-place to
   override err/errstr/state before they're stored (matching real
   DBI::PurePerl::set_err behaviour).

2. Errstr accumulation + priority promotion
   set_err no longer overwrites Errstr on every call; it appends
   with real DBI's annotations:
     - "\n$msg" when the message is new
     - " [err was X now Y]" when err changes
     - " [state was X now Y]" when state changes (ignoring S1000 seed)
   Err is only promoted when the new value is higher priority
   (truthy > "0" > "" > undef by length). This matches what
   t/08keeperr.t asserts about the running Errstr / Err state across
   a sequence of set_err calls.

3. Callbacks
   DBI::_::OuterHandle::AUTOLOAD now checks $h->{Callbacks}{$method}
   (falling back to the "*" wildcard) before dispatching. Callback
   gets $self plus the original args; $_ is localised to the method
   name; the callback's return value in scalar / list context short-
   circuits the real method dispatch (matching real DBI's callback
   protocol).

4. :preparse_flags export tag
   Added as an empty tag so `use DBI qw(:preparse_flags)` works in
   tests that probe the import even when they don't actually use
   the preparser (which we don't implement).

Effect on `jcpan -t DBI`:
  before: 200 files, 6294 subtests, 4504 passing
  after:  200 files, 6570 subtests, 4940 passing
  => +436 subtests now pass.

Per-file:
  t/08keeperr.t:     17 → 84 passing (7 remain failing)
  t/70callbacks.t:   36 → 67 passing (14 remain failing)
  t/17handle_error.t: all 84 still passing (no regression)

Also updates dev/modules/dbi_test_parity.md:
  - Baseline table updated to Phase 6.
  - New Open Questions entry describing the DBI::PurePerl reuse
    opportunity — upstream already implements most of what
    DBI/_Handles.pm does; a future PR could teach DBI.pm to
    require DBI::PurePerl unconditionally and delete most of
    _Handles.pm. Not done in this PR because it's a risky
    architectural change on top of 4900+ passing subtests.

Cumulative across PR #546:
  master was 562/308 passing; now 6570/4940 (~16x more passes).

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant