Skip to content

MDEV-38740 Implement JSON as a pluggable data type - #5263

Open
hadeer-r wants to merge 1 commit into
MariaDB:mainfrom
hadeer-r:MDEV-38740
Open

MDEV-38740 Implement JSON as a pluggable data type#5263
hadeer-r wants to merge 1 commit into
MariaDB:mainfrom
hadeer-r:MDEV-38740

Conversation

@hadeer-r

@hadeer-r hadeer-r commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

This PR implements JSON as a distinct pluggable data type in MariaDB using the MariaDB_DATA_TYPE_PLUGIN framework.

Related issue: MDEV-38740
This work is part of GSoC '26.


What This PR Adds:

  • Implemented Type_handler_json, Field_json, and Type_collection_json, following the Type_handler_xmltype architectural pattern while preserving existing JSON type behavior.
  • Implemented Type_collection_json with aggregation rules (aggregate_for_result, aggregate_for_comparison, aggregate_for_min_max, aggregate_for_num_op) to handle type mixing, JSON is compatible with scalar types but rejects numeric operations and incompatible types like INET6.
  • SHOW CREATE TABLE and DESCRIBE now explicitly display the column type as json instead of longtext.
  • Refactored the Field_json constructor to remove the redundant charset parameter, internally enforcing the required utf8mb4_bin charset for all JSON fields.
  • Field_json now overrides has_charset() to return false, and rejects any CHARACTER SET or COLLATE attributes with ER_UNSUPPORTED_DATA_TYPE_ATTRIBUTE.
  • Removed the CHECK(json_valid()) constraint and provided implicit validation through Field_json::store(), which validates JSON on insert and raises ER_TRUNCATED_WRONG_VALUE_FOR_FIELD for invalid values.
  • Implemented JSON typecast (CAST(expr AS JSON)) via Item_json_typecast, including strict JSON validation that returns NULL with ER_TRUNCATED_WRONG_VALUE warning for invalid input.
  • Implemented JSON() constructor function via make_constructor_item so JSON('{"a":1}') works as a shorthand for CAST('{"a":1}' AS JSON).
  • Rejected numeric operations on JSON type and all numeric/datetime typecast handlers to raise ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION.
  • Updated the parser (sql_yacc.yy) to move JSON_SYM from field_type_lob to reserved_keyword_udt_not_param_type, so JSON is resolved as a UDT through the plugin rather than being hardcoded as longtext.
  • Added JSON_SYM handling in json_table_field_type grammar rule so JSON_TABLE columns can use the native JSON type properly.
  • Updated Json_table_column::set() to use Type_handler_json_common::is_json_type_handler() instead of comparing directly with type_handler_long_blob_json, so it recognizes both the built-in and plugin JSON type handlers.
  • Extended Type_handler_json_common::is_json_type_handler() to also match by handler name ("json"), so the plugin-based JSON type is correctly identified.
  • Wrapped the internal Type_collection_json in sql_type_json.cc in an anonymous namespace to avoid duplicate symbol errors on Windows.
  • Overrode Field_json::rpl_conv_type_from to allow replicating from older string/blob types (blob, text, VARCHAR, etc.) into JSON columns, fixing row-based replication test failures.

Added Tests

CREATE TABLE:

  • Verifies creating a JSON table via SELECT from a TEXT table throws ER_TRUNCATED_WRONG_VALUE_FOR_FIELD when the first row contains invalid JSON.

INSERT:

  • Verifies inserting integer (777), decimal (123.45), and boolean (true) literals into a JSON column works correctly.
  • Verifies inserting valid JSON via CAST succeeds, and inserting invalid JSON throws ER_TRUNCATED_WRONG_VALUE.

CAST:

  • Verifies casting a valid JSON object produces the correct output.
  • Verifies casting invalid JSON returns NULL with a truncation warning.
  • Verifies casting NULL returns NULL.
  • Verifies casting with enable_metadata correctly displays format=json in the column metadata.
  • Verifies casting with CHARACTER SET utf8 throws ER_UNSUPPORTED_DATA_TYPE_ATTRIBUTE (verifying JSON rejects explicit charsets).
  • Verifies JSON CAST behavior and exclusion of invalid data across WHERE and UNION ALL clauses.

Numeric / Aggregate / Arithmetic Operations:

  • Verifies aggregate functions (SUM, AVG, VARIANCE, VAR_SAMP, STD, STDDEV_SAMP) on a JSON column are rejected with ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION.
  • Verifies bitwise aggregates on a JSON column are rejected with the same error.
  • Verifies arithmetic and math functions (-j, ABS, ROUND, TRUNCATE, CEIL, FLOOR) on a JSON column are rejected with the same error.

Updated Tests

Type Mixing (INET6 + JSON):

  • Removed JSON variants from combined COALESCE/LEAST SELECT tests (JSON is now tested separately with specific error expectations).
  • Removed JSON columns from CREATE TABLE … AS SELECT tests (only the LONGTEXT variant remains).

SHOW CREATE TABLE / Metadata:

  • Updated expectations for SHOW CREATE TABLE output to show json instead of longtext CHARACTER SET utf8mb4 -COLLATE utf8mb4_bin … CHECK (json_valid(…)).
create-table select

@gemini-code-assist gemini-code-assist Bot 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.

Code Review

This pull request introduces a pluggable JSON data type plugin (type_json) for MariaDB, migrating the built-in JSON type handling to a modular plugin structure. The changes include adding the plugin implementation (plugin.cc, sql_type_json.cc, sql_type_json.h), updating parser rules in sql_yacc.yy, and adding comprehensive test suites. Feedback on the implementation highlights several critical issues: a potential null pointer dereference in Field_json::report_wrong_value when table is null, an incorrect charset mapping in make_conversion_table_field which should strictly use utf8mb4_bin, and a regression in aggregate_for_comparison that prevents comparisons between JSON and numeric or temporal types. Additionally, minor cleanups are recommended, such as removing commented-out method declarations and fixing a comment typo in plugin.cc.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread plugin/type_json/sql_type_json.cc
Comment on lines +46 to +56
Field *Type_handler_json::make_conversion_table_field(
MEM_ROOT *root, TABLE *table, uint metadata, const Field *target) const
{
/* A JSON field uses 4 bytes for length as json file is a long blob*/
uint pack_length= metadata & 0x00ff;
if (pack_length != 4)
return NULL;

return new (root) Field_json(NULL, (uchar *) "", 1, Field::NONE,
&empty_clex_str, table->s, target->charset());
}

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.

high

In Type_handler_json::make_conversion_table_field, the charset of the conversion field is set to target->charset(). However, a Field_json must always use the utf8mb4_bin character set to ensure correct JSON representation and comparison behavior. If the target field has a different character set (e.g., latin1), creating a Field_json with that character set violates this invariant and can lead to encoding issues or assertion failures.

Field *Type_handler_json::make_conversion_table_field(
    MEM_ROOT *root, TABLE *table, uint metadata, const Field *target) const
{
  /* A JSON field uses 4 bytes for length as json file is a long blob*/
  uint pack_length= metadata & 0x00ff;
  if (pack_length != 4)
    return NULL;

  return new (root) Field_json(NULL, (uchar *) "", 1, Field::NONE,
                               &empty_clex_str, table->s, &my_charset_utf8mb4_bin);
}

Comment thread plugin/type_json/sql_type_json.h Outdated
Comment on lines +36 to +46
// const Type_handler *type_handler_for_comparison() const override;
// const Type_handler *type_handler_for_tmp_table(const Item *item) const
// override;
// bool Item_hybrid_func_fix_attributes(THD *thd, const LEX_CSTRING &func_name,
// Type_handler_hybrid_field_type *handler, Type_all_attributes *func,
// Item **items, uint nitems) const override;

// virtual Item *create_typecast_item(THD *thd, Item *item,
// const Type_cast_attributes &attr) const override;

// Item *make_constructor_item(THD *thd, List<Item> *args) const override;

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.

medium

There are several commented-out method declarations in the Type_handler_json class. To maintain code cleanliness and readability, these unused, commented-out lines should be removed.

References
  1. Keep the API of custom wrapper classes minimal by avoiding or removing unused methods.

Comment on lines +60 to +73
const Type_handler *
Type_collection_json::aggregate_for_comparison(const Type_handler *a,
const Type_handler *b) const
{
if (a->type_collection() == this)
swap_variables(const Type_handler *, a, b);
if (a == &type_handler_json || a == &type_handler_hex_hybrid ||
a == &type_handler_tiny_blob || a == &type_handler_blob ||
a == &type_handler_medium_blob || a == &type_handler_long_blob ||
a == &type_handler_varchar || a == &type_handler_string ||
a == &type_handler_null)
return b;
return NULL;
}

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.

medium

In Type_collection_json::aggregate_for_comparison, the aggregation only allows comparison between JSON and other string/blob/null types. Any comparison between JSON and numeric types (like INT, DOUBLE, DECIMAL) or temporal types (like DATETIME, DATE) will return NULL. This prevents implicit comparison between JSON columns and numbers/dates, which is a regression compared to the previous LONGTEXT-based JSON implementation. Consider allowing aggregation with other basic types or delegating to the standard string type collection.

Comment thread plugin/type_json/plugin.cc Outdated
@gkodinov gkodinov added the External Contribution All PRs from entities outside of MariaDB Foundation, Corporation, Codership agreements. label Jun 23, 2026
@hadeer-r
hadeer-r marked this pull request as ready for review August 24, 2026 15:01

@grooverdan grooverdan left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

fantastic work. I tested with #5278 and it shows good matches in making the most of JSON types.

Comment thread plugin/type_json/plugin.cc Outdated
kjarir added a commit to kjarir/server that referenced this pull request Aug 25, 2026
- Section 11: use LONGTEXT instead of JSON column to avoid dependency on
  check_constraint_checks which will be disabled in PR MariaDB#5263
- Remove remaining SHOW WARNINGS from section 13.7 (MTR outputs warnings
  automatically)
- Remove '(mentor)' annotations from echo labels in sections 15, 19, 20
- NULL candidate semantics: remove args[0]->null_value check, allowing
  NULL MEMBER OF ('[null]') = 1 (SQL NULL treated as JSON null via
  json_quote_item returning 'null' per MDEV-13645). Intentional divergence
  from MySQL documented behavior per grooverdan's request.
- Simplify stored function tests: single-line CREATE FUNCTION without
  delimiters, positive VARCHAR test (f() RETURNS VARCHAR), JSON-returning
  function test (j_row_result() RETURNS JSON)
- Add optimizer trace test (section 22) showing MEMBER OF participates
  correctly in equality_propagation and condition_processing
- Add set_maybe_null() in fix_length_and_dec() for cursor protocol
  compatibility per grooverdan
@grooverdan
grooverdan requested a review from holyfoot August 25, 2026 08:05

@gkodinov gkodinov left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thank you for your contribution. This is a preliminary review.

Can you please consider squashing the commits to (ideally) a single one? Unless there's a valid reason stated to keep more than one commit that is.

Also, Please rebase your change to the latest main and make sure the embedded test failure is fixed?

Comment thread plugin/type_json/plugin.cc Outdated

static struct st_mariadb_data_type plugin_descriptor_json=
{
MariaDB_DATA_TYPE_INTERFACE_VERSION,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

different offset from the one below. please be consistent.

Comment thread sql/sql_type_json.cc Outdated
@@ -148,6 +148,8 @@ bool Type_handler_json_common::make_json_valid_expr_if_needed(THD *thd,
}


namespace {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

why do you need this change?

@hadeer-r hadeer-r Aug 26, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Since there are two different definitions of the Type_collection_json class, one in the plugin and another in sql/sql_type_json.cc, the compiler/linker gets confused because they share the same name. This caused a multiple-definition conflict in the Windows CI build.

To solve this problem, I considered a few options:

  1. Merge the two classes and make the JSON implementation in /sql depend on the plugin implementation. However, this might break the current flow and affect compatibility.
  2. Wrap the code inside an anonymous namespace. This gives the class internal linkage, making it private to the file it's defined in, which safely avoids any naming collisions.

I went with the second option, as it is the safest approach without breaking the existing architecture.

@gkodinov gkodinov Aug 26, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

How about option 3: call the plugin class somehow otherwise. It's passed by pointer, so you can call it Fred for example ;)
Or even hide the plugin class. It's passed by pointer. No need to pollute the ld namespace.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes, makes sense. I will go with renaming the plugin class.

@gkodinov

Copy link
Copy Markdown
Member

One a personal note: I'd appreciate documenting better why you need to make the changes outside of the plugin.

Copilot AI 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.

Pull request overview

This PR implements JSON as a first-class pluggable data type using the MariaDB_DATA_TYPE_PLUGIN framework, shifting JSON from being a hardcoded LONGTEXT-based type to a dedicated type handler/field implementation while updating parser resolution and tests accordingly.

Changes:

  • Updates the SQL parser to resolve JSON via UDT/type plugin infrastructure (and enables native JSON in JSON_TABLE column definitions).
  • Adds a mandatory type_json plugin implementing Type_handler_json, Field_json, type aggregation rules, JSON validation on store, and JSON type casting/constructor support.
  • Refreshes many MTR expectations to reflect SHOW CREATE TABLE/metadata displaying json and the new error/warning behavior.

Reviewed changes

Copilot reviewed 34 out of 34 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
sql/sql_yacc.yy Moves JSON to UDT keyword handling and adds JSON support in JSON_TABLE column type grammar.
sql/sql_type_json.h Extends JSON-type-handler detection to recognize plugin JSON by handler name.
sql/sql_type_json.cc Wraps internal JSON type-collection in an anonymous namespace to avoid duplicate symbols.
sql/json_table.cc Detects JSON type via Type_handler_json_common::is_json_type_handler() rather than a single built-in handler pointer.
plugin/type_json/sql_type_json.h Introduces plugin-side JSON type handler, type collection, JSON field, and JSON CAST item class definitions.
plugin/type_json/sql_type_json.cc Implements plugin-side JSON type behavior (aggregation, CAST/constructor, validation on store, replication conversion rules).
plugin/type_json/plugin.cc Registers the JSON data type plugin (MariaDB_DATA_TYPE_PLUGIN).
plugin/type_json/CMakeLists.txt Adds the new type_json plugin build definition (MANDATORY).
plugin/type_inet/mysql-test/type_inet/type_inet6_mix_json.test Updates INET6+JSON mixing tests to expect errors (JSON no longer behaves like LONGTEXT aliasing).
plugin/type_inet/mysql-test/type_inet/type_inet6_mix_json.result Updates results for revised INET6+JSON error behavior and metadata.
mysql-test/suite/plugins/r/feedback_plugin_load.result Updates feedback plugin load expectations (collation usage set changes).
mysql-test/suite/json/t/json_table_mysql.test Adjusts JSON_TABLE tests to reflect new JSON cast usability (removes parse-error expectation).
mysql-test/suite/json/r/json_table_mysql.result Updates JSON_TABLE results to successful output plus truncation warning behavior.
mysql-test/suite/funcs_1/r/is_columns_mysql.result Updates Information Schema column/type expectations to show json type with NULL charset/collation.
mysql-test/suite/compat/oracle/r/column_compression.result Updates SHOW CREATE TABLE expectations to emit json instead of LONGTEXT+CHECK.
mysql-test/main/type_json.test Adds/updates tests for JSON CAST/constructor behavior, implicit validation on insert/select, and numeric op rejection.
mysql-test/main/type_json.result Updates expected output/errors/metadata for the revised JSON type behavior.
mysql-test/main/system_mysql_db.result Updates system table DDL expectations to show json columns for privilege/options JSON fields.
mysql-test/main/system_mysql_db_fix50568.result Same as above for the fix50568 variant expectations.
mysql-test/main/system_mysql_db_fix50117.result Same as above for the fix50117 variant expectations.
mysql-test/main/system_mysql_db_fix50030.result Same as above for the fix50030 variant expectations.
mysql-test/main/system_mysql_db_fix40123.result Same as above for the fix40123 variant expectations.
mysql-test/main/mysql-metadata.result Updates metadata output to reflect LONG_BLOB (type=json) rather than BLOB (format=json).
mysql-test/main/mysql_json_table_recreate.test Updates expectations so JSON rejects explicit charset/collation attributes with ER_UNSUPPORTED_DATA_TYPE_ATTRIBUTE.
mysql-test/main/mysql_json_table_recreate.result Updates results accordingly for the JSON charset/collation attribute rejection.
mysql-test/main/json_normalize.result Updates SHOW CREATE TABLE output to show json type.
mysql-test/main/information_schema.result Updates Information Schema expectations impacted by system-table type changes.
mysql-test/main/func_json.test Updates JSON invalid-value expectations from constraint failures to truncated-wrong-value-for-field errors.
mysql-test/main/func_json.result Updates results accordingly for new JSON validation error behavior.
mysql-test/main/ctype_utf32.result Updates SHOW CREATE TABLE output to show json type.
mysql-test/main/ctype_utf32_uca.result Updates SHOW CREATE TABLE output to show json type.
mysql-test/main/ctype_utf16_uca.result Updates SHOW CREATE TABLE output to show json type.
mysql-test/main/ctype_binary.result Updates SHOW CREATE TABLE output to show json type.
mysql-test/main/column_compression.result Updates SHOW CREATE TABLE output to emit json in compression-related expectations.

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

Comment thread plugin/type_json/sql_type_json.cc
Comment on lines +125 to +130
const Type_handler *
Type_collection_json::aggregate_for_num_op(const Type_handler *a,
const Type_handler *b) const
{
return NULL;
}

@holyfoot holyfoot 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.

add set_maybe_null() to the Item_json_typecast::fix_length_and_dec()

sql_type_json.h
	Item_append_extended_type_info()
		we call set_format_name()
        in make_send_field()
		we call set_data_type_name()

	i guess we have to call the set_data_type_name() in both spots.

Type_handler_json::Column_definition_prepare_stage1
	could be removed i think since it just calls the parent's function.

mysql_json_table_recreate.result

@@ -200,17 +200,11 @@ ERROR HY000: 'MYSQL_JSON' is not allowed in this context
 create table `testjson` (
 `t` json /* JSON from MySQL 5.7*/ CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL
 ) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci;
-ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near 'CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL
-) ENGINE=InnoDB DEFAULT CH...' at line 2
+ERROR HY000: Data type 'json' doesn't support CHARACTER SET attribute.

here we should remove the CHARSET statement in the test, not launch this error!

@gkodinov gkodinov left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

funcs_1.is_columns_mysql_embedded needs re-recording, please attend to that.

Implement JSON as a pluggable data type using the
MariaDB_DATA_TYPE_PLUGIN1 API.

- Add JSON type parsing, type recognition, and JSON_TABLE support.
- Add implicit validation, JSON casts, and the JSON constructor.
- Add replication support for older string/blob types.
- Enforce utf8mb4_bin and reject explicit charset attributes.
- Reject unsupported numeric operations on JSON values.
- Update client and extended metadata for JSON columns.
- Display JSON columns as `json` in SHOW CREATE TABLE and DESCRIBE.
- Refactor and expand JSON tests for the new native JSON behavior.
- Keep JSON tests in the main suite to ensure they are always run.
- Align the plugin structure with the XMLTYPE plugin.
- Resolve plugin integration and cross-platform build issues.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

External Contribution All PRs from entities outside of MariaDB Foundation, Corporation, Codership agreements. GSoC

Development

Successfully merging this pull request may close these issues.

5 participants