Skip to content

Introduce SqlFieldType Abstraction and Enhance MySQL Result Handling; - #2589

Merged
an-tao merged 7 commits into
drogonframework:masterfrom
karthikeyan-netizen:feature/extend-sqlfieldtype-mysql-support
Sep 28, 2026
Merged

an-tao merged 7 commits into
drogonframework:masterfrom
karthikeyan-netizen:feature/extend-sqlfieldtype-mysql-support

Conversation

@karthikeyan-netizen

@karthikeyan-netizen karthikeyan-netizen commented Sep 12, 2026 •

Copy link
Copy Markdown

Summary

This pull request introduces a new SqlFieldType enumeration and extends MySQL result handling to provide explicit SQL field type information, safer field access, and improved metadata handling.

The goal is to provide a consistent, type-safe representation of MySQL field types while maintaining backward compatibility with the existing result-handling APIs.


Motivation

Drogon currently exposes MySQL field metadata without a dedicated abstraction for representing the underlying SQL field type.

This PR addresses that by introducing:

  • A SqlFieldType enumeration representing MySQL field types
  • MySQL-to-SqlFieldType mapping logic
  • Type-aware helper APIs for inspecting result fields
  • Safer field metadata access with bounds checking
  • Structured column metadata including native type and relevant type attributes

This provides stronger type awareness in MySQL result handling and establishes a foundation for future database-related enhancements.


Changes Introduced

1. SqlFieldType Enumeration

  • Added SqlFieldType to represent MySQL FIELD_TYPE_* values.

  • Added mappings from MySQL native field types to SqlFieldType.

  • Integrated the new type information into:

    • MysqlResultImpl
    • ResultImpl
    • Result
    • Field
  • Provides type-safe and explicit field type interpretation across the result-handling layer.

2. MySQL Result Handling Enhancements

  • Extended MysqlResultImpl to expose and use SqlFieldType.

  • Added helper methods for inspecting field types and metadata.

  • Added structured ColumnMeta information for each result column, including:

    • Logical SQL type
    • Native MySQL type
    • Length
    • Precision
    • Scale
    • Nullability
    • Unsigned attribute
  • Preserved native MySQL type information where multiple native types map to the same logical type.

3. Safety and Code Quality Improvements

  • Added bounds checking for field metadata access to avoid invalid column access and potential undefined behavior.
  • Consolidated duplicate field-processing logic in the result constructor.
  • Improved type mapping to cover the relevant MySQL field types.
  • Added support for column metadata including type-specific attributes such as length, precision, and scale, along with nullability and unsigned status.
  • Added comprehensive Doxygen documentation for new public APIs, including parameter requirements, return values, and relevant MySQL-specific behavior.
  • Added `isUnsigned()` and `isNullable()` APIs to expose column nullability and unsigned attributes from database metadata.

4. API and Implementation Integration

  • Updated affected components including:

    • field.*
    • Result.*
    • MySQL result implementations
    • Related field metadata handling
  • Existing PostgreSQL and SQLite implementations are unchanged.


Type Mapping

The implementation distinguishes between the logical SQL type and the native MySQL type.

For example:

MySQL Type Logical Type Native Type
BIT(1) Bool BIT
BIT(n) Bit BIT
TINYINT Integer / Bool TINYINT
SMALLINT Integer SMALLINT
INT Integer INT
BIGINT Integer BIGINT
FLOAT Float FLOAT
DOUBLE Double DOUBLE
DECIMAL Decimal DECIMAL
VARCHAR String VARCHAR
ENUM String ENUM
SET String SET
BLOB Binary / String BLOB
JSON Json JSON
DATE Date DATE
TIME Time TIME
DATETIME DateTime DATETIME
TIMESTAMP Timestamp TIMESTAMP
GEOMETRY Geometry GEOMETRY

The native MySQL type is preserved separately so that information such as ENUM versus SET, or VARCHAR versus CHAR, is not lost when using the logical type abstraction.


Compatibility

  • No intentional breaking changes to existing public APIs.
  • Existing result-handling behavior remains unchanged unless the new functionality is explicitly used.
  • The new type and metadata APIs are additive.
  • PostgreSQL and SQLite implementations are unaffected.

Validation

The changes have been tested in a production-like environment for approximately one month.

Validation included:

  • MySQL field type mapping
  • Result metadata handling
  • Common MySQL native field types
  • Field bounds checking
  • Existing result-processing behavior
  • Integration with existing application code

No regressions or significant performance issues were observed during testing.


Notes

Feedback is welcome regarding:

  • The design and scope of SqlFieldType
  • The separation between logical SQL types and native MySQL types
  • The ColumnMeta metadata representation
  • Public API surface and naming
  • Alternative implementation approaches that may better fit Drogon's existing architecture

@an-tao an-tao 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.

Since this PR introduces a new public type abstraction, could we add a metadata test covering the MySQL field types?

At minimum, I think the test should cover:

TINYINT
SMALLINT
MEDIUMINT
INT
BIGINT
FLOAT
DOUBLE
DECIMAL(M,D)
CHAR/VARCHAR
TEXT/TINYTEXT/MEDIUMTEXT/LONGTEXT
BLOB/TINYBLOB/MEDIUMBLOB/LONGBLOB
BINARY/VARBINARY
DATE/TIME/DATETIME
BIT
JSON
YEAR

This would also make it much harder to accidentally regress the mapping when new database metadata support is added.

Comment thread orm_lib/inc/drogon/orm/Result.h
Comment thread orm_lib/src/mysql_impl/MysqlResultImpl.h Outdated
Comment thread orm_lib/src/mysql_impl/MysqlResultImpl.h Outdated
…, including numeric, text, binary, date/time, JSON, ENUM/SET, BOOLEAN, and GEOMETRY types.
…nversion, including numeric, text, binary, date/time, JSON, ENUM/SET, BOOLEAN, and GEOMETRY types."

This reverts commit 948d899.
…, including numeric, text, binary, date/time, JSON, ENUM/SET, BOOLEAN, and GEOMETRY types.
@karthikeyan-netizen

Copy link
Copy Markdown
Author

Hi @an-tao

Comprehensive MySQL type mapping—all the major categories covered including the commonly-skipped ones (ENUM, SET, GEOMETRY). Good foundation for schema tooling. Merge.

@an-tao

an-tao commented Sep 16, 2026

Copy link
Copy Markdown
Member

@karthikeyan-netizen Thanks for this PR. This is a very useful addition to Drogon ORM, especially the ability to inspect column type information from Result. I think this is a valuable direction.

I do have some concerns about the current SqlFieldType / ColumnMeta design, though. I'm not sure it can describe SQL types correctly in all cases, since SQL types are more complicated than a single enum can represent.

The main problem is that a database type has several different aspects:

  • native database type: TINYINT, BIGINT, DECIMAL, VARCHAR, ENUM, BIT, etc.
  • type attributes: length, precision, scale, unsigned, nullable, etc.
  • logical type used by the ORM: integer, bool, decimal, string, binary, datetime, etc.

These don't always map 1:1.

For example:

DECIMAL(10,2)

should have:

precision = 10
scale = 2

but MYSQL_FIELD::length is not the same thing as decimal precision.

Similarly:

TINYINT
TINYINT(1)
BIT(1)
BIT(8)
ENUM
SET

cannot all be cleanly represented by a single logical type without losing some information.

I wonder if ColumnMeta should separate the logical type from the native database type. Something like:

struct ColumnMeta
{
    // Logical type
    SqlType type;

    // Database-specific type
    std::string nativeType;

    // Type attributes
    std::optional<int64_t> length;
    std::optional<int> precision;
    std::optional<int> scale;

    bool nullable;
    bool unsigned_;
};

For example:

DECIMAL(10,2)
    type       = Decimal
    nativeType = "DECIMAL"
    precision  = 10
    scale      = 2

VARCHAR(100)
    type       = String
    nativeType = "VARCHAR"
    length     = 100

This makes it clear that SqlType is a logical/coarse-grained type, while nativeType represents the actual database type.

I think this distinction is important if we want this API to be useful for other databases later. Otherwise, SqlFieldType may end up mixing database type information with ORM type conversion semantics.

There are also a few concrete issues in the current implementation:

  • DECIMAL precision should not be taken directly from MYSQL_FIELD::length.
  • TINY_BLOB, MEDIUM_BLOB and LONG_BLOB seem to be missing from the native type-name mapping.
  • BIT should probably distinguish BIT(1) from BIT(n > 1) before mapping it to Bool.
  • The mapping between TINYINT and Bool should be clearly defined.
  • The meaning/unit of length should be documented, since MYSQL_FIELD::length does not have the same semantics for all MySQL types.
  • The PR description says that ColumnMeta has immutable members, but the current members are mutable.

I don't think all of these have to be fixed in this PR, but I would suggest defining the semantics of SqlFieldType and ColumnMeta first. Once this becomes a public API, changing the meaning later could be difficult.

@karthikeyan-netizen

Copy link
Copy Markdown
Author

@an-tao

Thanks for the valuable feedback. You've identified the core issue perfectly—a single enum can't capture the nuances of SQL type semantics, especially across different databases.

Your proposed ColumnMeta structure is spot on. Separating logical type (SqlType) from native database type (nativeType) + attributes is the right abstraction. It keeps ORM concerns clean and leaves room for multi-database support without retrofitting later.

You're right about the concrete issues too:

  • DECIMAL precision handling needs fixing—MYSQL_FIELD::length ≠ precision
  • Missing blob variants (TINY_BLOB, MEDIUM_BLOB, LONG_BLOB)
  • BIT(1) vs BIT(n) distinction before bool mapping
  • TINYINT → Bool mapping needs explicit definition
  • length semantics must be documented (different meaning per type)
  • ColumnMeta member mutability vs docs

These shouldn't all block this PR, but you're right that locking in the API semantics first is critical. Once this is public, changing the contract becomes painful.

I'll redesign this properly—separate the concerns, document the mapping rules clearly, and make sure we get the edge cases right. Drogon deserves a solid foundation here.

Will follow up with a revised approach soon. Thanks for the thorough review.

…sion/scale handling, and isNullable() / isUnsigned() accessors.
@karthikeyan-netizen

Copy link
Copy Markdown
Author

Hi @an-tao,

I’ve revised the implementation based on the feedback and clarified the type metadata design.

The changes now separate:

  • Logical type (SqlType) — application-level type abstraction.
  • Native type (nativeType) — the actual database type name such as VARCHAR, DECIMAL, BIT, ENUM, etc.
  • Type attributes — length, precision, scale, nullable, and unsigned.

For MySQL type handling:

  • BIT(1) is mapped to Bool, while BIT(n) is mapped to Bit.
  • TINYINT(1) is treated as Bool by convention; other TINYINT values are treated as Integer.
  • ENUM and SET are represented as String at the logical level while preserving their native type name.
  • Binary/string types use the MySQL field flags to distinguish binary from character types.
  • DECIMAL scale is taken from MYSQL_FIELD::decimals, while precision is inferred from the reported display width by removing formatting overhead such as the sign and decimal point.
  • Regarding the TINY_BLOB, MEDIUM_BLOB, and LONG_BLOB mappings: these have also been added to the native type-name mapping, including their corresponding TINYTEXT, MEDIUMTEXT, and LONGTEXT representations based on the binary flag.
  • Added isUnsigned() and isNullable() APIs to provide direct access to column unsigned and nullable metadata.

I’ve also documented that the inferred DECIMAL precision comes from result metadata and may not always represent the exact declared schema precision, particularly for expressions or derived columns.

ColumnMeta remains mutable because the metadata is populated from MYSQL_FIELD after the result metadata is constructed.

For the complete MySQL type mapping, please check the Type Mapping section in the PR description.

@karthikeyan-netizen karthikeyan-netizen left a comment •

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Code is ready for review. Hoping this can be merged soon. 👍

@an-tao
an-tao merged commit e0841fc into drogonframework:master Sep 28, 2026
34 checks passed
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.

2 participants