Skip to content

PostgreSQL: Support SQL standard ARRAY keyword in type declarations#2356

Merged
iffyio merged 9 commits into
apache:mainfrom
LucaCappelletti94:array
Jul 26, 2026
Merged

PostgreSQL: Support SQL standard ARRAY keyword in type declarations#2356
iffyio merged 9 commits into
apache:mainfrom
LucaCappelletti94:array

Conversation

@LucaCappelletti94

Copy link
Copy Markdown
Contributor

Adds support for the SQL standard ARRAY keyword in array type declarations, such as INTEGER ARRAY and INTEGER ARRAY[4], which PostgreSQL accepts anywhere a type is expected (column definitions, casts, and the :: operator). It is gated behind a new Dialect::supports_array_typedef_with_keyword method, enabled for PostgreSQL, and the keyword form with its optional cardinality is represented by a new ArrayElemTypeDef::Keyword variant.

This also unifies the existing MySQL multi-valued index syntax CAST(... AS UNSIGNED ARRAY). The array: bool field on Expr::Cast is removed and the trailing ARRAY keyword is now captured through ArrayElemTypeDef::Keyword for every dialect, so this is a breaking change to the AST. Parser behavior is unchanged for all dialects, only the representation is unified, and round-trip serialization is preserved.

@Rattenkrieg

Copy link
Copy Markdown

Hit this same limitation independently: production Flyway migrations using the SQL-standard keyword form on a user-defined type (ALTER TABLE t ADD COLUMN c currency ARRAY). Tested this branch against those migrations plus adjacent shapes (INTEGER ARRAY, TEXT ARRAY[4], NUMERIC(10,2) ARRAY, public.currency ARRAY, INT ARRAY DEFAULT ARRAY[]::INT[]) - all parse and round-trip correctly. Two suggestions:

1. Enable it for GenericDialect. The README asks for dialect-specific syntax to be accepted by both the relevant dialect and GenericDialect, and the bracket form's supports_array_typedef_with_brackets is already enabled for generic, so the keyword form should follow:

 // src/dialect/generic.rs
+    fn supports_array_typedef_with_keyword(&self) -> bool {
+        true
+    }

2. Widen the tests. The current tests cover built-in element types in CREATE TABLE and CAST. These cases exercise different paths (custom and schema-qualified type names, ALTER TABLE, parenthesized typmods, and the suffix-vs-constructor ambiguity) and are worth pinning:

pg_and_generic().verified_stmt("CREATE TABLE t (c currency ARRAY)");
pg_and_generic().verified_stmt("CREATE TABLE t (c public.currency ARRAY)");
pg_and_generic().verified_stmt("ALTER TABLE t ADD COLUMN c currency ARRAY");
pg_and_generic().verified_stmt("CREATE TABLE t (c NUMERIC(10,2) ARRAY)");
pg_and_generic().verified_stmt("CREATE TABLE t (c INT ARRAY DEFAULT ARRAY[]::INT[])");

(pg() instead of pg_and_generic() if you'd rather keep the generic change separate.)

Can send both as a patch against your branch if that's easier.

@LucaCappelletti94

Copy link
Copy Markdown
Contributor Author

@iffyio could you check this out when you have time?

Comment thread src/ast/data_type.rs Outdated
Comment on lines +1168 to +1169
/// Keyword style with an optional size, e.g. `INT ARRAY` or `INT ARRAY[4]`.
Keyword(Box<DataType>, Option<u64>),

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.

Suggested change
/// Keyword style with an optional size, e.g. `INT ARRAY` or `INT ARRAY[4]`.
Keyword(Box<DataType>, Option<u64>),
/// Qualified by a data type and optional size, e.g. `INT ARRAY` or `INT ARRAY[4]`.
Qualified(Box<DataType>, Option<u64>),

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.

Renamed the variant and propagated it

Comment thread src/dialect/generic.rs Outdated
true
}

fn supports_array_typedef_with_keyword(&self) -> bool {

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.

Suggested change
fn supports_array_typedef_with_keyword(&self) -> bool {
fn supports_type_qualified_array(&self) -> bool {

actually I wonder can we have the parser always accept the syntax (it doesnt seem like it would be ambiguous)?

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.

I generally tend to prefer keeping things as strict as possible, but I understand the general mantra of this parser is permissiveness.

I dropped supports_array_typedef_with_keyword entirely and now accept T ARRAY / T ARRAY[n] in every dialect. The bracket form stays gated since [ clashes with identifier quoting in MSSQL and SQLite, but the ARRAY keyword has no such clash.

This means that every dialect now also parses T ARRAY[4], including MySQL where only the bare T ARRAY is real SQL. No test treats that as an error, and I am not sure whether this is a good choice or not.

Again, I understand I tend to lean on strictness so maybe I am worrying over nothing. LMK your opinion.

Comment thread src/parser/mod.rs Outdated
Comment on lines +2907 to +2912
// A trailing `ARRAY` keyword makes the target an array type, e.g. MySQL's
// `CAST(... AS UNSIGNED ARRAY)`. PostgreSQL already consumes it while
// parsing the data type, so the guard avoids wrapping it twice.
if !matches!(data_type, DataType::Array(_)) && self.parse_keyword(Keyword::ARRAY) {
data_type = DataType::Array(ArrayElemTypeDef::Keyword(Box::new(data_type), None));
}

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.

not sure I follow the intent here, I imagined parse_data_type() already should produce the new variant here, removing the need for any special handling?

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.

That guard existed because the keyword form used to be gated off for MySQL, see #2356 (comment)

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

Thanks @LucaCappelletti94 just a minor cleanup comment, otherwise I think this looks good

Comment thread src/ast/mod.rs Outdated
Co-authored-by: Ifeanyi Ubah <ify1992@yahoo.com>

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

LGTM! Thanks @LucaCappelletti94!

@iffyio
iffyio added this pull request to the merge queue Jul 26, 2026
Merged via the queue into apache:main with commit 09827e1 Jul 26, 2026
10 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.

3 participants