Skip to content

Commit 0cd1040

Browse files
authored
core: one type row per expression, canonicalized per dialect, across every engine (#4618)
1 parent 0bb421e commit 0cd1040

107 files changed

Lines changed: 9459 additions & 1185 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

docs/howto/analyze.md

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ reports the result columns and parameters:
7171
{
7272
"name": "id",
7373
"type": {
74-
"name": "bigserial"
74+
"name": "bigint"
7575
},
7676
"table": "authors"
7777
},
@@ -97,7 +97,7 @@ reports the result columns and parameters:
9797
"column": {
9898
"name": "id",
9999
"type": {
100-
"name": "bigserial"
100+
"name": "bigint"
101101
},
102102
"table": "authors"
103103
}
@@ -109,10 +109,20 @@ reports the result columns and parameters:
109109

110110
A column's `type` is written as a call expression: a `name` applied to
111111
`args`, each of which carries an optional `label` and exactly one of `type`,
112-
`int`, `bool` or `string`, with `nullable` set at whatever depth it applies.
113-
An array of text is `array` applied to `text`; a `Map(String, Nullable(UInt8))`
114-
in ClickHouse is `map` applied to `string` and a nullable `uint8`. Names are
115-
recorded as the engine reports them.
112+
`int`, `bool`, `string` or `ident`, with `nullable` set at whatever depth it
113+
applies. A `numeric(10,2)` column is `numeric` applied to `10` and `2`; an
114+
array of text is `array` applied to `text`, and an array of arrays nests
115+
one `array` per dimension; a `Map(String, Nullable(UInt8))` in ClickHouse is
116+
`map` applied to `string` and a nullable `uint8`; a `STRUCT<a INT64>` in
117+
GoogleSQL is `struct` applied to an `int64` labelled `a`; the `MAX` of SQL
118+
Server's `nvarchar(max)` is the identifier `max`.
119+
120+
Types are reported the way the engine itself stores and reports them rather
121+
than the way the schema spelled them: PostgreSQL's `int` and `bigserial` are
122+
`integer` and `bigint`, as `format_type` prints them; MySQL's `BOOLEAN` is
123+
`tinyint(1)`; ClickHouse's `Decimal32(4)` is `decimal(9, 4)`; DuckDB's
124+
`TEXT` is `varchar`; SQL Server's `FLOAT(24)` is `real`. SQLite, which
125+
keeps a declared type as written, is reported as written.
116126

117127
Pass `--ast` to also include each statement's parsed AST under an `ast` key. It
118128
has the same shape as the output of [`parse`](parse.md), with every node tagged

internal/codegen/golang/postgresql_type.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ func postgresType(req *plugin.GenerateRequest, options *opts.Options, col *plugi
216216
}
217217
return "sql.NullTime"
218218

219-
case "pg_catalog.time":
219+
case "pg_catalog.time", "time", "time without time zone":
220220
if driver == opts.SQLDriverPGXV5 {
221221
return "pgtype.Time"
222222
}
@@ -228,7 +228,7 @@ func postgresType(req *plugin.GenerateRequest, options *opts.Options, col *plugi
228228
}
229229
return "sql.NullTime"
230230

231-
case "pg_catalog.timetz":
231+
case "pg_catalog.timetz", "timetz", "time with time zone":
232232
if notNull {
233233
return "time.Time"
234234
}
@@ -237,7 +237,7 @@ func postgresType(req *plugin.GenerateRequest, options *opts.Options, col *plugi
237237
}
238238
return "sql.NullTime"
239239

240-
case "pg_catalog.timestamp", "timestamp":
240+
case "pg_catalog.timestamp", "timestamp", "timestamp without time zone":
241241
if driver == opts.SQLDriverPGXV5 {
242242
return "pgtype.Timestamp"
243243
}
@@ -249,7 +249,7 @@ func postgresType(req *plugin.GenerateRequest, options *opts.Options, col *plugi
249249
}
250250
return "sql.NullTime"
251251

252-
case "pg_catalog.timestamptz", "timestamptz":
252+
case "pg_catalog.timestamptz", "timestamptz", "timestamp with time zone":
253253
if driver == opts.SQLDriverPGXV5 {
254254
return "pgtype.Timestamptz"
255255
}
@@ -261,7 +261,7 @@ func postgresType(req *plugin.GenerateRequest, options *opts.Options, col *plugi
261261
}
262262
return "sql.NullTime"
263263

264-
case "text", "pg_catalog.varchar", "pg_catalog.bpchar", "string", "citext", "name":
264+
case "text", "pg_catalog.varchar", "varchar", "character varying", "pg_catalog.bpchar", "bpchar", "character", "string", "citext", "name":
265265
if notNull {
266266
return "string"
267267
}
@@ -470,7 +470,7 @@ func postgresType(req *plugin.GenerateRequest, options *opts.Options, col *plugi
470470
}
471471
return "any"
472472

473-
case "bit", "varbit", "pg_catalog.bit", "pg_catalog.varbit":
473+
case "bit", "varbit", "bit varying", "pg_catalog.bit", "pg_catalog.varbit":
474474
if driver == opts.SQLDriverPGXV5 {
475475
return "pgtype.Bits"
476476
}

internal/compiler/catalog_core.go

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -32,19 +32,25 @@ func coreResultCatalog(c *core.Catalog) (*catalog.Catalog, error) {
3232
}
3333
t := &catalog.Table{Rel: &ast.TableName{Schema: ns.Name, Name: table.Name}}
3434
for _, col := range cols {
35-
// The catalog names an array type after its element with the
36-
// suffix appended, which is codegen's data type and array
37-
// flag in one string. The core catalog holds one dimension,
38-
// and codegen renders a "[]" per dimension.
39-
dataType, isArray := strings.CutSuffix(col.TypeName, core.ArraySuffix)
35+
// Codegen reads a data type and an array flag, and renders
36+
// a "[]" per dimension, so an array of arrays of integers
37+
// is the type integer with two dimensions.
38+
expr, err := c.TypeExprOf(col.TypeOID)
39+
if err != nil {
40+
return nil, err
41+
}
42+
inner := expr.Innermost()
4043
column := &catalog.Column{
41-
Name: col.Name,
42-
Type: ast.TypeName{Name: dataType},
43-
IsNotNull: col.NotNull,
44-
IsArray: isArray,
44+
Name: col.Name,
45+
Type: ast.TypeName{Name: strings.TrimSuffix(inner.Name, " unsigned")},
46+
IsNotNull: col.NotNull,
47+
IsArray: expr.IsArray(),
48+
ArrayDims: expr.ArrayDims(),
49+
IsUnsigned: strings.HasSuffix(inner.Name, " unsigned"),
4550
}
46-
if isArray {
47-
column.ArrayDims = 1
51+
if len(inner.Args) > 0 && inner.Args[0].Int != nil {
52+
l := int(*inner.Args[0].Int)
53+
column.Length = &l
4854
}
4955
t.Columns = append(t.Columns, column)
5056
}

internal/compiler/parse_core.go

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -107,21 +107,33 @@ func coreColumn(c core.Column) *Column {
107107
IsArray: c.IsArray,
108108
TypeExpr: c.Type,
109109
}
110-
// The core reports arrays without dimensions, and codegen renders one
111-
// "[]" per dimension.
112-
if c.IsArray {
113-
col.ArrayDims = 1
114-
}
110+
describeType(col, c.Type)
115111
if c.Source != nil && c.Source.Table != "" {
116112
col.Table = &ast.TableName{Schema: c.Source.Schema, Name: c.Source.Table}
117113
col.TableAlias = c.Source.TableAlias
118114
col.OriginalName = c.Source.Column
119115
}
120-
if c.TypeLength > 0 {
121-
l := c.TypeLength
116+
return col
117+
}
118+
119+
// describeType fills in what codegen reads about a type from its
120+
// expression: one array dimension per nesting, the length that is the
121+
// innermost type's first integer argument (which is how a MySQL tinyint(1)
122+
// is told from a tinyint), and whether the innermost type is unsigned.
123+
func describeType(col *Column, t *core.TypeExpr) {
124+
if t == nil {
125+
if col.IsArray {
126+
col.ArrayDims = 1
127+
}
128+
return
129+
}
130+
col.ArrayDims = t.ArrayDims()
131+
inner := t.Innermost()
132+
if len(inner.Args) > 0 && inner.Args[0].Int != nil {
133+
l := int(*inner.Args[0].Int)
122134
col.Length = &l
123135
}
124-
return col
136+
col.Unsigned = strings.HasSuffix(inner.Name, " unsigned")
125137
}
126138

127139
func coreParamColumn(p core.Parameter, params *named.ParamSet) *Column {
@@ -132,9 +144,7 @@ func coreParamColumn(p core.Parameter, params *named.ParamSet) *Column {
132144
IsArray: p.IsArray,
133145
TypeExpr: p.Type,
134146
}
135-
if p.IsArray {
136-
col.ArrayDims = 1
137-
}
147+
describeType(col, p.Type)
138148
if p.Source != nil && p.Source.Table != "" {
139149
col.Table = &ast.TableName{Schema: p.Source.Schema, Name: p.Source.Table}
140150
col.OriginalName = p.Source.Column

internal/core/analysis.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,6 @@ type Column struct {
6565
SourceAttributeOID int64 `json:"source_attribute_oid,omitempty"`
6666
Source *ColumnSource `json:"source,omitempty"`
6767
DeclType string `json:"decl_type,omitempty"`
68-
TypeLength int `json:"type_length,omitempty"`
69-
TypeScale int `json:"type_scale,omitempty"`
7068
IsPrimaryKey bool `json:"is_primary_key,omitempty"`
7169
IsUnique bool `json:"is_unique,omitempty"`
7270
IsAutoIncrement bool `json:"is_auto_increment,omitempty"`

internal/core/analyzer/analyzer.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ func derivedRel(alias string, cols []core.Column) scopeRel {
112112
AttOID: col.SourceAttributeOID,
113113
Name: col.Name,
114114
TypeOID: col.TypeOID,
115+
Type: col.Type.WithNullable(false),
115116
NotNull: col.NotNull,
116117
})
117118
}
@@ -123,12 +124,12 @@ func (a *analyzer) result() core.PrepareResult {
123124
// the dialect has such a type.
124125
if oid, ok := a.cat.UntypedTypeOID(); ok {
125126
for n, p := range a.params {
126-
if p.TypeOID == 0 && p.DataType == "" {
127+
if p.TypeOID == 0 && p.Type == nil {
127128
t := exprType{typeOID: oid, nullable: true}
128129
p.TypeOID = oid
129130
p.DataType, p.IsArray = a.typeNameOf(t)
130131
p.NotNull = false
131-
p.Type = a.typeExprOf(t, "")
132+
p.Type = a.typeExprOf(t)
132133
a.params[n] = p
133134
}
134135
}

internal/core/analyzer/dml.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,7 @@ func findColumn(rel scopeRel, name string) (core.ClassColumn, bool) {
202202
func columnType(rel scopeRel, col core.ClassColumn) exprType {
203203
return exprType{
204204
typeOID: col.TypeOID,
205+
expr: col.Type,
205206
nullable: !col.NotNull,
206207
sourceClassOID: rel.classOID,
207208
sourceAttributeOID: col.AttOID,

0 commit comments

Comments
 (0)