Skip to content

Commit

Permalink
Referencing columns within arrays
Browse files Browse the repository at this point in the history
  • Loading branch information
Hydrocharged committed Feb 9, 2024
1 parent f68f65c commit 6c08454
Show file tree
Hide file tree
Showing 7 changed files with 232 additions and 28 deletions.
4 changes: 4 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,10 @@ There are exceptions, as some statements we do not yet support, and cannot suppo
In these cases, we must add a `//TODO:` comment stating what is missing and why it isn't an error.
This will at least allow us to track all such instances where we deviate from the expected behavior, which we can also document elsewhere for users of DoltgreSQL.

### `server/expression`

TODO: FILL ME OUT BEFORE COMMITTING

### `server/functions`

The `functions` package contains the functions, along with an implementation to approximate the function overloading structure (and type coercion).
Expand Down
8 changes: 4 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ require (
github.com/PuerkitoBio/goquery v1.8.1
github.com/cockroachdb/apd/v2 v2.0.3-0.20200518165714-d020e156310a
github.com/cockroachdb/errors v1.7.5
github.com/dolthub/dolt/go v0.40.5-0.20240207142355-cbe6b0ce7f01
github.com/dolthub/dolt/go/gen/proto/dolt/services/eventsapi v0.0.0-20240205125942-fc7c3429f29c
github.com/dolthub/go-mysql-server v0.17.1-0.20240207124505-c0f397a6aaca
github.com/dolthub/dolt/go v0.40.5-0.20240209132845-d391bcf946e7
github.com/dolthub/dolt/go/gen/proto/dolt/services/eventsapi v0.0.0-20240209132845-d391bcf946e7
github.com/dolthub/go-mysql-server v0.17.1-0.20240209131710-527b791c74c0
github.com/dolthub/sqllogictest/go v0.0.0-20240118211725-a52e3f5697e3
github.com/dolthub/vitess v0.0.0-20240207121055-c057d2347007
github.com/dolthub/vitess v0.0.0-20240209125211-6c93b0341608
github.com/fatih/color v1.13.0
github.com/gogo/protobuf v1.3.2
github.com/golang/geo v0.0.0-20200730024412-e86565bf3f35
Expand Down
16 changes: 8 additions & 8 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -214,18 +214,18 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZm
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/dolthub/dolt/go v0.40.5-0.20240207142355-cbe6b0ce7f01 h1:r/1CAcbUpAHR0jG15YqMgoribkfOURNqH60M8lITN2M=
github.com/dolthub/dolt/go v0.40.5-0.20240207142355-cbe6b0ce7f01/go.mod h1:F/oS2i85PyQgoKG4ay4joe/iv2HR9njLbyYaqJ2FSyU=
github.com/dolthub/dolt/go/gen/proto/dolt/services/eventsapi v0.0.0-20240205125942-fc7c3429f29c h1:AVAqyKKv6UVOcKr9anIe1VQItJEIcZENMtU0FF8bycM=
github.com/dolthub/dolt/go/gen/proto/dolt/services/eventsapi v0.0.0-20240205125942-fc7c3429f29c/go.mod h1:gHeHIDGU7em40EhFTliq62pExFcc1hxDTIZ9g5UqXYM=
github.com/dolthub/dolt/go v0.40.5-0.20240209132845-d391bcf946e7 h1:5SlrntR/5HMgX6EQntyphERy+CVKdh6vTlS6Kk6f1aI=
github.com/dolthub/dolt/go v0.40.5-0.20240209132845-d391bcf946e7/go.mod h1:kDOROjdsJ4GK+ooNjO0Zk5KrERKGjFfglgy3vUojBGQ=
github.com/dolthub/dolt/go/gen/proto/dolt/services/eventsapi v0.0.0-20240209132845-d391bcf946e7 h1:G+gH9yQq962klV5vVyrqkGGWl+YJYuURn6SazPnq72E=
github.com/dolthub/dolt/go/gen/proto/dolt/services/eventsapi v0.0.0-20240209132845-d391bcf946e7/go.mod h1:gHeHIDGU7em40EhFTliq62pExFcc1hxDTIZ9g5UqXYM=
github.com/dolthub/flatbuffers/v23 v23.3.3-dh.2 h1:u3PMzfF8RkKd3lB9pZ2bfn0qEG+1Gms9599cr0REMww=
github.com/dolthub/flatbuffers/v23 v23.3.3-dh.2/go.mod h1:mIEZOHnFx4ZMQeawhw9rhsj+0zwQj7adVsnBX7t+eKY=
github.com/dolthub/fslock v0.0.3 h1:iLMpUIvJKMKm92+N1fmHVdxJP5NdyDK5bK7z7Ba2s2U=
github.com/dolthub/fslock v0.0.3/go.mod h1:QWql+P17oAAMLnL4HGB5tiovtDuAjdDTPbuqx7bYfa0=
github.com/dolthub/go-icu-regex v0.0.0-20230524105445-af7e7991c97e h1:kPsT4a47cw1+y/N5SSCkma7FhAPw7KeGmD6c9PBZW9Y=
github.com/dolthub/go-icu-regex v0.0.0-20230524105445-af7e7991c97e/go.mod h1:KPUcpx070QOfJK1gNe0zx4pA5sicIK1GMikIGLKC168=
github.com/dolthub/go-mysql-server v0.17.1-0.20240207124505-c0f397a6aaca h1:tI3X4fIUTOT0N8n+GYkPNa384WlJoOBcztK5c5mBzjU=
github.com/dolthub/go-mysql-server v0.17.1-0.20240207124505-c0f397a6aaca/go.mod h1:ANK0a6tyjrZ2cOzDJT3nFsDp80xksI4UfeijFlvnjwE=
github.com/dolthub/go-mysql-server v0.17.1-0.20240209131710-527b791c74c0 h1:0LLij7LnIfIc2NuLmfxBbojo+eXBZI/r1tDgZF38a4Y=
github.com/dolthub/go-mysql-server v0.17.1-0.20240209131710-527b791c74c0/go.mod h1:OV8F/PIK3eVVDO4rRWrlXxie7n+B9vnzssJeyCCeUFg=
github.com/dolthub/ishell v0.0.0-20221214210346-d7db0b066488 h1:0HHu0GWJH0N6a6keStrHhUAK5/o9LVfkh44pvsV4514=
github.com/dolthub/ishell v0.0.0-20221214210346-d7db0b066488/go.mod h1:ehexgi1mPxRTk0Mok/pADALuHbvATulTh6gzr7NzZto=
github.com/dolthub/jsonpath v0.0.2-0.20240201003050-392940944c15 h1:sfTETOpsrNJPDn2KydiCtDgVu6Xopq8k3JP8PjFT22s=
Expand All @@ -236,8 +236,8 @@ github.com/dolthub/sqllogictest/go v0.0.0-20240118211725-a52e3f5697e3 h1:+eDpuEJ
github.com/dolthub/sqllogictest/go v0.0.0-20240118211725-a52e3f5697e3/go.mod h1:e/FIZVvT2IR53HBCAo41NjqgtEnjMJGKca3Y/dAmZaA=
github.com/dolthub/swiss v0.1.0 h1:EaGQct3AqeP/MjASHLiH6i4TAmgbG/c4rA6a1bzCOPc=
github.com/dolthub/swiss v0.1.0/go.mod h1:BeucyB08Vb1G9tumVN3Vp/pyY4AMUnr9p7Rz7wJ7kAQ=
github.com/dolthub/vitess v0.0.0-20240207121055-c057d2347007 h1:MvFoe0FnHhxQLyp4Ldw0HRj1yu83YErbtbr7XxhaIFk=
github.com/dolthub/vitess v0.0.0-20240207121055-c057d2347007/go.mod h1:IwjNXSQPymrja5pVqmfnYdcy7Uv7eNJNBPK/MEh9OOw=
github.com/dolthub/vitess v0.0.0-20240209125211-6c93b0341608 h1:jnInva1KcJJf/QQsxbN9tTJckOZf73EzUen8rrik0Yw=
github.com/dolthub/vitess v0.0.0-20240209125211-6c93b0341608/go.mod h1:IwjNXSQPymrja5pVqmfnYdcy7Uv7eNJNBPK/MEh9OOw=
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
Expand Down
16 changes: 8 additions & 8 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,7 @@ func runServer(ctx context.Context, dEnv *env.DoltEnv) error {
//
// We also emit a heartbeat event every 24 hours the server is running.
// All events will be tagged with the doltgresql app id.
go emitUsageEvent(dEnv)
go emitUsageEvent(ctx, dEnv)

controller, err := server.RunOnDisk(ctx, os.Args[1:], dEnv)

Expand Down Expand Up @@ -589,22 +589,22 @@ func redirectStdio(args []string) ([]string, error) {
}

// emitUsageEvent emits a usage event to the event server
func emitUsageEvent(dEnv *env.DoltEnv) {
func emitUsageEvent(ctx context.Context, dEnv *env.DoltEnv) {
metricsDisabled := dEnv.Config.GetStringOrDefault(config.MetricsDisabled, "false")
disabled, err := strconv.ParseBool(metricsDisabled)
if err != nil || disabled {
return
}

evt := events.NewEvent(sqlserver.SqlServerCmd{}.EventType())
evtCollector := events.NewCollector()
evtCollector.CloseEventAndAdd(evt)
clientEvents := evtCollector.Close()

emitter, err := commands.GRPCEmitterForConfig(dEnv)
if err != nil {
return
}

_ = emitter.LogEvents(server.Version, clientEvents)
evt := events.NewEvent(sqlserver.SqlServerCmd{}.EventType())
evtCollector := events.NewCollector(server.Version, emitter)
evtCollector.CloseEventAndAdd(evt)
clientEvents := evtCollector.Close()

_ = emitter.LogEvents(ctx, server.Version, clientEvents)
}
30 changes: 22 additions & 8 deletions server/ast/expr.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (

"github.com/dolthub/doltgresql/postgres/parser/sem/tree"
"github.com/dolthub/doltgresql/postgres/parser/types"
pgexprs "github.com/dolthub/doltgresql/server/expression"
pgtypes "github.com/dolthub/doltgresql/server/types"
)

Expand Down Expand Up @@ -100,25 +101,38 @@ func nodeExpr(node tree.Expr) (vitess.Expr, error) {
return nil, fmt.Errorf("ANNOTATE_TYPE is not yet supported")
case *tree.Array:
//TODO: right now, this only works with boolean array values for the sake of demonstration
var gmsExpr sql.Expression
var sqlChildren []sql.Expression
var unresolvedChildren []vitess.Expr
var unresolvedIndexes []int
if len(node.Exprs) == 0 {
if node.ResolvedType().Family() == types.ArrayFamily && node.ResolvedType().ArrayContents().Family() == types.BoolFamily {
gmsExpr = expression.NewLiteral([]bool{}, pgtypes.BoolArray)
sqlChildren = []sql.Expression{expression.NewLiteral([]bool{}, pgtypes.BoolArray)}
} else {
return nil, fmt.Errorf("arrays are generally not yet supported")
}
} else {
vals := make([]bool, len(node.Exprs))
for i, arrayExpr := range node.Exprs {
for _, arrayExpr := range node.Exprs {
if arrayVal, ok := arrayExpr.(*tree.DBool); ok && arrayVal != nil {
vals[i] = bool(*arrayVal)
sqlChildren = append(sqlChildren, expression.NewLiteral(bool(*arrayVal), pgtypes.Bool))
} else {
return nil, fmt.Errorf("array value is not yet supported")
unresolvedChild, err := nodeExpr(arrayExpr)
if err != nil {
return nil, err
}
unresolvedChildren = append(unresolvedChildren, unresolvedChild)
unresolvedIndexes = append(unresolvedIndexes, len(sqlChildren))
sqlChildren = append(sqlChildren, nil)
}
}
gmsExpr = expression.NewLiteral(vals, pgtypes.BoolArray)
}
return vitess.InjectedExpr{Expression: gmsExpr}, nil
arrayExpr, err := pgexprs.NewArray(sqlChildren, unresolvedChildren, unresolvedIndexes, nil)
if err != nil {
return nil, err
}
return vitess.InjectedExpr{
Expression: arrayExpr,
Children: unresolvedChildren,
}, nil
case *tree.ArrayFlatten:
return nil, fmt.Errorf("flattening arrays is not yet supported")
case *tree.BinaryExpr:
Expand Down
163 changes: 163 additions & 0 deletions server/expression/array.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
// Copyright 2024 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package expression

import (
"fmt"
"strings"

"github.com/dolthub/go-mysql-server/sql"
vitess "github.com/dolthub/vitess/go/vt/sqlparser"

pgtypes "github.com/dolthub/doltgresql/server/types"
)

// Array represents an ARRAY[...] expression.
type Array struct {
sqlChildren []sql.Expression
vitessChildren vitess.Exprs
vitessIndexes []int
coercedType sql.Type
}

var _ vitess.InjectableExpression = (*Array)(nil)
var _ sql.Expression = (*Array)(nil)

// NewArray returns a new *Array.
func NewArray(expressions []sql.Expression, unresolvedChildren vitess.Exprs, unresolvedIndexes []int, coercedType sql.Type) (*Array, error) {
if len(unresolvedChildren) != len(unresolvedIndexes) {
return nil, fmt.Errorf("ARRAY has an invalid number of unresolved children (%d) and indexes (%d)",
len(unresolvedChildren), len(unresolvedIndexes))
}
for _, index := range unresolvedIndexes {
if index >= len(expressions) || index < 0 {
return nil, fmt.Errorf("ARRAY unresolved index (%d) is out of bounds (expression count: %d)",
index, len(expressions))
}
if expressions[index] != nil {
return nil, fmt.Errorf("ARRAY unresolved index (%d) points to a resolved expression", index)
}
}
return &Array{
sqlChildren: expressions,
vitessChildren: unresolvedChildren,
vitessIndexes: unresolvedIndexes,
coercedType: coercedType,
}, nil
}

// Children implements the sql.Expression interface.
func (array *Array) Children() []sql.Expression {
return array.sqlChildren
}

// Eval implements the sql.Expression interface.
func (array *Array) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) {
// TODO: make this an actual implementation instead of the mock boolean implementation
values := make([]bool, len(array.sqlChildren))
for i, expr := range array.sqlChildren {
val, err := expr.Eval(ctx, row)
if err != nil {
return nil, err
}
boolVal, ok := val.(bool)
if !ok {
return nil, fmt.Errorf("only boolean values are supported within an ARRAY for now")
}
values[i] = boolVal
}
return values, nil
}

// IsNullable implements the sql.Expression interface.
func (array *Array) IsNullable() bool {
// TODO: verify if this is actually nullable
return false
}

// Resolved implements the sql.Expression interface.
func (array *Array) Resolved() bool {
for _, child := range array.sqlChildren {
if child == nil || !child.Resolved() {
return false
}
}
return true
}

// String implements the sql.Expression interface.
func (array *Array) String() string {
sb := strings.Builder{}
sb.WriteString("ARRAY[")
for i, child := range array.sqlChildren {
if i > 0 {
sb.WriteString(", ")
}
if child == nil {
sb.WriteString("...")
} else {
sb.WriteString(child.String())
}
}
sb.WriteRune(']')
return sb.String()
}

// Type implements the sql.Expression interface.
func (array *Array) Type() sql.Type {
if array.coercedType != nil {
return array.coercedType
}
// TODO: how do we handle multiple children with different types?
for _, child := range array.sqlChildren {
if child != nil {
childType := child.Type()
if _, ok := childType.(pgtypes.BoolType); ok {
return pgtypes.BoolArray
}
}
}
// TODO: remove mock boolean array demonstration
return pgtypes.BoolArray
}

// WithChildren implements the sql.Expression interface.
func (array *Array) WithChildren(children ...sql.Expression) (sql.Expression, error) {
return &Array{
sqlChildren: children,
coercedType: array.coercedType,
}, nil
}

// WithResolvedChildren implements the vitess.InjectableExpression interface.
func (array *Array) WithResolvedChildren(children []any) (any, error) {
if len(children) != len(array.vitessIndexes) {
return nil, fmt.Errorf("invalid vitess child count, expected `%d` but got `%d`",
len(array.vitessIndexes), len(children))
}
newExpressions := make([]sql.Expression, len(array.sqlChildren))
copy(newExpressions, array.sqlChildren)
for i, resolvedChild := range children {
resolvedExpression, ok := resolvedChild.(sql.Expression)
if !ok {
return nil, fmt.Errorf("expected vitess child to be an expression but has type `%T`", resolvedChild)
}
newExpressions[array.vitessIndexes[i]] = resolvedExpression
}
return &Array{
sqlChildren: newExpressions,
coercedType: array.coercedType,
}, nil
}
23 changes: 23 additions & 0 deletions testing/go/smoke_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,29 @@ func TestSmokeTests(t *testing.T) {
},
},
},
{
Name: "ARRAY expression",
SetUpScript: []string{
"CREATE TABLE test (id INTEGER primary key, v1 BOOLEAN);",
"INSERT INTO test VALUES (1, 'true'), (2, 'false');",
},
Assertions: []ScriptTestAssertion{
{
Query: "SELECT ARRAY[v1] FROM test ORDER BY id;",
Expected: []sql.Row{
{"{t}"},
{"{f}"},
},
},
{
Query: "SELECT ARRAY[v1, true, v1] FROM test ORDER BY id;",
Expected: []sql.Row{
{"{t,t,t}"},
{"{f,t,f}"},
},
},
},
},
{
Name: "Empty statement",
Assertions: []ScriptTestAssertion{
Expand Down

0 comments on commit 6c08454

Please sign in to comment.