Skip to content

Commit d227db3

Browse files
committed
core: carry a MySQL column's UNSIGNED through the analysis core
MySQL declares UNSIGNED on the column rather than in a type of its own, and the core catalog kept no record of it, so under SQLCEXPERIMENT=coreanalyzer a BIGINT UNSIGNED column and every placeholder compared with one came out as int64 instead of uint64. sql_attribute now records the flag, and it travels the way the column's other declaration details do: a result column that comes straight from the table carries it, as does a placeholder that stands in for such a column, and the catalog dump hands it to codegen for the table models. An ALTER COLUMN that changes the type sets it afresh. In the core replay context 11 more cases pass, with no case regressing. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XZcKb4GFiZQbeo9oVmyF3m
1 parent 2c11559 commit d227db3

11 files changed

Lines changed: 56 additions & 18 deletions

File tree

internal/compiler/catalog_core.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,11 @@ func coreResultCatalog(c *core.Catalog) (*catalog.Catalog, error) {
4040
// and codegen renders a "[]" per dimension.
4141
dataType, isArray := strings.CutSuffix(col.TypeName, core.ArraySuffix)
4242
column := &catalog.Column{
43-
Name: col.Name,
44-
Type: ast.TypeName{Name: dataType},
45-
IsNotNull: col.NotNull,
46-
IsArray: isArray,
43+
Name: col.Name,
44+
Type: ast.TypeName{Name: dataType},
45+
IsNotNull: col.NotNull,
46+
IsUnsigned: col.Unsigned,
47+
IsArray: isArray,
4748
}
4849
if isArray {
4950
column.ArrayDims = 1

internal/compiler/parse_core.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ func coreColumn(c core.Column) *Column {
106106
NotNull: c.NotNull,
107107
IsArray: c.IsArray,
108108
TypeExpr: c.Type,
109+
Unsigned: c.Unsigned,
109110
}
110111
// The core reports arrays without dimensions, and codegen renders one
111112
// "[]" per dimension.
@@ -131,6 +132,7 @@ func coreParamColumn(p core.Parameter, params *named.ParamSet) *Column {
131132
NotNull: p.NotNull,
132133
IsArray: p.IsArray,
133134
TypeExpr: p.Type,
135+
Unsigned: p.Unsigned,
134136
}
135137
if p.IsArray {
136138
col.ArrayDims = 1

internal/core/analysis.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@ type Column struct {
7070
IsPrimaryKey bool `json:"is_primary_key,omitempty"`
7171
IsUnique bool `json:"is_unique,omitempty"`
7272
IsAutoIncrement bool `json:"is_auto_increment,omitempty"`
73+
// Unsigned is the UNSIGNED the column was declared with, when the
74+
// column comes straight from a MySQL table.
75+
Unsigned bool `json:"unsigned,omitempty"`
7376
}
7477

7578
type Parameter struct {
@@ -81,4 +84,7 @@ type Parameter struct {
8184
NotNull bool `json:"not_null"`
8285
IsArray bool `json:"is_array,omitempty"`
8386
Source *ColumnSource `json:"source,omitempty"`
87+
// Unsigned is the UNSIGNED of the MySQL column the placeholder stands
88+
// in for, when it stands in for one.
89+
Unsigned bool `json:"unsigned,omitempty"`
8490
}

internal/core/analyzer/expr.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,7 @@ func (a *analyzer) inferParam(number int, t exprType) {
223223
TableAlias: t.sourceTableAlias,
224224
Column: ad.Column,
225225
}
226+
cur.Unsigned = ad.Unsigned
226227
if typed {
227228
cur.Type = a.typeExprOf(t, ad.DeclType)
228229
}

internal/core/analyzer/projection.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ func (a *analyzer) decorateSource(col *core.Column, attOID int64, tableAlias str
126126
col.IsPrimaryKey = ad.IsPrimaryKey
127127
col.IsUnique = ad.IsUnique
128128
col.IsAutoIncrement = ad.AutoIncrement
129+
col.Unsigned = ad.Unsigned
129130
}
130131

131132
// targetName picks the output name for a target. fields is the already

internal/core/attribute.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ type AttributeSpec struct {
2424
// the relation's model, like the column an sqlite fts5 table names
2525
// after itself.
2626
Hidden bool
27+
// Unsigned is the UNSIGNED a MySQL column is declared with, which the
28+
// dialect puts on the column rather than in a type of its own.
29+
Unsigned bool
2730
}
2831

2932
func (c *Catalog) CreateAttributeSpec(s AttributeSpec) error {
@@ -41,6 +44,7 @@ func (c *Catalog) CreateAttributeSpec(s AttributeSpec) error {
4144
IsPrimaryKey: boolToInt64(s.IsPrimaryKey),
4245
IsUnique: boolToInt64(s.IsUnique),
4346
Hidden: boolToInt64(s.Hidden),
47+
Unsigned: boolToInt64(s.Unsigned),
4448
})
4549
if err != nil {
4650
return fmt.Errorf("create attribute %q on class %d: %w", s.Name, s.ClassOID, err)
@@ -85,12 +89,13 @@ func (c *Catalog) RenameAttribute(classOID int64, name, newName string) error {
8589
}
8690

8791
// SetAttributeType changes a column's type.
88-
func (c *Catalog) SetAttributeType(classOID int64, name string, typeOID int64, declType string) error {
92+
func (c *Catalog) SetAttributeType(classOID int64, name string, typeOID int64, declType string, unsigned bool) error {
8993
err := c.q.SetAttributeType(context.Background(), catalogdb.SetAttributeTypeParams{
9094
ClassOid: classOID,
9195
Name: name,
9296
TypeOid: typeOID,
9397
DeclType: declType,
98+
Unsigned: boolToInt64(unsigned),
9499
})
95100
if err != nil {
96101
return fmt.Errorf("set type of attribute %q on class %d: %w", name, classOID, err)
@@ -244,6 +249,7 @@ type CodegenColumn struct {
244249
Name string
245250
TypeName string
246251
NotNull bool
252+
Unsigned bool
247253
}
248254

249255
func (c *Catalog) ClassCodegenColumns(classOID int64) ([]CodegenColumn, error) {
@@ -257,6 +263,7 @@ func (c *Catalog) ClassCodegenColumns(classOID int64) ([]CodegenColumn, error) {
257263
Name: r.ColumnName,
258264
TypeName: r.TypeName,
259265
NotNull: r.NotNull != 0,
266+
Unsigned: r.Unsigned != 0,
260267
})
261268
}
262269
return out, nil
@@ -274,6 +281,7 @@ type AttributeDetails struct {
274281
IsPrimaryKey bool
275282
IsUnique bool
276283
NotNull bool
284+
Unsigned bool
277285
}
278286

279287
func (c *Catalog) LookupAttribute(attOID int64) (AttributeDetails, error) {
@@ -293,5 +301,6 @@ func (c *Catalog) LookupAttribute(attOID int64) (AttributeDetails, error) {
293301
IsPrimaryKey: r.IsPrimaryKey != 0,
294302
IsUnique: r.IsUnique != 0,
295303
NotNull: r.NotNull != 0,
304+
Unsigned: r.Unsigned != 0,
296305
}, nil
297306
}

internal/core/catalogdb/models.go

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/core/catalogdb/query.sql.go

Lines changed: 19 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/core/catalogdef/query.sql

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -115,8 +115,8 @@ UPDATE sql_class SET name = sqlc.arg(new_name) WHERE oid = sqlc.arg(oid);
115115
INSERT INTO sql_attribute (
116116
class_oid, name, type_oid, not_null, has_default, num,
117117
decl_type, type_length, type_scale,
118-
auto_increment, is_primary_key, is_unique, hidden
119-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);
118+
auto_increment, is_primary_key, is_unique, hidden, unsigned
119+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);
120120

121121
-- name: SetAttributePrimaryKey :exec
122122
UPDATE sql_attribute SET is_primary_key = 1, not_null = 1
@@ -137,7 +137,7 @@ UPDATE sql_attribute SET name = sqlc.arg(new_name)
137137
WHERE class_oid = sqlc.arg(class_oid) AND name = sqlc.arg(name);
138138

139139
-- name: SetAttributeType :exec
140-
UPDATE sql_attribute SET type_oid = sqlc.arg(type_oid), decl_type = sqlc.arg(decl_type)
140+
UPDATE sql_attribute SET type_oid = sqlc.arg(type_oid), decl_type = sqlc.arg(decl_type), unsigned = sqlc.arg(unsigned)
141141
WHERE class_oid = sqlc.arg(class_oid) AND name = sqlc.arg(name);
142142

143143
-- name: SetAttributeNotNull :exec
@@ -173,7 +173,7 @@ WHERE class_oid = ?
173173
ORDER BY num;
174174

175175
-- name: ListClassColumns :many
176-
SELECT a.name AS column_name, t.name AS type_name, a.not_null
176+
SELECT a.name AS column_name, t.name AS type_name, a.not_null, a.unsigned
177177
FROM sql_attribute a
178178
JOIN sql_type t ON t.oid = a.type_oid
179179
WHERE a.class_oid = ? AND a.hidden = 0
@@ -182,7 +182,7 @@ ORDER BY a.num;
182182
-- name: LookupAttribute :one
183183
SELECT ns.name AS schema_name, cls.name AS table_name, a.name AS column_name, a.num,
184184
a.decl_type, a.type_length, a.type_scale,
185-
a.auto_increment, a.is_primary_key, a.is_unique, a.not_null
185+
a.auto_increment, a.is_primary_key, a.is_unique, a.not_null, a.unsigned
186186
FROM sql_attribute a
187187
JOIN sql_class cls ON cls.oid = a.class_oid
188188
JOIN sql_namespace ns ON ns.oid = cls.namespace_oid

internal/core/catalogdef/schema.sql

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,8 @@ CREATE TABLE sql_class (
7878
-- hidden: resolvable by name but absent from a star expansion and
7979
-- from the relation's model, like the column an sqlite
8080
-- fts5 table names after itself.
81+
-- unsigned: the column was declared UNSIGNED, which MySQL puts on
82+
-- the column rather than in a type of its own.
8183
CREATE TABLE sql_attribute (
8284
oid INTEGER PRIMARY KEY AUTOINCREMENT,
8385
class_oid INTEGER NOT NULL REFERENCES sql_class(oid),
@@ -93,6 +95,7 @@ CREATE TABLE sql_attribute (
9395
is_primary_key INTEGER NOT NULL DEFAULT 0,
9496
is_unique INTEGER NOT NULL DEFAULT 0,
9597
hidden INTEGER NOT NULL DEFAULT 0,
98+
unsigned INTEGER NOT NULL DEFAULT 0,
9699
UNIQUE(class_oid, name),
97100
UNIQUE(class_oid, num)
98101
);

0 commit comments

Comments
 (0)