Skip to content

Preserve dialect-specific ARRAY types instead of adapting to generic ARRAY#480

Merged
agronholm merged 2 commits into
agronholm:masterfrom
mokashang:fix/postgresql-array-dialect-type
Jun 8, 2026
Merged

Preserve dialect-specific ARRAY types instead of adapting to generic ARRAY#480
agronholm merged 2 commits into
agronholm:masterfrom
mokashang:fix/postgresql-array-dialect-type

Conversation

@mokashang

Copy link
Copy Markdown
Contributor

Fixes #441.

Problem

When sqlacodegen reflects a PostgreSQL text[] (or any dialect-specific
ARRAY) column, get_adapted_type walks the column type's MRO and substitutes
the original sqlalchemy.dialects.postgresql.ARRAY with the generic
sqlalchemy.ARRAY. The generic ARRAY does not implement operators like
.contains(), .any() or .all(), so calling them on a generated model
raises at runtime:

NotImplementedError: ARRAY.contains() not implemented for the base ARRAY type;
please use the dialect-specific ARRAY type

The existing workaround (--options keep_dialect_types) opts out of all
generic-type adaptation, which is more than the user usually wants:
Integer becomes INTEGER, Boolean becomes BOOLEAN, and so on.

Fix

get_adapted_type now special-cases ARRAY: when the input is a subclass of
the generic ARRAY (i.e. a dialect-specific ARRAY such as
postgresql.ARRAY), the original class is preserved and only the item type
is adapted recursively. Plain sqlalchemy.ARRAY inputs are unaffected, and
no other type's adaptation behavior changes.

This matches the direction the maintainer pointed to in the issue thread
("Should the array type perhaps be special cased? I think that overall the
generalization of types is correct.").

Generated output before / after

Input column: Column("tags", postgresql.ARRAY(postgresql.TEXT))

Before:

from sqlalchemy import ARRAY, Column, Table, Text

# tags.contains([...]) -> NotImplementedError at runtime
Column('tags', ARRAY(Text()))

After:

from sqlalchemy import Column, Table, Text
from sqlalchemy.dialects.postgresql import ARRAY

# tags.contains([...]) -> 'tags @> :tags_1'
Column('tags', ARRAY(Text()))

Item types are still adapted: postgresql.DOUBLE_PRECISION still becomes
Double, postgresql.TEXT still becomes Text. Only the outer ARRAY
class is kept.

Tests

  • The existing test_arrays in tests/test_generator_tables.py is
    updated to expect from sqlalchemy.dialects.postgresql import ARRAY
    for inputs constructed with postgresql.ARRAY(...). The item-type
    adaptation it was already exercising (DOUBLE_PRECISION -> Double,
    INTEGER -> Integer) is preserved.
  • A new test_array_preserves_dialect_for_runtime_operators is added as
    a focused regression test that mirrors the scenario from the issue
    (text[] column) and documents why the dialect-specific ARRAY is
    required.
  • Full test suite passes (pytest -q -> 157 passed). ruff check,
    ruff format --check, and mypy src/sqlacodegen/generators.py all
    pass.

Notes / scope

  • Tests that construct ARRAY(SAEnum(...)) using the generic
    sqlalchemy.ARRAY directly continue to work unchanged, because the
    early-return only triggers when type(coltype) is not ARRAY (i.e.
    the input is a dialect-specific subclass).
  • The keep_dialect_types option is unaffected; this change improves
    the default path, which is what the workaround was previously needed
    to escape.

…ARRAY

When reflecting a PostgreSQL ``text[]`` (or any dialect-specific ARRAY)
column, sqlacodegen previously walked the type's MRO in
``get_adapted_type`` and substituted ``sqlalchemy.dialects.postgresql.ARRAY``
with the generic ``sqlalchemy.ARRAY``. The generic ARRAY does not implement
operators like ``.contains()``, ``.any()`` or ``.all()`` -- calling them on
a generated model raises::

    NotImplementedError: ARRAY.contains() not implemented for the base
    ARRAY type; please use the dialect-specific ARRAY type

The workaround was to pass ``--options keep_dialect_types``, but that
also forces every other type back to its dialect-specific form
(``INTEGER`` instead of ``Integer``, etc.), which is more than the user
wants.

This change special-cases ARRAY in ``get_adapted_type``: when the column
type is a subclass of the generic ARRAY (i.e. a dialect-specific ARRAY),
the original class is kept and only the item type is adapted. Plain
generic ``sqlalchemy.ARRAY`` inputs are unaffected, and no other type's
adaptation behavior changes.

The existing ``test_arrays`` is updated to expect the dialect-specific
import, and a new ``test_array_preserves_dialect_for_runtime_operators``
test locks in the regression scenario described in the issue.

Fixes agronholm#441
@coveralls

coveralls commented Jun 6, 2026

Copy link
Copy Markdown

Coverage Status

coverage: 97.794% (-0.05%) from 97.839% — mokashang:fix/postgresql-array-dialect-type into agronholm:master

Comment thread tests/test_generator_tables.py Outdated
Comment thread tests/test_generator_tables.py
Comment thread src/sqlacodegen/generators.py Outdated
@sheinbergon

Copy link
Copy Markdown
Collaborator

@agronholm fine by me. WDYT?

@sheinbergon sheinbergon requested a review from agronholm June 6, 2026 20:22
Comment thread src/sqlacodegen/generators.py
@agronholm

Copy link
Copy Markdown
Owner

@agronholm fine by me. WDYT?

I share your sentiments about the excessively verbose AI comments. And I left a minor comment of my own.

@agronholm agronholm merged commit 592747a into agronholm:master Jun 8, 2026
8 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

4 participants