From 0d775900b01cd2a528babd02a6839c793831fb39 Mon Sep 17 00:00:00 2001 From: Riccardo Perotti Date: Mon, 11 May 2026 15:33:55 -0400 Subject: [PATCH 1/2] Add DBR::Misc::DBI::Compat shim to restore DBD::mysql 4.036 string behavior MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DBD::mysql 4.050 (required for Debian Bookworm / libmariadb 3.x) returns numeric MySQL columns as IV/NV scalars instead of string scalars (SVp_POK). This changes JSON::XS output from quoted strings ("42") to bare numbers (42), breaking consumers that rely on string-encoded numerics. DBR::Misc::DBI::Compat is a DBI RootClass that overrides all fetch methods on MySQL statement handles to stringify every returned value, restoring 4.036 behavior transparently across all callers — both DBR ORM paths and raw $dbh users (e.g. Dashboard.pm) — with no changes required in service code. This is an intentional temporary migration bridge. It should be removed once all consumers have been audited and verified safe with bare-number JSON encoding. See: perl-base-image-v2/DBD-mysql-numeric-types.md (Option B) Co-Authored-By: Claude Sonnet 4.6 --- lib/DBR/Config/Instance.pm | 4 +++ lib/DBR/Misc/DBI/Compat.pm | 64 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 lib/DBR/Misc/DBI/Compat.pm diff --git a/lib/DBR/Config/Instance.pm b/lib/DBR/Config/Instance.pm index dcc26e9..52038f2 100644 --- a/lib/DBR/Config/Instance.pm +++ b/lib/DBR/Config/Instance.pm @@ -298,6 +298,10 @@ sub _new_connection{ my $config = $INSTANCES_BY_GUID{ $self->{guid} }; my @params = ($config->{connectstring}, $config->{user}, $config->{password}); + if ( $config->{connectstring} =~ /^dbi:mysql:/i ) { + push @params, { RootClass => 'DBR::Misc::DBI::Compat' }; + } + my $dbh = DBI->connect(@params); unless ($dbh) { $self->_log("DBI error attempting to connect to db $config->{handle},$config->{class}: $DBI::errstr"); diff --git a/lib/DBR/Misc/DBI/Compat.pm b/lib/DBR/Misc/DBI/Compat.pm new file mode 100644 index 0000000..661009f --- /dev/null +++ b/lib/DBR/Misc/DBI/Compat.pm @@ -0,0 +1,64 @@ +package DBR::Misc::DBI::Compat; + +# DBI RootClass that restores DBD::mysql 4.036 behavior: all values returned +# by fetch methods are string scalars (SVp_POK), regardless of MySQL column type. +# +# Required because DBD::mysql 4.050 returns IV/NV scalars for numeric columns, +# changing JSON::XS output from quoted strings ("42") to bare numbers (42). +# Remove this shim once all consumers are verified safe with bare-number encoding. + +package DBR::Misc::DBI::Compat::db; +use parent -norequire, 'DBI::db'; + +package DBR::Misc::DBI::Compat::st; +use parent -norequire, 'DBI::st'; + +sub fetchrow_arrayref { + my $row = $_[0]->SUPER::fetchrow_arrayref; + return undef unless $row; + @$row = map { defined $_ ? "$_" : $_ } @$row; + return $row; +} + +*fetch = \&fetchrow_arrayref; + +sub fetchrow_array { + my @row = $_[0]->SUPER::fetchrow_array; + return map { defined $_ ? "$_" : $_ } @row; +} + +sub fetchrow_hashref { + my $row = $_[0]->SUPER::fetchrow_hashref(@_[1..$#_]); + return undef unless $row; + $row->{$_} = "$row->{$_}" for grep { defined $row->{$_} } keys %$row; + return $row; +} + +sub fetchall_arrayref { + my $self = shift; + my $slice = $_[0]; + my $rows = $self->SUPER::fetchall_arrayref(@_); + return $rows unless $rows && @$rows; + if ( ref($slice) eq 'HASH' ) { + for my $row (@$rows) { + $row->{$_} = "$row->{$_}" for grep { defined $row->{$_} } keys %$row; + } + } else { + for my $row (@$rows) { + @$row = map { defined $_ ? "$_" : $_ } @$row; + } + } + return $rows; +} + +sub fetchall_hashref { + my ($self, $key_field) = @_; + my $result = $self->SUPER::fetchall_hashref($key_field); + return $result unless $result; + for my $row (values %$result) { + $row->{$_} = "$row->{$_}" for grep { defined $row->{$_} } keys %$row; + } + return $result; +} + +1; From 375b750a115d9c766dfa0c5953e26535a43e3a28 Mon Sep 17 00:00:00 2001 From: Riccardo Perotti Date: Mon, 11 May 2026 15:44:46 -0400 Subject: [PATCH 2/2] Migrate CI from CircleCI to GitHub Actions CircleCI SSH key was no longer functional. Replaces .circleci/config.yml with .github/workflows/build-and-test.yml using the standard perl:5.24 Docker Hub image; installs deps via cpanm, builds with make, runs make test. Co-Authored-By: Claude Sonnet 4.6 Fix CI: use actions/checkout@v3 for perl:5.24 (Debian Stretch) compatibility actions/checkout@v4 uses Node 20 which requires glibc 2.27+; Debian Stretch ships glibc 2.24. Downgrade to v3 (Node 16, requires glibc 2.17). Co-Authored-By: Claude Sonnet 4.6 Fix CI: switch to perl:5.36 (Debian Bookworm) container GitHub Actions now forces Node 20 for all actions, including checkout@v3. Node 20 requires glibc 2.28+; perl:5.24 (Debian Stretch) ships glibc 2.24. perl:5.36 (Debian Bookworm, glibc 2.36) resolves the incompatibility and aligns CI with the migration target environment. Co-Authored-By: Claude Sonnet 4.6 Fix CI: run on ubuntu-latest without container Drop the Docker container entirely. The perl:5.24 image (Debian Stretch, glibc 2.24) is incompatible with Node 20 required by all current GitHub Actions. Running directly on ubuntu-latest uses the system Perl on Ubuntu 22.04 and avoids the issue. Added sudo for apt-get (runner is non-root). Co-Authored-By: Claude Sonnet 4.6 Fix CI: install cpanminus via apt before using cpanm ubuntu-latest does not include cpanm pre-installed. Co-Authored-By: Claude Sonnet 4.6 Fix CI: run cpanm with sudo so deps install to system Perl paths Without sudo, cpanm cannot write to system Perl directories and the installs fail silently, leaving modules missing from @INC. Co-Authored-By: Claude Sonnet 4.6 Fix CI: chown working directory back to runner after sudo cpanm sudo cpanm creates MYMETA.json as root, causing 'Permission denied' when the non-root build step tries to overwrite it. Co-Authored-By: Claude Sonnet 4.6 --- .circleci/config.yml | 21 --------------------- .github/workflows/build-and-test.yml | 18 ++++++++++++++++++ 2 files changed, 18 insertions(+), 21 deletions(-) delete mode 100644 .circleci/config.yml create mode 100644 .github/workflows/build-and-test.yml diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index 47cae8f..0000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,21 +0,0 @@ -version: 2 - -jobs: - build: - docker: - - image: perl:5.28.1 - steps: - - checkout - - run: - name: "Install CPAN dependencies and build" - command: | - export PERL_MM_USE_DEFAULT=1 - cpan App::mymeta_requires - perl Makefile.PL - cpan $(mymeta-requires) - make - - run: - name: "Run Test Suite" - command: | - make test - diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml new file mode 100644 index 0000000..3726104 --- /dev/null +++ b/.github/workflows/build-and-test.yml @@ -0,0 +1,18 @@ +name: Build and Test + +on: + push: + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install system dependencies + run: sudo apt-get update -qq && sudo apt-get install -y --no-install-recommends cpanminus libncurses-dev libsqlite3-dev + - name: Install Perl dependencies + run: sudo cpanm --notest --installdeps . && sudo chown -R "$USER:$USER" . + - name: Build + run: perl Makefile.PL && make + - name: Test + run: make test