Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
160 changes: 160 additions & 0 deletions internal/execution/expressions/subquery.go
Original file line number Diff line number Diff line change
@@ -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
}
1 change: 1 addition & 0 deletions internal/syntax/lexing/lex.go
Original file line number Diff line number Diff line change
Expand Up @@ -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}},
Expand Down
4 changes: 4 additions & 0 deletions internal/syntax/lexing/lexer.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand All @@ -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,
Expand Down
29 changes: 29 additions & 0 deletions internal/syntax/parsing/expressions.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
}

Expand Down Expand Up @@ -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)
5 changes: 5 additions & 0 deletions internal/syntax/tokens/token_type.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const (
TokenTypeAll
TokenTypeAlter
TokenTypeAnd
TokenTypeAny
TokenTypeAs
TokenTypeAscending
TokenTypeBetween
Expand All @@ -30,12 +31,14 @@ const (
TokenTypeDescending
TokenTypeDistinct
TokenTypeExcept
TokenTypeExists
TokenTypeExplain
TokenTypeFalse
TokenTypeForeign
TokenTypeFrom
TokenTypeGroup
TokenTypeILike
TokenTypeIn
TokenTypeIndex
TokenTypeInsert
TokenTypeIntersect
Expand All @@ -58,6 +61,7 @@ const (
TokenTypeSelect
TokenTypeSequence
TokenTypeSet
TokenTypeSome
TokenTypeSymmetric
TokenTypeTable
TokenTypeTrue
Expand Down Expand Up @@ -110,6 +114,7 @@ const (
TokenTypeNotBetween
TokenTypeNotBetweenSymmetric
TokenTypeNotILike
TokenTypeNotIn
TokenTypeNotLike
TokenTypeNotNull
TokenTypePrimaryKey
Expand Down