diff --git a/internal/execution/expressions/subquery.go b/internal/execution/expressions/subquery.go new file mode 100644 index 0000000..3c97fb4 --- /dev/null +++ b/internal/execution/expressions/subquery.go @@ -0,0 +1,160 @@ +package expressions + +import ( + "bytes" + "fmt" + + "github.com/efritz/gostgres/internal/execution/serialization" + "github.com/efritz/gostgres/internal/shared/impls" + "github.com/efritz/gostgres/internal/shared/rows" + "github.com/efritz/gostgres/internal/shared/scan" +) + +type Query interface { + Serialize(w serialization.IndentWriter) + Optimize() + Scanner(ctx impls.Context) (scan.RowScanner, error) +} + +// +// +// + +type existsSubqueryExpression struct { + subquery Query +} + +func NewExistsSubqueryExpression(subquery Query) impls.Expression { + return existsSubqueryExpression{ + subquery: subquery, + } +} + +func (e existsSubqueryExpression) String() string { return "" } // TODO +func (e existsSubqueryExpression) Equal(other impls.Expression) bool { return false } // TODO +func (e existsSubqueryExpression) Children() []impls.Expression { return nil } // TODO +func (e existsSubqueryExpression) Fold() impls.Expression { return e } // TODO +func (e existsSubqueryExpression) Map(f func(impls.Expression) impls.Expression) impls.Expression { + return e +} // TODO + +func (e existsSubqueryExpression) ValueFrom(ctx impls.Context, row rows.Row) (any, error) { + // TODO - want to pull this out and do optimization before the enclosing query is executed + // TODO - need to pull out serialization into separate parts in the explain output as well + ctx.Log("Optimizing subquery") + e.subquery.Optimize() + + var buf bytes.Buffer + w := serialization.NewIndentWriter(&buf) + e.subquery.Serialize(w) + ctx.Log("%s", buf.String()) + + ctx.Log("Preparing subquery over %s", row) + + s, err := e.subquery.Scanner(ctx.WithOuterRow(row)) + if err != nil { + return nil, err + } + + ctx.Log("Scanning subquery") + + if _, err := s.Scan(); err != nil { + if err != scan.ErrNoRows { + return nil, err + } + + return false, err + } + + return true, nil +} + +// +// +// + +type anySubqueryExpression struct { + expression impls.Expression + op string // TODO + subquery Query +} + +func NewAnySubqueryExpression(expression impls.Expression, op string, subquery Query) impls.Expression { + return anySubqueryExpression{ + expression: expression, + op: op, + subquery: subquery, + } +} + +func (e anySubqueryExpression) String() string { return "" } // TODO +func (e anySubqueryExpression) Equal(other impls.Expression) bool { return false } // TODO +func (e anySubqueryExpression) Children() []impls.Expression { return nil } // TODO +func (e anySubqueryExpression) Fold() impls.Expression { return e } // TODO +func (e anySubqueryExpression) Map(f func(impls.Expression) impls.Expression) impls.Expression { + return e +} // TODO + +func (e anySubqueryExpression) ValueFrom(ctx impls.Context, row rows.Row) (any, error) { + return nil, fmt.Errorf("unimplemented") // TODO +} + +// +// +// + +type allSubqueryExpression struct { + expression impls.Expression + op string // TODO + subquery Query +} + +func NewAllSubqueryExpression(expression impls.Expression, op string, subquery Query) impls.Expression { + return allSubqueryExpression{ + expression: expression, + op: op, + subquery: subquery, + } +} + +func (e allSubqueryExpression) String() string { return "" } // TODO +func (e allSubqueryExpression) Equal(other impls.Expression) bool { return false } // TODO +func (e allSubqueryExpression) Children() []impls.Expression { return nil } // TODO +func (e allSubqueryExpression) Fold() impls.Expression { return e } // TODO +func (e allSubqueryExpression) Map(f func(impls.Expression) impls.Expression) impls.Expression { + return e +} // TODO + +func (e allSubqueryExpression) ValueFrom(ctx impls.Context, row rows.Row) (any, error) { + return nil, fmt.Errorf("unimplemented") // TODO +} + +// +// +// + +type opSubqueryExpression struct { + expression impls.Expression + op string // TODO + subquery Query +} + +func NewOpSubqueryExpression(expression impls.Expression, op string, subquery Query) impls.Expression { + return opSubqueryExpression{ + expression: expression, + op: op, + subquery: subquery, + } +} + +func (e opSubqueryExpression) String() string { return "" } // TODO +func (e opSubqueryExpression) Equal(other impls.Expression) bool { return false } // TODO +func (e opSubqueryExpression) Children() []impls.Expression { return nil } // TODO +func (e opSubqueryExpression) Fold() impls.Expression { return e } // TODO +func (e opSubqueryExpression) Map(f func(impls.Expression) impls.Expression) impls.Expression { + return e +} // TODO + +func (e opSubqueryExpression) ValueFrom(ctx impls.Context, row rows.Row) (any, error) { + return nil, fmt.Errorf("unimplemented") // TODO +} diff --git a/internal/syntax/lexing/lex.go b/internal/syntax/lexing/lex.go index dca1834..53674b2 100644 --- a/internal/syntax/lexing/lex.go +++ b/internal/syntax/lexing/lex.go @@ -28,6 +28,7 @@ var tokenSequenceReplacements = []tokenSequenceReplacement{ {tokens.TokenTypeNotBetween, []tokens.TokenType{tokens.TokenTypeNot, tokens.TokenTypeBetween}}, {tokens.TokenTypeNotBetweenSymmetric, []tokens.TokenType{tokens.TokenTypeNot, tokens.TokenTypeBetween, tokens.TokenTypeSymmetric}}, {tokens.TokenTypeNotILike, []tokens.TokenType{tokens.TokenTypeNot, tokens.TokenTypeILike}}, + {tokens.TokenTypeNotIn, []tokens.TokenType{tokens.TokenTypeNot, tokens.TokenTypeIn}}, {tokens.TokenTypeNotLike, []tokens.TokenType{tokens.TokenTypeNot, tokens.TokenTypeLike}}, {tokens.TokenTypeNotNull, []tokens.TokenType{tokens.TokenTypeNot, tokens.TokenTypeNull}}, {tokens.TokenTypePrimaryKey, []tokens.TokenType{tokens.TokenTypePrimary, tokens.TokenTypeKey}}, diff --git a/internal/syntax/lexing/lexer.go b/internal/syntax/lexing/lexer.go index 6430238..1645371 100644 --- a/internal/syntax/lexing/lexer.go +++ b/internal/syntax/lexing/lexer.go @@ -18,6 +18,7 @@ var keywordSet = map[string]tokens.TokenType{ "all": tokens.TokenTypeAll, "alter": tokens.TokenTypeAlter, "and": tokens.TokenTypeAnd, + "any": tokens.TokenTypeAny, "as": tokens.TokenTypeAs, "asc": tokens.TokenTypeAscending, "between": tokens.TokenTypeBetween, @@ -30,12 +31,14 @@ var keywordSet = map[string]tokens.TokenType{ "desc": tokens.TokenTypeDescending, "distinct": tokens.TokenTypeDistinct, "except": tokens.TokenTypeExcept, + "exists": tokens.TokenTypeExists, "explain": tokens.TokenTypeExplain, "false": tokens.TokenTypeFalse, "foreign": tokens.TokenTypeForeign, "from": tokens.TokenTypeFrom, "group": tokens.TokenTypeGroup, "ilike": tokens.TokenTypeILike, + "in": tokens.TokenTypeIn, "index": tokens.TokenTypeIndex, "insert": tokens.TokenTypeInsert, "intersect": tokens.TokenTypeIntersect, @@ -59,6 +62,7 @@ var keywordSet = map[string]tokens.TokenType{ "select": tokens.TokenTypeSelect, "sequence": tokens.TokenTypeSequence, "set": tokens.TokenTypeSet, + "some": tokens.TokenTypeSome, "symmetric": tokens.TokenTypeSymmetric, "table": tokens.TokenTypeTable, "true": tokens.TokenTypeTrue, diff --git a/internal/syntax/parsing/expressions.go b/internal/syntax/parsing/expressions.go index e17567e..f25a550 100644 --- a/internal/syntax/parsing/expressions.go +++ b/internal/syntax/parsing/expressions.go @@ -57,6 +57,7 @@ func (p *parser) initExpressionPrefixParsers() { tokens.TokenTypeMinus: p.parseUnary(expressions.NewUnaryMinus), tokens.TokenTypeLeftParen: p.parseParenthesizedExpression, tokens.TokenTypePlus: p.parseUnary(expressions.NewUnaryPlus), + tokens.TokenTypeExists: p.parseExistsExpression, } } @@ -261,3 +262,31 @@ func negate(parserFunc infixParserFunc) infixParserFunc { return expressions.NewNot(expression), nil } } + +// exists := `EXISTS` `(` subquery `)` +func (p *parser) parseExistsExpression(token tokens.Token) (impls.Expression, error) { + if _, err := p.mustAdvance(isType(tokens.TokenTypeLeftParen)); err != nil { + return nil, err + } + + subquery, err := p.parseSelectOrValues() + if err != nil { + return nil, err + } + + if _, err := p.mustAdvance(isType(tokens.TokenTypeRightParen)); err != nil { + return nil, err + } + + return expressions.NewExistsSubqueryExpression(subquery), nil +} + +// IN/NOT IN (subquery) => = ANY/!= ANY +// op ANY/SOME (subquery) +// op ALL (subquery) + +// TODO +// row_constructor {IN / NOT IN} (subquery) +// row_constructor op {ALL / ANY / SOME} (subquery) +// row_constructor op ALL (subquery) +// row_constructor op (single-row subquery) diff --git a/internal/syntax/tokens/token_type.go b/internal/syntax/tokens/token_type.go index 31ec83c..3119d19 100644 --- a/internal/syntax/tokens/token_type.go +++ b/internal/syntax/tokens/token_type.go @@ -18,6 +18,7 @@ const ( TokenTypeAll TokenTypeAlter TokenTypeAnd + TokenTypeAny TokenTypeAs TokenTypeAscending TokenTypeBetween @@ -30,12 +31,14 @@ const ( TokenTypeDescending TokenTypeDistinct TokenTypeExcept + TokenTypeExists TokenTypeExplain TokenTypeFalse TokenTypeForeign TokenTypeFrom TokenTypeGroup TokenTypeILike + TokenTypeIn TokenTypeIndex TokenTypeInsert TokenTypeIntersect @@ -58,6 +61,7 @@ const ( TokenTypeSelect TokenTypeSequence TokenTypeSet + TokenTypeSome TokenTypeSymmetric TokenTypeTable TokenTypeTrue @@ -110,6 +114,7 @@ const ( TokenTypeNotBetween TokenTypeNotBetweenSymmetric TokenTypeNotILike + TokenTypeNotIn TokenTypeNotLike TokenTypeNotNull TokenTypePrimaryKey