Skip to content

Commit 47ac3a6

Browse files
authored
Merge pull request #2254 from dolthub/daylon/issue-2197-part-2
Issue #2197 Part 2
2 parents d0a9247 + 284ca20 commit 47ac3a6

16 files changed

Lines changed: 778 additions & 34 deletions

File tree

go.mod

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
module github.com/dolthub/doltgresql
22

3-
go 1.25.3
3+
go 1.25.6
44

55
require (
66
github.com/PuerkitoBio/goquery v1.8.1
77
github.com/cockroachdb/apd/v2 v2.0.3-0.20200518165714-d020e156310a
88
github.com/cockroachdb/errors v1.7.5
9-
github.com/dolthub/dolt/go v0.40.5-0.20260122084121-6b5d5373d1ec
9+
github.com/dolthub/dolt/go v0.40.5-0.20260205001014-db7263eb669c
1010
github.com/dolthub/eventsapi_schema v0.0.0-20250915094920-eadfd39051ca
1111
github.com/dolthub/flatbuffers/v23 v23.3.3-dh.2
12-
github.com/dolthub/go-mysql-server v0.20.1-0.20260121234050-2f0507726303
12+
github.com/dolthub/go-mysql-server v0.20.1-0.20260204193159-86990113e4cc
1313
github.com/dolthub/pg_query_go/v6 v6.0.0-20251215122834-fb20be4254d1
1414
github.com/dolthub/sqllogictest/go v0.0.0-20240618184124-ca47f9354216
15-
github.com/dolthub/vitess v0.0.0-20260121194826-a5ce52b608e4
15+
github.com/dolthub/vitess v0.0.0-20260202234501-b14ed9b1632b
1616
github.com/fatih/color v1.13.0
1717
github.com/goccy/go-json v0.10.2
1818
github.com/gogo/protobuf v1.3.2

go.sum

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -228,8 +228,8 @@ github.com/dolthub/aws-sdk-go-ini-parser v0.0.0-20250305001723-2821c37f6c12 h1:I
228228
github.com/dolthub/aws-sdk-go-ini-parser v0.0.0-20250305001723-2821c37f6c12/go.mod h1:rN7X8BHwkjPcfMQQ2QTAq/xM3leUSGLfb+1Js7Y6TVo=
229229
github.com/dolthub/dolt-mcp v0.2.2 h1:bpROmam74n95uU4EA3BpOIVlTDT0pzeFMBwe/YRq2mI=
230230
github.com/dolthub/dolt-mcp v0.2.2/go.mod h1:S++DJ4QWTAXq+0TNzFa7Oq3IhoT456DJHwAINFAHgDQ=
231-
github.com/dolthub/dolt/go v0.40.5-0.20260122084121-6b5d5373d1ec h1:IzUyIbG7oX3Je53nxq0ZNrsp3C9n2THmiWMJHA9KeDo=
232-
github.com/dolthub/dolt/go v0.40.5-0.20260122084121-6b5d5373d1ec/go.mod h1:UUKnXBHOBTr5CzxFLOZ/9Pm0O+F4ZArKRxs5kUMpXH8=
231+
github.com/dolthub/dolt/go v0.40.5-0.20260205001014-db7263eb669c h1:lrTKtYUO5T5ka0/rgOx9TnSIFuzUv6R86wx7cRDVuUU=
232+
github.com/dolthub/dolt/go v0.40.5-0.20260205001014-db7263eb669c/go.mod h1:GTxR5JEXqpGgYHQc6nBnu/m+TgEC/LP9IPB+b3tIrvc=
233233
github.com/dolthub/eventsapi_schema v0.0.0-20250915094920-eadfd39051ca h1:BGFz/0OlKIuC6qHIZQbvPapFvdAJkeEyGXWVgL5clmE=
234234
github.com/dolthub/eventsapi_schema v0.0.0-20250915094920-eadfd39051ca/go.mod h1:CoDLfgPqHyBtth0Cp+fi/CmC4R81zJNX4wPjShdZ+Bw=
235235
github.com/dolthub/flatbuffers/v23 v23.3.3-dh.2 h1:u3PMzfF8RkKd3lB9pZ2bfn0qEG+1Gms9599cr0REMww=
@@ -238,8 +238,8 @@ github.com/dolthub/fslock v0.0.3 h1:iLMpUIvJKMKm92+N1fmHVdxJP5NdyDK5bK7z7Ba2s2U=
238238
github.com/dolthub/fslock v0.0.3/go.mod h1:QWql+P17oAAMLnL4HGB5tiovtDuAjdDTPbuqx7bYfa0=
239239
github.com/dolthub/go-icu-regex v0.0.0-20250916051405-78a38d478790 h1:zxMsH7RLiG+dlZ/y0LgJHTV26XoiSJcuWq+em6t6VVc=
240240
github.com/dolthub/go-icu-regex v0.0.0-20250916051405-78a38d478790/go.mod h1:F3cnm+vMRK1HaU6+rNqQrOCyR03HHhR1GWG2gnPOqaE=
241-
github.com/dolthub/go-mysql-server v0.20.1-0.20260121234050-2f0507726303 h1:O4s+FF9G1Jv7uRnR/bB9jTzvPfvnZbcZOZMa3cip8d4=
242-
github.com/dolthub/go-mysql-server v0.20.1-0.20260121234050-2f0507726303/go.mod h1:7L2EdzgWLnS7blMNuF+67RTZVMhRLyKQv36mZcjU8u8=
241+
github.com/dolthub/go-mysql-server v0.20.1-0.20260204193159-86990113e4cc h1:gnUcLBhmGmUeoYTdkPuknx2X5Imjo1/O4onz3duwC90=
242+
github.com/dolthub/go-mysql-server v0.20.1-0.20260204193159-86990113e4cc/go.mod h1:LEWdXw6LKjdonOv2X808RpUc8wZVtQx4ZEPvmDWkvY4=
243243
github.com/dolthub/gozstd v0.0.0-20240423170813-23a2903bca63 h1:OAsXLAPL4du6tfbBgK0xXHZkOlos63RdKYS3Sgw/dfI=
244244
github.com/dolthub/gozstd v0.0.0-20240423170813-23a2903bca63/go.mod h1:lV7lUeuDhH5thVGDCKXbatwKy2KW80L4rMT46n+Y2/Q=
245245
github.com/dolthub/ishell v0.0.0-20240701202509-2b217167d718 h1:lT7hE5k+0nkBdj/1UOSFwjWpNxf+LCApbRHgnCA17XE=
@@ -250,8 +250,8 @@ github.com/dolthub/pg_query_go/v6 v6.0.0-20251215122834-fb20be4254d1 h1:GY17cGA4
250250
github.com/dolthub/pg_query_go/v6 v6.0.0-20251215122834-fb20be4254d1/go.mod h1:qnrZP3/1slFl2Bq5yw38HLOsArZareGwdpEceriblLc=
251251
github.com/dolthub/sqllogictest/go v0.0.0-20240618184124-ca47f9354216 h1:JWkKRE4EHUcEVQCMRBej8DYxjYjRz/9MdF/NNQh0o70=
252252
github.com/dolthub/sqllogictest/go v0.0.0-20240618184124-ca47f9354216/go.mod h1:e/FIZVvT2IR53HBCAo41NjqgtEnjMJGKca3Y/dAmZaA=
253-
github.com/dolthub/vitess v0.0.0-20260121194826-a5ce52b608e4 h1:isOZRx9OfOdwZ4UmeYrS+UYiiu02e8taNJLAuqZjfcQ=
254-
github.com/dolthub/vitess v0.0.0-20260121194826-a5ce52b608e4/go.mod h1:FLWqdXsAeeBQyFwDjmBVu0GnbjI2MKeRf3tRVdJEKlI=
253+
github.com/dolthub/vitess v0.0.0-20260202234501-b14ed9b1632b h1:B8QS0U5EHtJTiOptjti1cH/OiE6uczyhePtvVFigf3w=
254+
github.com/dolthub/vitess v0.0.0-20260202234501-b14ed9b1632b/go.mod h1:eLLslh1CSPMf89pPcaMG4yM72PQbTN9OUYJeAy0fAis=
255255
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
256256
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
257257
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=

postgres/parser/parser/sql.y

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13073,7 +13073,7 @@ d_expr:
1307313073
| '(' a_expr ')' '.' '@' ICONST
1307413074
{
1307513075
idx, err := $6.numVal().AsInt32()
13076-
if err != nil || idx <= 0 { return setErr(sqllex, err) }
13076+
if err != nil { return setErr(sqllex, err) }
1307713077
$$.val = &tree.ColumnAccessExpr{Expr: $2.expr(), ByIndex: true, ColIndex: int(idx-1)}
1307813078
}
1307913079
| '(' a_expr ')'

server/analyzer/resolve_type.go

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
package analyzer
1616

1717
import (
18+
"github.com/cockroachdb/errors"
1819
"github.com/dolthub/go-mysql-server/sql"
1920
"github.com/dolthub/go-mysql-server/sql/analyzer"
2021
"github.com/dolthub/go-mysql-server/sql/plan"
@@ -24,7 +25,7 @@ import (
2425

2526
"github.com/dolthub/doltgresql/core"
2627
"github.com/dolthub/doltgresql/core/id"
27-
"github.com/dolthub/doltgresql/server/expression"
28+
pgexprs "github.com/dolthub/doltgresql/server/expression"
2829
pgtransform "github.com/dolthub/doltgresql/server/transform"
2930
pgtypes "github.com/dolthub/doltgresql/server/types"
3031
)
@@ -118,26 +119,38 @@ func ResolveTypeForNodes(ctx *sql.Context, a *analyzer.Analyzer, node sql.Node,
118119

119120
// ResolveTypeForExprs replaces types.ResolvableType to appropriate pgtypes.DoltgresType.
120121
func ResolveTypeForExprs(ctx *sql.Context, a *analyzer.Analyzer, node sql.Node, scope *plan.Scope, selector analyzer.RuleSelector, qFlags *sql.QueryFlags) (sql.Node, transform.TreeIdentity, error) {
121-
return pgtransform.NodeExprsWithOpaque(node, func(expr sql.Expression) (sql.Expression, transform.TreeIdentity, error) {
122-
var same = transform.SameTree
123-
switch e := expr.(type) {
124-
case *expression.ExplicitCast:
125-
if rt, ok := e.Type().(*pgtypes.DoltgresType); ok && !rt.IsResolvedType() {
122+
return pgtransform.NodeExprsWithOpaque(node, func(e sql.Expression) (sql.Expression, transform.TreeIdentity, error) {
123+
switch expr := e.(type) {
124+
case *pgexprs.ColumnAccess:
125+
exprType, _ := expr.Type().(*pgtypes.DoltgresType)
126+
if exprType == nil {
127+
return nil, transform.NewTree, errors.New("column access is missing its child expression")
128+
} else if exprType.IsResolvedType() {
129+
// The type has already been resolved
130+
return expr, transform.SameTree, nil
131+
}
132+
newType, err := resolveType(ctx, exprType)
133+
if err != nil {
134+
return nil, transform.NewTree, err
135+
}
136+
return expr.WithType(newType), transform.NewTree, nil
137+
case *pgexprs.ExplicitCast:
138+
if rt, ok := expr.Type().(*pgtypes.DoltgresType); ok && !rt.IsResolvedType() {
126139
dt, err := resolveType(ctx, rt)
127140
if err != nil {
128141
return nil, transform.NewTree, err
129142
}
130-
same = transform.NewTree
131143
if !dt.IsDefined {
132144
return nil, transform.NewTree, pgtypes.ErrTypeIsOnlyAShell.New(dt.Name())
133145
} else {
134-
expr = e.WithCastToType(dt)
146+
return expr.WithCastToType(dt), transform.NewTree, nil
135147
}
148+
} else {
149+
return expr, transform.SameTree, nil
136150
}
137-
return expr, same, nil
138151
default:
139152
// TODO: add expressions that use unresolved types like domain
140-
return e, transform.SameTree, nil
153+
return expr, transform.SameTree, nil
141154
}
142155
})
143156
}

server/ast/expr.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -300,7 +300,18 @@ func nodeExpr(ctx *Context, node tree.Expr) (vitess.Expr, error) {
300300
logrus.Warnf("collate is not yet supported, ignoring")
301301
return nodeExpr(ctx, node.Expr)
302302
case *tree.ColumnAccessExpr:
303-
return nil, errors.Errorf("(E).x is not yet supported")
303+
colAccess, err := pgexprs.NewColumnAccess(node.ColName, node.ColIndex)
304+
if err != nil {
305+
return nil, err
306+
}
307+
expr, err := nodeExpr(ctx, node.Expr)
308+
if err != nil {
309+
return nil, err
310+
}
311+
return vitess.InjectedExpr{
312+
Expression: colAccess,
313+
Children: vitess.Exprs{expr},
314+
}, nil
304315
case *tree.ColumnItem:
305316
var tableName vitess.TableName
306317
if node.TableName != nil {

server/expression/column_access.go

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
// Copyright 2026 Dolthub, Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package expression
16+
17+
import (
18+
"fmt"
19+
20+
"github.com/cockroachdb/errors"
21+
"github.com/dolthub/go-mysql-server/sql"
22+
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
23+
24+
pgtypes "github.com/dolthub/doltgresql/server/types"
25+
)
26+
27+
// ColumnAccess represents an ARRAY[...] expression.
28+
type ColumnAccess struct {
29+
colName string
30+
colNameIdx int
31+
colTyp *pgtypes.DoltgresType
32+
child sql.Expression
33+
}
34+
35+
var _ vitess.Injectable = (*ColumnAccess)(nil)
36+
var _ sql.Expression = (*ColumnAccess)(nil)
37+
38+
// NewColumnAccess returns a new *ColumnAccess.
39+
func NewColumnAccess(colName string, colIdx int) (*ColumnAccess, error) {
40+
if len(colName) > 0 {
41+
return &ColumnAccess{
42+
colName: colName,
43+
colNameIdx: -1,
44+
colTyp: nil,
45+
child: nil,
46+
}, nil
47+
} else {
48+
return &ColumnAccess{
49+
colName: "",
50+
colNameIdx: colIdx,
51+
colTyp: nil,
52+
child: nil,
53+
}, nil
54+
}
55+
}
56+
57+
// Children implements the sql.Expression interface.
58+
func (expr *ColumnAccess) Children() []sql.Expression {
59+
return []sql.Expression{expr.child}
60+
}
61+
62+
// Eval implements the sql.Expression interface.
63+
func (expr *ColumnAccess) Eval(ctx *sql.Context, row sql.Row) (any, error) {
64+
field, err := expr.child.Eval(ctx, row)
65+
if err != nil {
66+
return nil, err
67+
}
68+
if field == nil {
69+
return nil, nil
70+
}
71+
recordVals, ok := field.([]pgtypes.RecordValue)
72+
if !ok {
73+
if len(expr.colName) > 0 {
74+
return nil, errors.Errorf("column notation .%s applied to type %s, which is not a composite type",
75+
expr.colName, expr.child.Type().String())
76+
} else {
77+
return nil, errors.Errorf("column notation .@%d applied to type %s, which is not a composite type",
78+
expr.colNameIdx+1, expr.child.Type().String())
79+
}
80+
}
81+
return recordVals[expr.colNameIdx].Value, nil
82+
}
83+
84+
// IsNullable implements the sql.Expression interface.
85+
func (expr *ColumnAccess) IsNullable() bool {
86+
return true
87+
}
88+
89+
// Resolved implements the sql.Expression interface.
90+
func (expr *ColumnAccess) Resolved() bool {
91+
return expr.child != nil && expr.child.Resolved()
92+
}
93+
94+
// String implements the sql.Expression interface.
95+
func (expr *ColumnAccess) String() string {
96+
if expr.child == nil {
97+
return "COLUMN_ACCESS"
98+
}
99+
if len(expr.colName) > 0 {
100+
return fmt.Sprintf("(%s).%s", expr.child.String(), expr.colName)
101+
} else {
102+
return fmt.Sprintf("(%s).@%d", expr.child.String(), expr.colNameIdx+1)
103+
}
104+
}
105+
106+
// Type implements the sql.Expression interface.
107+
func (expr *ColumnAccess) Type() sql.Type {
108+
if expr.colTyp != nil {
109+
return expr.colTyp
110+
}
111+
if expr.child == nil {
112+
return nil
113+
}
114+
// We're technically returning a different type here since an unresolved type is not the same as a resolved one.
115+
// However, for many early analyzer steps, we only check the ID, so this at least lets us get past those cases.
116+
return pgtypes.NewUnresolvedDoltgresTypeFromID(expr.child.Type().(*pgtypes.DoltgresType).CompositeAttrs[expr.colNameIdx].TypeID)
117+
}
118+
119+
// WithChildren implements the sql.Expression interface.
120+
func (expr *ColumnAccess) WithChildren(children ...sql.Expression) (sql.Expression, error) {
121+
if len(children) != 1 {
122+
return nil, sql.ErrInvalidChildrenNumber.New(expr, len(children), 1)
123+
}
124+
childType := children[0].Type()
125+
doltgresType, ok := childType.(*pgtypes.DoltgresType)
126+
if !ok {
127+
return nil, errors.New("column access is only valid for Doltgres types")
128+
}
129+
if !doltgresType.IsCompositeType() {
130+
if len(expr.colName) > 0 {
131+
return nil, errors.Errorf("column notation .%s applied to type %s, which is not a composite type",
132+
expr.colName, children[0].Type().String())
133+
} else {
134+
return nil, errors.Errorf("column notation .@%d applied to type %s, which is not a composite type",
135+
expr.colNameIdx+1, children[0].Type().String())
136+
}
137+
}
138+
var idx int
139+
if len(expr.colName) > 0 {
140+
idx = -1
141+
for _, attr := range doltgresType.CompositeAttrs {
142+
if attr.Name == expr.colName {
143+
idx = int(attr.Num - 1)
144+
break
145+
}
146+
}
147+
if idx == -1 {
148+
return nil, errors.Errorf(`column "%s" not found in data type %s`,
149+
expr.colName, doltgresType.String())
150+
}
151+
} else {
152+
if expr.colNameIdx < 0 || expr.colNameIdx >= len(doltgresType.CompositeAttrs) {
153+
return nil, errors.Errorf("column notation .@%d applied to type %s is out of bounds",
154+
expr.colNameIdx+1, children[0].Type().String())
155+
}
156+
idx = expr.colNameIdx
157+
}
158+
return &ColumnAccess{
159+
colName: expr.colName,
160+
colNameIdx: idx,
161+
colTyp: expr.colTyp,
162+
child: children[0],
163+
}, nil
164+
}
165+
166+
// WithResolvedChildren implements the vitess.InjectableExpression interface.
167+
func (expr *ColumnAccess) WithResolvedChildren(children []any) (any, error) {
168+
newExpressions := make([]sql.Expression, len(children))
169+
for i, resolvedChild := range children {
170+
resolvedExpression, ok := resolvedChild.(sql.Expression)
171+
if !ok {
172+
return nil, errors.Errorf("expected vitess child to be an expression but has type `%T`", resolvedChild)
173+
}
174+
newExpressions[i] = resolvedExpression
175+
}
176+
return expr.WithChildren(newExpressions...)
177+
}
178+
179+
// WithType returns this expression with the given type set, as it must be set within the analyzer.
180+
func (expr *ColumnAccess) WithType(typ *pgtypes.DoltgresType) sql.Expression {
181+
ne := *expr
182+
ne.colTyp = typ
183+
return &ne
184+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// Copyright 2024 Dolthub, Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package functions
16+
17+
import (
18+
"github.com/cockroachdb/errors"
19+
"github.com/dolthub/go-mysql-server/sql"
20+
21+
"github.com/dolthub/doltgresql/server/functions/framework"
22+
pgtypes "github.com/dolthub/doltgresql/server/types"
23+
)
24+
25+
// initDoltRecordTrim registers the functions to the catalog.
26+
func initDoltRecordTrim() {
27+
framework.RegisterFunction(dolt_recordTrim)
28+
}
29+
30+
// dolt_recordTrim is used to remove a specific column within a composite type. This will generally lead to an invalid
31+
// value for the composite type, however this is used within the DROP COLUMN table hook to fix data for all columns that
32+
// reference the type, as that is the only time when the data is invalid. This is why this is a "dolt_" function as
33+
// well, as it's not intended for general use.
34+
var dolt_recordTrim = framework.Function2{
35+
Name: "dolt_recordtrim",
36+
Return: pgtypes.AnyElement,
37+
Parameters: [2]*pgtypes.DoltgresType{pgtypes.AnyElement, pgtypes.Int32},
38+
Strict: true,
39+
Callable: func(ctx *sql.Context, types [3]*pgtypes.DoltgresType, val1 any, val2 any) (any, error) {
40+
if !types[0].IsCompositeType() {
41+
return val1, nil
42+
}
43+
trimVal := val2.(int32)
44+
recordVals := val1.([]pgtypes.RecordValue)
45+
if trimVal < 0 || int(trimVal) >= len(recordVals) {
46+
return nil, errors.New("record trim index is out of bounds")
47+
}
48+
newRecordVals := make([]pgtypes.RecordValue, len(recordVals)-1)
49+
copy(newRecordVals, recordVals[:trimVal])
50+
copy(newRecordVals[trimVal:], recordVals[trimVal+1:])
51+
return newRecordVals, nil
52+
},
53+
}

0 commit comments

Comments
 (0)