Skip to content

[RFC]: MySQL DDL column attributes: fractional seconds, ON UPDATE for DATETIME, generated columns, INVISIBLE, strict option handling #81

Description

@simon-mundy

Proposed Version

0.5.0 — builds on #79, which needs to merge first

Basic Information

Currently the MySQL decorators handle unsigned, zerofill, charset, collate, auto_increment (and aliases), comment, column_format, storage (CreateTableDecorator.php#L118-L170, AlterTableDecorator.php#L130-L185) and after (ADD COLUMN only, in AlterTableDecorator::processAddColumns — ignored for CHANGE COLUMN and in CreateTableDecorator). Anything else is dropped without a word: none of the three switch statements (create, add, change) has a default case, so setOption('invisible', true) or setOption('srid', '4326') emit nothing.

This proposal adds the attributes the decorators can't express (GENERATED ALWAYS AS, INVISIBLE), fixes the silent drop, and files a small core sub-task for fractional-second precision, ON UPDATE on Datetime, and a wider Column::setOption() type. Every new option value goes out as an identifier, a quoted value, a whitelisted keyword or an explicit Literal — the invariant #79 introduces.

Background

What is missing on the write side, all within the >= 8.0.19 baseline unless noted:

Attribute Status Where the gap is
Fractional seconds DATETIME(6), TIMESTAMP(3), TIME(6) missing core: Datetime, Time, Timestamp have no length slot (spec %s %s)
ON UPDATE CURRENT_TIMESTAMP on DATETIME silently ignored core: only Timestamp extends AbstractTimestampColumn (AbstractTimestampColumn.php#L22-L25); Datetime extends Column
GENERATED ALWAYS AS (expr) VIRTUAL | STORED missing adapter
INVISIBLE missing adapter; MySQL 8.0.23+, above the baseline, needs documenting as such
Unknown option silently ignored adapter (see the #79 note under Considerations)
Options after an inline CHECK cosmetic getSqlInsertOffsets() doesn't know CHECK, so the last-slot options land after it: INTEGER NOT NULL CHECK (age >= 0) COMMENT 'c'. MySQL 8.4.10 (reproduced on 8.0.46) accepts that order, so low priority

The phpdb-mysql-ddl-overrides branch has GeneratedColumn, Datetime/Time/Timestamp with an $fsp argument, a MySQL Check with ENFORCED, an IndexOptionsTrait and Fulltext/Spatial index classes. It predates the invariant #79 introduces — GeneratedColumn concatenates the expression and DATETIME(' . $fsp . ') is built as a Literal — so the design and tests carry over, the rendering does not.

Considerations

Proposal(s)

Core (separate issue in php-db/phpdb, linked here):

  1. Optional precision on Datetime, Time and Timestamp (constructor argument or precision option) rendering DATETIME(6) etc. via an Argument\Literal built from an int in 0..6 (cast to string — Argument\Literal takes a string).
  2. on_update for Datetime — extend AbstractTimestampColumn, or move the option handling into a trait — emitting CURRENT_TIMESTAMP(n) when a precision is set, since MySQL requires the ON UPDATE precision to match the column's.
  3. Widen Column::setOption() from bool|string to bool|int|string|Literal.

Adapter:

  1. generated (an Argument\Literal or PhpDb\Sql\Literal expression) plus stored (bool, default false) render GENERATED ALWAYS AS (<expr>) STORED|VIRTUAL in the order above. Reject a column that also has DEFAULT or AUTO_INCREMENT — MySQL disallows both on generated columns.
  2. invisible (bool) renders INVISIBLE between the default and AUTO_INCREMENT, per ... [DEFAULT ...] [VISIBLE | INVISIBLE] [AUTO_INCREMENT] .... Document the 8.0.23 requirement.
  3. Unknown option names throw InvalidArgumentException naming the option and the accepted list — or, if agreed with Validate DDL column options and PDO DSN parameters #79, stay ignored but documented.
  4. getSqlInsertOffsets() treats CONSTRAINT and CHECK as boundaries for the last slot so COMMENT/COLUMN_FORMAT/STORAGE land before an inline check, with the cascade noted above.
  5. length on Integer is ignored by the MySQL decorator (deprecated since 8.0.17) and documented as such. The core rendering fix is Integer length option renders the display width after NOT NULL phpdb#178, so platforms that accept a display width keep INTEGER(11) NOT NULL (PostgreSQL doesn't accept one either).

Test plan:

  • new Datetime('d', options: ['precision' => 6]) (or a constructor argument) renders `d` DATETIME(6) NOT NULL; with on_update, ... ON UPDATE CURRENT_TIMESTAMP(6); same for Timestamp and Time.
  • new Decimal('total', 10, 2, options: ['generated' => new Literal('price * qty'), 'stored' => true]) (or setOption() once widened) renders `total` DECIMAL(10,2) GENERATED ALWAYS AS (price * qty) STORED NOT NULL; without stored, VIRTUAL.
  • ['invisible' => true] renders INVISIBLE in the right position; docs say 8.0.23+.
  • An unknown option throws (or is documented and tested as ignored, agreed with Validate DDL column options and PDO DSN parameters #79).
  • test/unit/Sql/Ddl/TestAsset/ColumnOptionMatrix.php (from Validate DDL column options and PDO DSN parameters #79) gains cases for every new option and pins exact SQL for CreateTable, AlterTable::addColumn and AlterTable::changeColumn.
  • An integration test executes each new attribute against the CI MySQL image.

Appendix/Additional Info

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Projects

Relationships

None yet

Development

No branches or pull requests

Issue actions