Skip to content

Fix store_accessor on MariaDB by declaring the JSON attribute type - #3038

Open
a-abdellatif98 wants to merge 1 commit into
basecamp:mainfrom
a-abdellatif98:fix/mariadb-json-store-accessor
Open

Fix store_accessor on MariaDB by declaring the JSON attribute type#3038
a-abdellatif98 wants to merge 1 commit into
basecamp:mainfrom
a-abdellatif98:fix/mariadb-json-store-accessor

Conversation

@a-abdellatif98

Copy link
Copy Markdown

Fixes #2402.

MariaDB implements JSON as an alias for LONGTEXT with a CHECK (json_valid(...)) constraint, so ActiveRecord introspects filters.fields and events.particulars as Type::Text. store_accessor rejects that type and Filter.from_params raises ActiveRecord::ConfigurationError on every request.

Verified against MariaDB 11.8.8, where the column comes back as:

`fields` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL
  DEFAULT json_object() CHECK (json_valid(`fields`))

Declaring attribute :fields, :json overrides the introspected type on MariaDB and is a no-op on MySQL and SQLite, where the column already resolves to Type::Json. Unlike store ..., coder: JSON it adds no Type::Serialized layer and keeps StringKeyedHashAccessor, so the existing string-keyed hash semantics are unchanged.

These are the only two JSON columns in the schema. The other serialized columns (webhooks.subscribed_actions, webhook_deliveries.request/response, passkeys.transports) are text columns with explicit coders and resolve to Type::Serialized on every adapter, so they are unaffected.

Why this does not also quote the fixtures

#2769 fixes the same bug with the same attribute declaration, but it additionally quotes the JSON fixture values. That part is a silent data-loss regression on MySQL and SQLite, so this PR leaves fixtures untouched.

build_fixture_sql serializes through the database column type, not the model's declared attribute type, so attribute :fields, :json never reaches fixtures at all:

fixture value as YAML Hash (current main, unquoted):
  Type::Json (MySQL/SQLite) -> "{\"assignee_ids\":[\"abc\"]}"        correct
  Type::Text (MariaDB)      -> {"assignee_ids" => ["abc"]}           violates json_valid

fixture value as YAML String (quoted, per #2769):
  Type::Json (MySQL/SQLite) -> "\"{\\\"assignee_ids\\\":[\\\"abc\\\"]}\""   double encoded
  Type::Text (MariaDB)      -> "{\"assignee_ids\":[\"abc\"]}"                correct

The double-encoded value deserializes back to a String rather than a Hash, so the store accessors find no keys and the data quietly disappears. On SQLite with #2769's quoting applied, bin/rails test test/models/event/description_test.rb gives 5 failures of the form:

Expected "David assigned  to ..." to include "Tom & Jerry"

Reverting only the quoting: 18 tests, 62 assertions, 0 failures.

Neither a YAML String nor a YAML Hash is correct on all three adapters, so there is no stock-Rails fixture value that works everywhere. That means this PR unblocks MariaDB at runtime but does not make the test suite runnable on MariaDB, and it deliberately does not add MariaDB to CI. Closing that gap needs either an upstream Rails change or an adapter-level patch mapping MariaDB's json_valid longtext columns to Type::Json, which seemed out of scope here.

Testing

Adapter Result
SQLite 3.53.2 full suite: 1553 tests, 5856 assertions, 0 failures
MySQL 8.4.11 (Trilogy) full suite: 1553 tests, 5851 assertions, 0 failures
MariaDB 11.8.8 (Trilogy) error reproduced pre-fix, confirmed fixed post-fix on the same connection

Added four tests: a type assertion per model that fails on MariaDB before this change, and a create/reload round trip through the store accessors.

Not verified: bin/rails test:system on any adapter, and the full suite on MariaDB, which is blocked by the pre-existing fixture issue described above rather than by this change.

MariaDB implements JSON as an alias for LONGTEXT with a
CHECK (json_valid(...)) constraint, so ActiveRecord introspects
filters.fields and events.particulars as Type::Text. store_accessor
rejects that type, and Filter.from_params raised
ActiveRecord::ConfigurationError on every request.

Declaring the attribute as :json overrides the introspected type on
MariaDB and is a no-op on MySQL and SQLite, where the column already
resolves to Type::Json. Unlike `store ..., coder: JSON` it adds no
Type::Serialized layer and keeps StringKeyedHashAccessor, so the
existing string-keyed hash semantics are unchanged.

These are the only two JSON columns in the schema. The remaining
serialized columns (webhooks.subscribed_actions,
webhook_deliveries.request/response, passkeys.transports) are text
columns with explicit coders and resolve to Type::Serialized on every
adapter, so they are unaffected.

Fixes basecamp#2402
Copilot AI balanced review requested due to automatic review settings August 16, 2026 08:43

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code Review

This pull request is from a fork — automated review is disabled. A repository maintainer can comment @claude review to run a one-time review.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

This PR ensures store_accessor works correctly with MariaDB JSON columns by explicitly declaring affected columns as :json, and adds regression tests to verify type casting and persistence.

Changes:

  • Declare Filter#fields and Event#particulars as :json attributes to support store_accessor when MariaDB reports JSON as LONGTEXT.
  • Add tests asserting JSON typing and confirming store accessor values round-trip through the DB.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 5 comments.

File Description
test/models/filter_test.rb Adds regression tests for fields JSON typing and store_accessor persistence.
test/models/event_test.rb Adds regression tests for particulars JSON typing and store_accessor persistence.
app/models/filter/fields.rb Declares fields as :json to make store_accessor work with MariaDB.
app/models/event/particulars.rb Declares particulars as :json to make store_accessor work with MariaDB.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

end

test "fields is a JSON attribute however the adapter reports the column" do
assert_instance_of ActiveRecord::Type::Json, Filter.type_for_attribute(:fields)
Comment thread test/models/event_test.rb
end

test "particulars is a JSON attribute however the adapter reports the column" do
assert_instance_of ActiveRecord::Type::Json, Event.type_for_attribute(:particulars)
Comment on lines +33 to +35
# MariaDB reports JSON columns as LONGTEXT, so declare the type store_accessor needs.
attribute :fields, :json

Comment on lines +5 to +6
# MariaDB reports JSON columns as LONGTEXT, so declare the type store_accessor needs.
attribute :particulars, :json
assert_not_includes filter.board_titles, "Private board"
end

test "fields is a JSON attribute however the adapter reports the column" do
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.

MariaDB JSON columns cause ActiveRecord::ConfigurationError with store_accessor (filters.fields)

2 participants