squonk_ast/ast/ty.rs
1// SPDX-License-Identifier: MIT
2// Copyright (c) 2026 Moderately AI Inc.
3
4//! SQL type-name AST nodes shared by casts, DDL, parameters, and temporal syntax.
5
6use super::{Extension, Ident, Literal, NoExt, ObjectName};
7use crate::vocab::Meta;
8use thin_vec::ThinVec;
9
10/// SQL data types in the M1 AST surface.
11///
12/// Variants intentionally keep spelling tags where common dialects spell the
13/// same canonical type differently (`INT` vs `INTEGER`, `VARCHAR` vs
14/// `CHARACTER VARYING`, `BOOL` vs `BOOLEAN`). Tier-1 rendering can therefore
15/// round-trip the parsed surface, while dialect-target rendering can normalize
16/// to a target spelling.
17///
18/// Dialect-only type names live in the same closed enum (a type is one
19/// canonical shape, not a per-dialect tree). Their *recognition* is gated
20/// by [`TypeNameSyntax`](crate::dialect::TypeNameSyntax) data, so a name like
21/// `TINYINT` resolves to its built-in variant only under a dialect that opts in;
22/// elsewhere the same word falls through to [`UserDefined`](Self::UserDefined).
23///
24/// The [`Other(X)`](Self::Other) seam (ADR-0009) lets an out-of-tree consumer carry a
25/// host-owned type node produced by the parser crate's `Dialect::parse_data_type_hook`
26/// without spelling it as a stock variant or forcing it into
27/// [`UserDefined`](Self::UserDefined). Stock builtins use `X = NoExt` (uninhabited), so
28/// the arm is statically dead and `DataType` (= `DataType<NoExt>`) keeps its node size.
29#[derive(Clone, Debug, PartialEq, Eq, Hash)]
30#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
31#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
32pub enum DataType<X: Extension = NoExt> {
33 /// A boolean type (`BOOLEAN`/`BOOL`).
34 Boolean {
35 /// Exact source spelling retained for faithful rendering.
36 spelling: BooleanTypeName,
37 /// Source location and node identity.
38 meta: Meta,
39 },
40 /// MySQL `TINYINT` (1-byte integer); recognized only under
41 /// [`TypeNameSyntax::extended_scalar_type_names`](crate::dialect::TypeNameSyntax).
42 TinyInt {
43 /// Optional integer display width `(M)`, e.g. `TINYINT(1)`. This is *display
44 /// metadata* — it governs the left-pad width under `ZEROFILL` — never the
45 /// stored value's precision or range (`INT(1)` still stores the full 32-bit
46 /// range). Canonically MySQL's (`INT(11)`-style, deprecated in 8.0.17+ but
47 /// ubiquitous in dumps); SQLite accepts it through affinity type-name
48 /// absorption. Gated by
49 /// [`TypeNameSyntax::integer_display_width`](crate::dialect::TypeNameSyntax),
50 /// so ANSI/PostgreSQL reject the parenthesized form on a built-in integer.
51 display_width: Option<u32>,
52 /// Source location and node identity.
53 meta: Meta,
54 },
55 /// A `SMALLINT` (2-byte integer).
56 SmallInt {
57 /// Optional display width for this syntax.
58 display_width: Option<u32>,
59 /// Source location and node identity.
60 meta: Meta,
61 },
62 /// MySQL `MEDIUMINT` (3-byte integer); recognized only under
63 /// [`TypeNameSyntax::extended_scalar_type_names`](crate::dialect::TypeNameSyntax).
64 MediumInt {
65 /// Optional display width for this syntax.
66 display_width: Option<u32>,
67 /// Source location and node identity.
68 meta: Meta,
69 },
70 /// An `INT`/`INTEGER` (4-byte integer).
71 Integer {
72 /// Exact source spelling retained for faithful rendering.
73 spelling: IntegerTypeName,
74 /// Optional display width for this syntax.
75 display_width: Option<u32>,
76 /// Source location and node identity.
77 meta: Meta,
78 },
79 /// A `BIGINT` (8-byte integer).
80 BigInt {
81 /// Optional display width for this syntax.
82 display_width: Option<u32>,
83 /// Source location and node identity.
84 meta: Meta,
85 },
86 /// An exact `DECIMAL`/`NUMERIC(p, s)` type.
87 Decimal {
88 /// Exact source spelling retained for faithful rendering.
89 spelling: DecimalTypeName,
90 /// The precision modifier. Signed because PostgreSQL parses the `numeric`/`decimal`
91 /// type-modifier arguments as a general expression list at raw-parse time, so a signed
92 /// modifier (`numeric(-3, 6)`) is accepted and validated only later — the sign is gated
93 /// for acceptance by
94 /// [`TypeNameSyntax::signed_type_modifier`](crate::dialect::TypeNameSyntax); off
95 /// elsewhere a leading `-` is a clean parse error.
96 precision: Option<i32>,
97 /// The scale modifier. Signed for the same reason as [`precision`](Self::Decimal::precision):
98 /// PostgreSQL accepts a negative scale (`numeric(5, -2)` rounds to tens), which the
99 /// standard/MySQL reject.
100 scale: Option<i32>,
101 /// Source location and node identity.
102 meta: Meta,
103 },
104 /// A `FLOAT[(p)]` approximate numeric type.
105 Float {
106 /// Numeric precision specified by this syntax.
107 precision: Option<u32>,
108 /// Source location and node identity.
109 meta: Meta,
110 },
111 /// A `REAL` (single-precision floating-point) type.
112 Real {
113 /// Source location and node identity.
114 meta: Meta,
115 },
116 /// A `DOUBLE PRECISION` (double-precision floating-point) type.
117 Double {
118 /// Exact source spelling retained for faithful rendering.
119 spelling: DoubleTypeName,
120 /// Source location and node identity.
121 meta: Meta,
122 },
123 /// A variable-length character LOB. The spelling tag carries the MySQL size
124 /// family (`TINYTEXT`/`MEDIUMTEXT`/`LONGTEXT`), which differ only in declared
125 /// maximum length; the bare PostgreSQL/MySQL `TEXT` is the default spelling.
126 Text {
127 /// Exact source spelling retained for faithful rendering.
128 spelling: TextTypeName,
129 /// MySQL's character-set type annotation — the whole TEXT size family admits it in
130 /// column position (engine-measured on mysql:8.4: `TEXT CHARACTER SET utf8mb4`,
131 /// `TINYTEXT ASCII`, `LONGTEXT BINARY`, `TEXT BYTE` all prepare). See the field
132 /// docs on [`Character`](Self::Character); `None` outside MySQL.
133 charset: Option<Box<CharsetAnnotation>>,
134 /// Source location and node identity.
135 meta: Meta,
136 },
137 /// A binary LOB family: MySQL `BLOB`/`TINYBLOB`/`MEDIUMBLOB`/`LONGBLOB`. The
138 /// binary analog of [`Text`](Self::Text); recognized only under
139 /// [`TypeNameSyntax::extended_scalar_type_names`](crate::dialect::TypeNameSyntax).
140 Blob {
141 /// Exact source spelling retained for faithful rendering.
142 spelling: BlobTypeName,
143 /// Source location and node identity.
144 meta: Meta,
145 },
146 /// A fixed/variable-length character type (`CHAR`/`VARCHAR`/`CHARACTER`/…).
147 Character {
148 /// Exact source spelling retained for faithful rendering.
149 spelling: CharacterTypeName,
150 /// Optional size for this syntax.
151 size: Option<u32>,
152 /// MySQL's character-set type annotation — the grammar's
153 /// `opt_charset_with_opt_binary` production: `CHARACTER SET <name>` (or the
154 /// `CHARSET` synonym), the `ASCII`/`UNICODE`/`BYTE` shortcuts, and/or the trailing
155 /// `BINARY` binary-collation modifier. Part of the *type*, not a column attribute:
156 /// it must directly follow the type (and its length) and is rejected once any
157 /// column attribute intervenes (`CHAR(5) NOT NULL CHARACTER SET x` is an
158 /// `ER_PARSE_ERROR` on mysql:8), which is why it lives on the type node — unlike
159 /// `COLLATE`, a free-floating column attribute. Recognized only under
160 /// [`TypeNameSyntax::character_set_annotation`](crate::dialect::TypeNameSyntax),
161 /// and only on the non-national spellings (`CHAR`/`CHARACTER`/`VARCHAR`): the
162 /// national forms (`NCHAR`/`NATIONAL CHAR`) fix their own charset and reject the
163 /// annotation, so they always carry `None`. `None` for every non-MySQL char type.
164 ///
165 /// Boxed: the annotation is a rare, fat payload (MySQL-only, and only on annotated
166 /// char types), so ADR-0007 keeps it off the hot [`DataType`] node's inline width —
167 /// the box is paid only on the rare annotated path.
168 charset: Option<Box<CharsetAnnotation>>,
169 /// Source location and node identity.
170 meta: Meta,
171 },
172 /// A fixed/variable-length binary type (`BINARY`/`VARBINARY`).
173 Binary {
174 /// Exact source spelling retained for faithful rendering.
175 spelling: BinaryTypeName,
176 /// Optional size for this syntax.
177 size: Option<u32>,
178 /// Source location and node identity.
179 meta: Meta,
180 },
181 /// A bit-string type: `BIT [VARYING] [(n)]` (PostgreSQL `bit`/`varbit`).
182 Bit {
183 /// `BIT VARYING` (variable-length) vs fixed-length `BIT`.
184 varying: bool,
185 /// Optional size for this syntax.
186 size: Option<u32>,
187 /// Source location and node identity.
188 meta: Meta,
189 },
190 /// The PostgreSQL `JSON` type (distinct from the `jsonb` user-defined name).
191 Json {
192 /// Source location and node identity.
193 meta: Meta,
194 },
195 /// The `UUID` type (PostgreSQL `uuid`, DuckDB `UUID`). A single-keyword scalar
196 /// carrying no parameters, shaped exactly like [`Json`](Self::Json). Recognition is
197 /// *ungated* — the word resolves to this canonical variant wherever a type name is
198 /// admitted, so a type planner reads one UUID identity regardless of dialect rather
199 /// than a per-dialect [`UserDefined`](Self::UserDefined) name. This is *identity*, not
200 /// *acceptance*: dialects that reject `UUID` in a given position still reject it there
201 /// (e.g. MySQL's narrow `CAST` target set excludes `UUID`), the same way `JSON` is a
202 /// first-class variant yet remains subject to each dialect's positional rules.
203 Uuid {
204 /// Source location and node identity.
205 meta: Meta,
206 },
207 /// A `DATE` type.
208 Date {
209 /// Source location and node identity.
210 meta: Meta,
211 },
212 /// A `TIME [WITH|WITHOUT TIME ZONE]` type.
213 Time {
214 /// Exact source spelling retained for faithful rendering.
215 spelling: TimeTypeName,
216 /// Numeric precision specified by this syntax.
217 precision: Option<u32>,
218 /// The `WITH`/`WITHOUT TIME ZONE` qualifier; see [`TimeZone`].
219 time_zone: TimeZone,
220 /// Source location and node identity.
221 meta: Meta,
222 },
223 /// A `TIMESTAMP [WITH|WITHOUT TIME ZONE]` type.
224 Timestamp {
225 /// Exact source spelling retained for faithful rendering.
226 spelling: TimestampTypeName,
227 /// Numeric precision specified by this syntax.
228 precision: Option<u32>,
229 /// The `WITH`/`WITHOUT TIME ZONE` qualifier; see [`TimeZone`].
230 time_zone: TimeZone,
231 /// Source location and node identity.
232 meta: Meta,
233 },
234 /// An `INTERVAL` type, with an optional field qualifier and precision.
235 Interval {
236 /// Optional fields for this syntax.
237 fields: Option<IntervalFields>,
238 /// Numeric precision specified by this syntax.
239 precision: Option<u32>,
240 /// Source location and node identity.
241 meta: Meta,
242 },
243 /// `ENUM('a', 'b', ...)`: a string type constrained to a value list — MySQL's column
244 /// type and DuckDB's `x::ENUM('a', 'b')` cast target. Structural (it carries the value
245 /// list), so it is one canonical variant rather than a spelling tag; recognized under
246 /// [`TypeNameSyntax::enum_type`](crate::dialect::TypeNameSyntax). The values are string
247 /// [`Literal`]s whose spelling round-trips from their span.
248 Enum {
249 /// Values in source order.
250 values: ThinVec<Literal>,
251 /// MySQL's character-set type annotation, which `ENUM` admits in column position
252 /// (engine-measured on mysql:8.4: `ENUM('a') CHARACTER SET utf8mb4` / `ASCII` /
253 /// `BYTE` / `BINARY` all prepare). See the field docs on
254 /// [`Character`](Self::Character); always `None` for DuckDB (whose grammar has no
255 /// such annotation) and outside MySQL.
256 charset: Option<Box<CharsetAnnotation>>,
257 /// Source location and node identity.
258 meta: Meta,
259 },
260 /// MySQL `SET('a', 'b', ...)`: a string type whose value is a subset of the
261 /// listed members, recognized under
262 /// [`TypeNameSyntax::set_type`](crate::dialect::TypeNameSyntax). Shares
263 /// [`Enum`](Self::Enum)'s value-list shape; the two stay distinct variants because the
264 /// membership semantics differ.
265 Set {
266 /// Values in source order.
267 values: ThinVec<Literal>,
268 /// MySQL's character-set type annotation — same surface as [`Enum`](Self::Enum)
269 /// (engine-measured on mysql:8.4).
270 charset: Option<Box<CharsetAnnotation>>,
271 /// Source location and node identity.
272 meta: Meta,
273 },
274 /// A numeric type carrying MySQL's `SIGNED`/`UNSIGNED`/`ZEROFILL` modifiers,
275 /// gated by [`TypeNameSyntax::numeric_modifiers`](crate::dialect::TypeNameSyntax).
276 ///
277 /// One wrapper models the modifier for every numeric inner type, so the flag is
278 /// kept once rather than duplicated onto each numeric variant — the
279 /// same wrap-the-inner-type shape as [`Array`](Self::Array). `element` is
280 /// optional because MySQL's `CAST(x AS UNSIGNED)` / `CAST(x AS SIGNED)` use the
281 /// modifier as a standalone integer cast target that names no base type.
282 NumericModifier {
283 /// Optional element for this syntax.
284 element: Option<Box<DataType<X>>>,
285 /// The `SIGNED`/`UNSIGNED` modifier; see [`Signedness`].
286 signedness: Signedness,
287 /// Whether the zerofill form was present in the source.
288 zerofill: bool,
289 /// Source location and node identity.
290 meta: Meta,
291 },
292 /// An array type (`T[]`, `T[n]`, or `T ARRAY[n]`).
293 Array {
294 /// The array element type.
295 element: Box<DataType<X>>,
296 /// Fixed cardinality for the DuckDB fixed-size `ARRAY` (`INTEGER[3]` /
297 /// `INTEGER ARRAY[3]`), or `None` for the variable-length list (`INTEGER[]` /
298 /// `INTEGER ARRAY`). The two are distinct DuckDB types (`ARRAY` enforces the
299 /// count, `LIST` is unbounded); PostgreSQL accepts the bound syntactically but
300 /// ignores it (`int[3]` binds identically to `int[]`), so the value is retained
301 /// for round-trip regardless of dialect and its enforcement is a consumer concern.
302 size: Option<u32>,
303 /// Bracket `T[]`/`T[n]` vs keyword `T ARRAY`/`T ARRAY[n]` surface. One canonical
304 /// array-type shape covers both spellings; the tag round-trips the
305 /// written form.
306 spelling: ArrayTypeSpelling,
307 /// Source location and node identity.
308 meta: Meta,
309 },
310 /// An anonymous composite (record) type: DuckDB `STRUCT(a INTEGER, b VARCHAR)` or
311 /// the standard `ROW(...)` spelling of the same shape. One canonical named-field
312 /// list covers both spellings; `spelling` records the written keyword.
313 /// Recognized only under
314 /// [`TypeNameSyntax::composite_types`](crate::dialect::TypeNameSyntax); ANSI/
315 /// PostgreSQL reject the anonymous form (PostgreSQL has only *named* composite
316 /// types and spells `ROW` as a value constructor, never a type).
317 Struct {
318 /// fields in source order.
319 fields: ThinVec<StructTypeField<X>>,
320 /// Exact source spelling retained for faithful rendering.
321 spelling: StructTypeSpelling,
322 /// Source location and node identity.
323 meta: Meta,
324 },
325 /// An anonymous tagged-union type: DuckDB `UNION(tag1 T1, tag2 T2, ...)`. Shares the
326 /// named-member shape of [`Struct`](Self::Struct) but stays a distinct variant
327 /// because the semantics differ (a tagged sum type vs a product type), exactly as
328 /// [`Enum`](Self::Enum)/[`Set`](Self::Set) share a value-list shape yet stay
329 /// distinct. Gated by the same `composite_types` flag.
330 Union {
331 /// members in source order.
332 members: ThinVec<StructTypeField<X>>,
333 /// Source location and node identity.
334 meta: Meta,
335 },
336 /// An anonymous map type: DuckDB `MAP(K, V)`, where `K` and `V` are themselves
337 /// types (`MAP(VARCHAR, STRUCT(x INTEGER))`, `MAP(INTEGER[], VARCHAR)`). DuckDB
338 /// desugars a map to a `LIST` of `STRUCT(key, value)` in its serialized tree; the
339 /// canonical shape keeps the written key/value types directly (the desugaring is a
340 /// representation-equivalent difference the structural oracle normalizes, like the
341 /// `list_value`/`struct_pack` value desugars). Gated by `composite_types`.
342 Map {
343 /// The map's key type.
344 key: Box<DataType<X>>,
345 /// Value supplied by this syntax.
346 value: Box<DataType<X>>,
347 /// Source location and node identity.
348 meta: Meta,
349 },
350 /// A ClickHouse parametric type combinator wrapping exactly one inner type:
351 /// `Nullable(T)` (and the sibling `LowCardinality(T)`, which shares this exact
352 /// single-inner-type shape). One canonical variant with a [`WrappedTypeKind`] axis
353 /// models the whole wrapper-shaped family — the same wrap-one-inner-type idiom as
354 /// [`NumericModifier`](Self::NumericModifier), which keeps its modifier flag once
355 /// rather than duplicating a variant per numeric inner type — so a further
356 /// wrapper keyword is a new [`WrappedTypeKind`] arm plus a parser branch, never a
357 /// new `DataType` variant or `Render` arm.
358 ///
359 /// `inner` is a full, recursively-nested type, so `Nullable(DECIMAL(10, 2))` and
360 /// `Nullable(String)[]` both parse. ClickHouse constrains *composability* only at
361 /// bind time — it rejects `Nullable(Nullable(T))` and `Nullable(Array(T))` with a
362 /// type-resolution `DB::Exception`, not a grammar error — so those nestings
363 /// parse-accept here and the constraint is a binder concern, the same parse-vs-bind
364 /// split as ragged array literals.
365 Wrapped {
366 /// Which wrapper (`Nullable`/`LowCardinality`); see [`WrappedTypeKind`].
367 kind: WrappedTypeKind,
368 /// The wrapped inner type.
369 inner: Box<DataType<X>>,
370 /// Source location and node identity.
371 meta: Meta,
372 },
373 /// ClickHouse `FixedString(N)`: a fixed-length byte string of exactly `N` bytes.
374 ///
375 /// Deliberately its own variant rather than a [`WrappedTypeKind`](Self::Wrapped) arm:
376 /// its argument is a scalar length `N`, not a nested inner type, so it does not share
377 /// the single-inner-type wrapper shape. It instead reuses the length-carrying string
378 /// idiom of [`Character`](Self::Character)/[`Binary`](Self::Binary), but with the
379 /// length as a **non-optional** `u32` rather than `Option<u32>`: ClickHouse's grammar
380 /// makes `N` mandatory (a bare `FixedString` with no parens is a different, invalid
381 /// spelling that falls through to the user-defined-type path), so the required argument
382 /// is encoded in the type itself instead of a runtime `None` those spelling-tagged
383 /// variants must tolerate. That mandatory-scalar shape is why it is not folded onto
384 /// [`Character`](Self::Character) under a spelling tag — and `FixedString` is a *byte*
385 /// string carrying no charset annotation, unlike the character variants.
386 ///
387 /// `N` is a positive integer in ClickHouse; the grammar admits any `u32` literal (`0`
388 /// included) and ClickHouse rejects `FixedString(0)` only at type resolution, the same
389 /// parse-vs-bind split as the `Wrapped` composability rejects. Recognized only under
390 /// [`TypeNameSyntax::fixed_string_type`](crate::dialect::TypeNameSyntax); the canonical
391 /// render emits ClickHouse's mixed-case spelling (`FixedString`, never `FIXEDSTRING`).
392 FixedString {
393 /// Length bound specified by this syntax.
394 length: u32,
395 /// Source location and node identity.
396 meta: Meta,
397 },
398 /// ClickHouse `DateTime64(P[, 'timezone'])`: a sub-second timestamp with `P` fractional
399 /// digits of precision and an optional IANA time-zone name.
400 ///
401 /// Its own variant rather than a spelling tag on [`Timestamp`](Self::Timestamp), for two
402 /// shape reasons that mirror [`FixedString`](Self::FixedString)'s mandatory-scalar
403 /// argument. First, precision is **mandatory**: ClickHouse's `DateTime64` has no bare
404 /// spelling (the `(P)` is required; a bare `DateTime64` with no parens is a different
405 /// spelling that falls through to the user-defined-type path), so `precision` is a
406 /// non-optional `u32` rather than the `Option<u32>` that the ANSI/MySQL
407 /// [`Timestamp`](Self::Timestamp)/[`Time`](Self::Time) variants carry. Second, its time
408 /// zone is a single-quoted string-literal *argument*, not the ANSI `WITH TIME ZONE`
409 /// flag those variants encode as a [`TimeZone`] tag — a different surface that would not
410 /// fit the tag. Those two differences are why it is not folded onto `Timestamp`.
411 ///
412 /// `timezone` is the optional second argument, held as the source-spelled string
413 /// [`Literal`] so its exact quoting round-trips (the [`Enum`](Self::Enum) value-member
414 /// idiom). `P` is parsed as any `u32` literal; ClickHouse's documented `0..=9` range is a
415 /// bind-time reject, not a grammar error — the same parse-vs-bind split as
416 /// [`FixedString`](Self::FixedString)'s positive-length rule. Recognized only under
417 /// [`TypeNameSyntax::datetime64_type`](crate::dialect::TypeNameSyntax); the canonical
418 /// render emits ClickHouse's mixed-case spelling (`DateTime64`, never `DATETIME64`).
419 ///
420 /// The timezone-only sibling `DateTime('timezone')` (no precision, on the plain
421 /// `DateTime` type) is a distinct surface and out of scope here — see the deferral on
422 /// the owning ticket.
423 DateTime64 {
424 /// Numeric precision specified by this syntax.
425 precision: u32,
426 /// Optional timezone for this syntax.
427 timezone: Option<Box<Literal>>,
428 /// Source location and node identity.
429 meta: Meta,
430 },
431 /// ClickHouse `Nested(name1 Type1, name2 Type2, ...)`: a named-field composite that
432 /// models a repeated group — semantically a set of parallel same-length arrays sharing
433 /// a common column prefix, not a single record value.
434 ///
435 /// Shares the named-field [`StructTypeField`] shape of [`Struct`](Self::Struct)/
436 /// [`Union`](Self::Union) but stays a distinct variant because the semantics differ (a
437 /// repeated nested structure vs a product or a tagged sum), exactly as
438 /// [`Union`](Self::Union) shares `Struct`'s field shape yet stays distinct and
439 /// [`Enum`](Self::Enum)/[`Set`](Self::Set) share a value-list shape yet stay distinct.
440 /// A spelling tag on `Struct` would conflate those semantics and force the
441 /// `composite_types` gate; `Nested` instead carries its own
442 /// [`TypeNameSyntax::nested_type`](crate::dialect::TypeNameSyntax) flag
443 /// (one behaviour = one flag) and its canonical render re-emits `Nested`, never `STRUCT`.
444 ///
445 /// A field type is a full, recursively-nested type, so `Nested(x Nested(y UInt8))`
446 /// parses: ClickHouse permits arbitrary nesting levels (under `flatten_nested=0`), and
447 /// any level limit is a setting/bind concern, not a grammar error — the same parse-vs-bind
448 /// split as the [`Wrapped`](Self::Wrapped) composability rejects. At least one field is
449 /// required (a bare `Nested()` is a syntax error), enforced by the one-or-more field list.
450 Nested {
451 /// fields in source order.
452 fields: ThinVec<StructTypeField<X>>,
453 /// Source location and node identity.
454 meta: Meta,
455 },
456 /// ClickHouse's fixed-bit-width integer type names: the signed `Int8`/`Int16`/`Int32`/
457 /// `Int64`/`Int128`/`Int256` family and their unsigned `UInt8`…`UInt256` siblings.
458 ///
459 /// One canonical variant carries the signedness ([`signed`](Self::FixedWidthInt::signed))
460 /// and the width ([`IntWidth`]) as *data*, rather than a variant per width — the whole
461 /// bit-width family travels together in a dialect exactly as MySQL's
462 /// `TINYINT`/`MEDIUMINT`/… ride one recognition gate, so it is one behaviour and one
463 /// [`DataType`] shape (the [`extended_scalar_type_names`](crate::dialect::TypeNameSyntax)
464 /// precedent), gated by
465 /// [`TypeNameSyntax::bit_width_integer_names`](crate::dialect::TypeNameSyntax). Signedness
466 /// and width are the only axes, so the whole family is one `DataType` shape and one
467 /// `Render` arm rather than twelve.
468 ///
469 /// Kept distinct from [`Integer`](Self::Integer)/[`BigInt`](Self::BigInt) rather than
470 /// folded under a spelling tag: `Int32`/`Int64` name the same underlying types as `INT`
471 /// and `BIGINT` but round-trip their own written surface, and the narrower/wider widths
472 /// (`Int8`/`Int128`/`Int256`) have no ANSI integer variant at all. The names take no
473 /// arguments, so a bare `Int256` off-gate is an ordinary user-defined type name (the
474 /// trivial off-gate boundary, like a bare `Nullable`), never a parse error. The canonical
475 /// render emits ClickHouse's mixed-case spelling (`Int256`/`UInt256`, never `INT256`).
476 FixedWidthInt {
477 /// `true` for the signed `Int*` spellings, `false` for the unsigned `UInt*`.
478 signed: bool,
479 /// The integer bit width (`Int8`…`Int256`); see [`IntWidth`].
480 width: IntWidth,
481 /// Source location and node identity.
482 meta: Meta,
483 },
484 /// A user-defined (or otherwise non-built-in) type reference — a dot-separated
485 /// qualified [`ObjectName`] with an optional parenthesized modifier list. Every type
486 /// name no typed variant claims lands here (the FALLBACK path), so a bare `GEOMETRY`,
487 /// `citext`, or `schema.mytype` is a `UserDefined`.
488 UserDefined {
489 /// Name referenced by this syntax.
490 name: ObjectName,
491 /// The parenthesized modifier arguments, as constant [`Literal`]s whose spelling
492 /// round-trips from their span. An unsigned-integer modifier (`FOO(3)`) parses
493 /// under every dialect; a string-literal modifier (`GEOMETRY('OGC:CRS84')`) is
494 /// DuckDB's, admitted only under
495 /// [`TypeNameSyntax::string_type_modifiers`](crate::dialect::TypeNameSyntax).
496 /// Empty when the name carries no parenthesized list.
497 modifiers: ThinVec<Literal>,
498 /// Source location and node identity.
499 meta: Meta,
500 },
501 /// SQLite's liberal affinity type name — a free run of one-or-more space-separated
502 /// words (`UNSIGNED BIG INT`, `LONG INTEGER`, `NATIVE CHARACTER`, and even the
503 /// misspelled `INTEGEB PRIMARI KEY`) with an optional one-or-two-argument parenthesized
504 /// modifier (`VARCHAR(123,456)`, `FLOATING POINT(5,10)`). SQLite has no fixed type
505 /// vocabulary — a column/cast type is any `ids ...` token run terminated by a
506 /// column-constraint keyword, a comma, or a close paren — so a multi-word or two-argument
507 /// name that no typed variant can faithfully hold lands here instead of over-rejecting.
508 /// Recognized only under
509 /// [`TypeNameSyntax::liberal_type_names`](crate::dialect::TypeNameSyntax) (SQLite +
510 /// Lenient).
511 ///
512 /// # Why its own variant, not [`UserDefined`](Self::UserDefined)
513 ///
514 /// `UserDefined` carries an [`ObjectName`] — a *dot*-separated qualified name
515 /// (`schema.type`) that renders with `.` — and its `modifiers` are unsigned `u32`s. A
516 /// liberal affinity name is a *space*-separated word run with a distinct render, so it is
517 /// a genuinely different canonical shape (ADR-0011: one type = one shape), not a spelling
518 /// of the same `UserDefined` reference. Folding it in would force a render branch and a
519 /// separator tag onto the common single-word user-defined path and grow its hot node; a
520 /// dedicated variant keeps `UserDefined` untouched. A bare single-word affinity name
521 /// (`BANANA`) still parses to `UserDefined` — this variant is reached only when a second
522 /// word or a two-argument paren list makes the typed / user-defined parse insufficient
523 /// (the FALLBACK ordering: typed variants win wherever they can faithfully represent the
524 /// input, so `INT`, `DOUBLE PRECISION`, `VARCHAR(255)`, `NATIONAL CHARACTER(15)` keep
525 /// their typed variants under SQLite).
526 ///
527 /// # Boundary (engine-probed, rusqlite/sqlite3 3.53.2 & 3.43.2)
528 ///
529 /// The word run terminates at a column-constraint keyword — `PRIMARY`, `NOT`, `NULL`,
530 /// `UNIQUE`, `CHECK`, `DEFAULT`, `COLLATE`, `REFERENCES`, `CONSTRAINT`, `AS`, `GENERATED`
531 /// (each engine-probed as a terminator, e.g. `x MY PRIMARY` rejects because `PRIMARY`
532 /// starts `PRIMARY KEY`, not the type) — or a comma / close paren; every other
533 /// identifier-or-non-reserved-keyword word is absorbed (`x FOO BAR BAZ QUX` accepts). A
534 /// reserved word (`SELECT`) can never be a type word (`x SELECT` rejects). The optional
535 /// paren list holds at most two arguments (`FOO(1,2)` accepts, `FOO(1,2,3)` rejects).
536 Liberal {
537 /// The one-or-more space-separated affinity words, each an [`Ident`] whose quote
538 /// style round-trips (a quoted `"foo bar"` word counts as one). Rendered
539 /// space-separated.
540 words: ThinVec<Ident>,
541 /// The zero-, one-, or two-element parenthesized modifier list (`VARCHAR(123,456)`
542 /// carries `[123, 456]`). Empty when the name has no parens. Unsigned like
543 /// [`UserDefined::modifiers`](Self::UserDefined) — SQLite's grammar also admits a
544 /// signed or fractional argument, but the corpus surface is unsigned and the
545 /// signed/float forms are left to a follow-up (their absence under-accepts, never
546 /// over-accepts).
547 args: ThinVec<u32>,
548 /// Source location and node identity.
549 meta: Meta,
550 },
551 /// A host-owned type node an out-of-tree dialect parses through the parser crate's
552 /// `Dialect::parse_data_type_hook` (ADR-0009). The sixth `Other(X)` seam:
553 /// `X = NoExt` for every shipped builtin leaves this arm uninhabited and statically
554 /// dead, so stock parsing never produces it and `DataType<NoExt>` keeps its
555 /// byte-identical size.
556 Other {
557 /// The dialect extension node value.
558 ext: X,
559 /// Source location and node identity.
560 meta: Meta,
561 },
562}
563
564/// One named field of an anonymous composite type: `name TYPE`, as in a DuckDB
565/// `STRUCT(a INTEGER)` field or a `UNION(tag T)` member.
566///
567/// The field name is a genuine identifier position (bare `a`, or double-quoted
568/// `"key"`), so it is an [`Ident`] that round-trips its quote style, not a bare
569/// symbol. Mirrors the Expr-side `StructField`: a spanned sub-node with its own
570/// [`Meta`].
571#[derive(Clone, Debug, PartialEq, Eq, Hash)]
572#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
573#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
574pub struct StructTypeField<X: Extension = NoExt> {
575 /// Name referenced by this syntax.
576 pub name: Ident,
577 /// Data type named by this syntax.
578 pub ty: DataType<X>,
579 /// Source location and node identity.
580 pub meta: Meta,
581}
582
583/// How an anonymous composite type ([`DataType::Struct`]) was spelled.
584///
585/// DuckDB folds `STRUCT(...)` and the standard `ROW(...)` to the same canonical
586/// composite type; this tag records the written keyword so each round-trips,
587/// mirroring [`CastSyntax`](crate::ast::CastSyntax).
588#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
589#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
590#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
591pub enum StructTypeSpelling {
592 /// The `STRUCT(...)` keyword.
593 Struct,
594 /// The standard `ROW(...)` keyword.
595 Row,
596 /// BigQuery angle-bracket type form `STRUCT<field TYPE, …>` (type position).
597 AngleBracket,
598}
599
600/// The written surface of an array-type suffix ([`DataType::Array`]).
601///
602/// Bracket `T[]`/`T[n]` and keyword `T ARRAY`/`T ARRAY[n]` name the same canonical
603/// array-type shape; the tag round-trips the written form.
604#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
605#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
606#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
607pub enum ArrayTypeSpelling {
608 /// The bracket suffix `T[]` / `T[n]`.
609 Bracket,
610 /// The `ARRAY` keyword suffix `T ARRAY` / `T ARRAY[n]`.
611 Keyword,
612 /// BigQuery angle-bracket type form `ARRAY<T>` (type position, prefix).
613 AngleBracket,
614}
615
616/// The parametric wrapper keyword of a [`DataType::Wrapped`] type combinator.
617///
618/// Each variant is a distinct ClickHouse type combinator that shares the
619/// single-inner-type wrapper shape; recognition of each is gated on its own
620/// [`TypeNameSyntax`](crate::dialect::TypeNameSyntax) flag (one behaviour = one flag),
621/// so a preset opts into each keyword independently rather than through one bundled
622/// gate. The canonical render emits ClickHouse's mixed-case spelling (`Nullable`).
623#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
624#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
625#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
626pub enum WrappedTypeKind {
627 /// `Nullable(T)` — the inner type extended with a `NULL` value.
628 Nullable,
629 /// `LowCardinality(T)` — a dictionary-encoding wrapper over the inner type,
630 /// transparent to query semantics (ClickHouse constrains which `T` at type
631 /// resolution; the grammar accepts any single inner type).
632 LowCardinality,
633}
634
635#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
636#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
637#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
638/// The SQL boolean type name forms represented by the AST.
639pub enum BooleanTypeName {
640 /// The `BOOLEAN` spelling.
641 Boolean,
642 /// The `BOOL` spelling.
643 Bool,
644}
645
646#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
647#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
648#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
649/// The SQL integer type name forms represented by the AST.
650pub enum IntegerTypeName {
651 /// The `INT` spelling.
652 Int,
653 /// The `INTEGER` spelling.
654 Integer,
655}
656
657/// The bit width spelled in a ClickHouse [`DataType::FixedWidthInt`] type name — the `N`
658/// of `Int<N>`/`UInt<N>`. ClickHouse defines exactly these six widths; the width is a
659/// closed axis (a precise enum, not a raw bit count) so only the real spellings are
660/// representable.
661#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
662#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
663#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
664pub enum IntWidth {
665 /// An 8-bit integer width.
666 W8,
667 /// A 16-bit integer width.
668 W16,
669 /// A 32-bit integer width.
670 W32,
671 /// A 64-bit integer width.
672 W64,
673 /// A 128-bit integer width.
674 W128,
675 /// A 256-bit integer width.
676 W256,
677}
678
679#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
680#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
681#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
682/// The SQL decimal type name forms represented by the AST.
683pub enum DecimalTypeName {
684 /// The `DECIMAL` spelling.
685 Decimal,
686 /// The `DEC` spelling.
687 Dec,
688 /// The `NUMERIC` spelling.
689 Numeric,
690}
691
692#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
693#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
694#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
695/// The SQL double type name forms represented by the AST.
696pub enum DoubleTypeName {
697 /// The standard `DOUBLE PRECISION` spelling.
698 DoublePrecision,
699 /// MySQL's bare `DOUBLE` floating-point type (PostgreSQL leaves bare `double`
700 /// unreserved, so it is recognized only under
701 /// [`TypeNameSyntax::extended_scalar_type_names`](crate::dialect::TypeNameSyntax)).
702 Double,
703}
704
705/// The MySQL character-LOB size family spelled by [`DataType::Text`]. They differ
706/// only in declared maximum length, so they share one variant tagged by spelling.
707#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
708#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
709#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
710pub enum TextTypeName {
711 /// The bare `TEXT` type (PostgreSQL; MySQL's mid-size member).
712 Text,
713 /// MySQL `TINYTEXT`.
714 TinyText,
715 /// MySQL `MEDIUMTEXT`.
716 MediumText,
717 /// MySQL `LONGTEXT`.
718 LongText,
719}
720
721/// The MySQL binary-LOB size family spelled by [`DataType::Blob`].
722#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
723#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
724#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
725pub enum BlobTypeName {
726 /// MySQL `BLOB`.
727 Blob,
728 /// MySQL `TINYBLOB`.
729 TinyBlob,
730 /// MySQL `MEDIUMBLOB`.
731 MediumBlob,
732 /// MySQL `LONGBLOB`.
733 LongBlob,
734}
735
736/// MySQL's numeric sign modifier, carried by [`DataType::NumericModifier`].
737///
738/// `ZEROFILL` (a separate flag on the wrapper) implies unsigned semantics in
739/// MySQL, but the written sign is preserved here so the surface round-trips: a
740/// bare `INT ZEROFILL` keeps [`Unspecified`](Self::Unspecified), not a synthesized
741/// `UNSIGNED`.
742#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
743#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
744#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
745pub enum Signedness {
746 /// No `SIGNED`/`UNSIGNED` keyword was written.
747 Unspecified,
748 /// `SIGNED`.
749 Signed,
750 /// `UNSIGNED`.
751 Unsigned,
752}
753
754#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
755#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
756#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
757/// The SQL character type name forms represented by the AST.
758pub enum CharacterTypeName {
759 /// The fixed-length `CHAR` spelling.
760 Char,
761 /// The fixed-length `CHARACTER` spelling.
762 Character,
763 /// The variable-length `CHAR VARYING` spelling.
764 CharVarying,
765 /// The variable-length `CHARACTER VARYING` spelling.
766 CharacterVarying,
767 /// The variable-length `VARCHAR` spelling.
768 Varchar,
769 /// `NCHAR` (national character).
770 Nchar,
771 /// `NCHAR VARYING`.
772 NcharVarying,
773 /// `NATIONAL CHAR`.
774 NationalChar,
775 /// `NATIONAL CHAR VARYING`.
776 NationalCharVarying,
777 /// `NATIONAL CHARACTER`.
778 NationalCharacter,
779 /// `NATIONAL CHARACTER VARYING`.
780 NationalCharacterVarying,
781}
782
783/// A MySQL character-set type annotation carried by the string-typed [`DataType`]
784/// variants — [`Character`](DataType::Character), the [`Text`](DataType::Text) LOB family,
785/// [`Enum`](DataType::Enum), and [`Set`](DataType::Set) (each engine-measured to admit it
786/// on mysql:8.4) — the grammar's `opt_charset_with_opt_binary` production, part of the
787/// *type* rather than a column attribute (see the field docs on
788/// [`DataType::Character`]).
789///
790/// The canonical render emits the charset selector first, then `BINARY`
791/// (`CHAR CHARACTER SET utf8mb4 BINARY`); MySQL's reversed spellings
792/// (`CHAR BINARY CHARACTER SET utf8mb4`, `CHAR BINARY ASCII`) fold onto this one shape,
793/// their exact written order recovered from the node span (an ADR-0011 spelling trade,
794/// mirroring the plural-interval and `SIGNED INTEGER` folds). The `CHARSET` synonym
795/// likewise folds to the canonical `CHARACTER SET`.
796#[derive(Clone, Debug, PartialEq, Eq, Hash)]
797#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
798#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
799pub struct CharsetAnnotation {
800 /// The character-set selector, or `None` when only the `BINARY` collation modifier was
801 /// written (`CHAR BINARY`, with no charset). At least one of `charset` / `binary` is
802 /// always present — an empty annotation is `None` on the [`DataType::Character`] node.
803 pub charset: Option<Charset>,
804 /// The charset name for [`Charset::Named`] (`CHARACTER SET <name>` / `CHARSET <name>`);
805 /// `None` for the keyword shortcuts ([`Charset::Ascii`]/[`Charset::Unicode`]/
806 /// [`Charset::Byte`]) and the bare-`BINARY` form. Held on this meta-bearing struct
807 /// rather than inside the [`Charset`] enum so the tag stays a plain `Copy` kind. The
808 /// name is a MySQL `ident_or_text`: a bare or backtick-quoted identifier, or a quoted
809 /// string (`CHARACTER SET 'utf8mb4'`), whose spelling round-trips from the [`Ident`]'s
810 /// quote style.
811 pub name: Option<Ident>,
812 /// Whether the `BINARY` binary-collation modifier accompanies the annotation
813 /// (`CHAR BINARY`, `CHAR CHARACTER SET x BINARY`, `CHAR ASCII BINARY`).
814 pub binary: bool,
815 /// Source location and node identity.
816 pub meta: Meta,
817}
818
819/// The character-set selector kind of a [`CharsetAnnotation`].
820#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
821#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
822#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
823pub enum Charset {
824 /// `CHARACTER SET <name>` or the `CHARSET <name>` synonym (folded to the canonical
825 /// `CHARACTER SET`). The name is carried by [`CharsetAnnotation::name`].
826 Named,
827 /// `ASCII` — MySQL shorthand for `CHARACTER SET latin1`.
828 Ascii,
829 /// `UNICODE` — MySQL shorthand for `CHARACTER SET ucs2`.
830 Unicode,
831 /// `BYTE` — MySQL/Oracle-compatibility shorthand producing a binary `CHAR`.
832 Byte,
833}
834
835#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
836#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
837#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
838/// The SQL binary type name forms represented by the AST.
839pub enum BinaryTypeName {
840 /// The fixed-length `BINARY` spelling.
841 Binary,
842 /// The variable-length `BINARY VARYING` spelling.
843 BinaryVarying,
844 /// The variable-length `VARBINARY` spelling.
845 Varbinary,
846 /// PostgreSQL's `BYTEA` spelling.
847 Bytea,
848}
849
850#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
851#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
852#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
853/// The SQL time type name forms represented by the AST.
854pub enum TimeTypeName {
855 /// The `TIME` spelling.
856 Time,
857 /// PostgreSQL's `TIMETZ` (`TIME WITH TIME ZONE`) spelling.
858 Timetz,
859}
860
861#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
862#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
863#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
864/// The SQL timestamp type name forms represented by the AST.
865pub enum TimestampTypeName {
866 /// The `TIMESTAMP` spelling.
867 Timestamp,
868 /// PostgreSQL's `TIMESTAMPTZ` (`TIMESTAMP WITH TIME ZONE`) spelling.
869 Timestamptz,
870 /// MySQL `DATETIME`: a timestamp-shaped type without time-zone semantics,
871 /// recognized only under
872 /// [`TypeNameSyntax::extended_scalar_type_names`](crate::dialect::TypeNameSyntax). It
873 /// reuses the timestamp variant (one canonical shape) and pins
874 /// [`TimeZone::Unspecified`](TimeZone), since `DATETIME` takes no zone suffix.
875 Datetime,
876}
877
878#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
879#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
880#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
881/// The SQL time zone forms represented by the AST.
882pub enum TimeZone {
883 /// No time-zone qualifier was written.
884 Unspecified,
885 /// `WITH TIME ZONE` — a time-zone-aware type.
886 WithTimeZone,
887 /// `WITHOUT TIME ZONE` — a time-zone-naive type.
888 WithoutTimeZone,
889}
890
891#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
892#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
893#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
894/// The SQL interval fields forms represented by the AST.
895pub enum IntervalFields {
896 /// `YEAR` — a year-precision interval qualifier.
897 Year,
898 /// `MONTH` — a month-precision interval qualifier.
899 Month,
900 /// `DAY` — a day-precision interval qualifier.
901 Day,
902 /// `HOUR` — an hour-precision interval qualifier.
903 Hour,
904 /// `MINUTE` — a minute-precision interval qualifier.
905 Minute,
906 /// `SECOND` — a second-precision interval qualifier.
907 Second,
908 /// `YEAR TO MONTH` — a year-and-month interval qualifier.
909 YearToMonth,
910 /// `DAY TO HOUR` — a day-to-hour interval qualifier.
911 DayToHour,
912 /// `DAY TO MINUTE` — a day-to-minute interval qualifier.
913 DayToMinute,
914 /// `DAY TO SECOND` — a day-to-second interval qualifier.
915 DayToSecond,
916 /// `HOUR TO MINUTE` — an hour-to-minute interval qualifier.
917 HourToMinute,
918 /// `HOUR TO SECOND` — an hour-to-second interval qualifier.
919 HourToSecond,
920 /// `MINUTE TO SECOND` — a minute-to-second interval qualifier.
921 MinuteToSecond,
922 // DuckDB-only extended units, admitted as `INTERVAL <amount> <unit>` multipliers
923 // (both singular and plural spellings) solely under
924 // [`ExpressionSyntax::relaxed_interval_syntax`](crate::dialect::ExpressionSyntax).
925 // ANSI/PostgreSQL never produce these — their interval grammar has no such
926 // qualifier — so the shared `DataType::Interval` render/accept path is unchanged.
927 // Each is a whole, simple qualifier: DuckDB has no `TO` composite for them and
928 // rejects a trailing precision, so (like the plural forms) they carry no precision.
929 /// `WEEK` — a DuckDB week interval qualifier (non-standard).
930 Week,
931 /// `QUARTER` — a DuckDB quarter interval qualifier (non-standard).
932 Quarter,
933 /// `DECADE` — a DuckDB decade interval qualifier (non-standard).
934 Decade,
935 /// `CENTURY` — a DuckDB century interval qualifier (non-standard).
936 Century,
937 /// `MILLENNIUM` — a DuckDB millennium interval qualifier (non-standard).
938 Millennium,
939 /// `MILLISECOND` — a DuckDB millisecond interval qualifier (non-standard).
940 Millisecond,
941 /// `MICROSECOND` — a DuckDB microsecond interval qualifier (non-standard).
942 Microsecond,
943 // MySQL-only microsecond composite units, admitted solely in the MySQL `interval`
944 // vocabulary (the `EVERY <expr> <unit>` event schedule and `INTERVAL <expr> <unit>`
945 // arithmetic). MySQL spells them with an underscore (`DAY_MICROSECOND`), not the ANSI
946 // `TO` composite; the standard `DataType::Interval` render never produces them (no
947 // dialect's INTERVAL type grammar admits a microsecond composite), so they extend the
948 // shared vocabulary without touching the ANSI interval type. The other MySQL composites
949 // (`DAY_HOUR`, `MINUTE_SECOND`, `YEAR_MONTH`, …) reuse the existing `*To*` variants.
950 /// `DAY_MICROSECOND` — a MySQL day-to-microsecond composite interval unit.
951 DayToMicrosecond,
952 /// `HOUR_MICROSECOND` — a MySQL hour-to-microsecond composite interval unit.
953 HourToMicrosecond,
954 /// `MINUTE_MICROSECOND` — a MySQL minute-to-microsecond composite interval unit.
955 MinuteToMicrosecond,
956 /// `SECOND_MICROSECOND` — a MySQL second-to-microsecond composite interval unit.
957 SecondToMicrosecond,
958}