Skip to content

Allow custom column type #1068

Open
Open

Description

@Erwane

Since 5.x, only ?string can be passed to addColumn() for type.

Plan: create a method addSpecificColumnType(string $type) to allow specific type sets by extensions (in postgresql)

Activity

  1. added this to the 5.next milestone on Apr 17, 2026
  2. dereuromark commented on Apr 17, 2026

    @dereuromark
    Member

    Picking this up from #1069 (citext support) — wanted to share what I found while looking at the wider "custom column type" problem, and get your read on the API shape before writing code.

    One useful finding: DDL is not actually a blocker

    Cake\Database\Schema\PostgresSchemaDialect::columnDefinitionSql() already has a fallback at the bottom of the type map (around line 790):

    if (!$foundType) {
        $out .= ' ' . strtoupper($column['type']);
        $hasLength[] = $column['type'];
    }

    So a column with an unknown type string like 'hstore' already produces "col" HSTORE correctly, and 'vector' with limit => 1536 produces "col" VECTOR(1536). The only thing blocking these types in Migrations today is isValidColumnType() — no dialect subclass or DDL-fragment API is needed for the common case. That narrows the problem nicely.

    Design options I've been weighing

    A. addSpecificColumnType(string $type) (your plan)

    Simple, matches the existing $specificColumnTypes machinery.

    Open question: static vs instance state?

    • Static (mutate static::$specificColumnTypes): simplest, but tests leak registered types between runs. The property is currently never mutated — introducing mutation makes isolation harder.
    • Instance (per-adapter list): cleaner, but the registration call has to happen every time an adapter is constructed, which is awkward unless it runs from a central bootstrap.

    B. Configure-driven additive list — Configure::write('Migrations.Postgres.extraColumnTypes', ['hstore', 'ltree']) read once in the adapter constructor.

    Pros: set once per app, no migration-file noise, no static mutation.
    Cons: small coupling to Configure; not discoverable from the migration itself.

    C. Literal escape hatch — accept Migrations\Db\Literal in Column::$type, skip validation when it's a Literal.

    Usage: $table->addColumn('data', Literal::from('VECTOR(1536)')).

    Pros: zero registration, explicit at call site, handles parameterized types in one shot.
    Cons: changes Column::$type from strict string to string|Literal. Every ->getType() caller would need auditing — most use in_array() / string equality, which would silently stop matching for Literal and could mask bugs.

    My recommendation

    Do A + B as a hybrid: the registration method as the primary API, plus a Configure hook as the "set it and forget it" integration point. Defer C (Literal) to a follow-up unless someone shows up with a concrete parameterized-type need — speculative feature.

    Concretely:

    // In a migration, or once in Application::bootstrap():
    PostgresAdapter::addSpecificColumnType('hstore');
    
    // Or app-wide config:
    Configure::write('Migrations.Postgres.extraColumnTypes', ['hstore', 'ltree']);
    
    // Then:
    $table->addColumn('data', 'hstore');  // passes validation, Cake dialect emits correct DDL

    Things I want your opinion on before coding

    1. Static vs instance vs Configure? Leaning static + Configure hook (with a test teardown helper to reset the registration), but it's a judgment call.
    2. Scope of v1: registration only, no DDL-fragment parameter? I think yes — the dialect fallback covers the 90% case; users who need custom DDL can subclass the adapter.
    3. MySQL/SQL Server parity: keep it Postgres-only for now, or add the same hook shape across all adapters? (MySQL has the $specificColumnTypes pattern too but fewer extension types in the wild.)
  3. Erwane commented on Apr 24, 2026

    @Erwane
    Author

    After checking this plugin, Adapter instance is created in the Environment::executeMigration() on first migration found.
    Adding column to the adapter instance is not "safe", like static method.

    Configure is the cleanest and comprehensive way.
    Should be set in Migrations. options or Datasource (core) ?

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions