Skip to content

Commit

Permalink
chore: Refactor <predicate> (#167)
Browse files Browse the repository at this point in the history
As a longer term effort to remove ast.v and eval.v, this moves
<predicate> and its subtypes into their respective locations.

This also fixes a bug where the Makefile will no regenerate the
grammar.bnf.
  • Loading branch information
elliotchance authored Oct 25, 2023
1 parent 3f6b2f9 commit 9c801c0
Show file tree
Hide file tree
Showing 12 changed files with 470 additions and 309 deletions.
4 changes: 1 addition & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,8 @@ clean-docs:

# Grammar (BNF)

grammar.bnf:
grammar:
grep "//~" -r vsql | cut -d~ -f2 > grammar.bnf

grammar: grammar.bnf
python3 generate-grammar.py
v fmt -w vsql/grammar.v

Expand Down
93 changes: 4 additions & 89 deletions vsql/ast.v
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@ type Stmt = AlterSequenceStmt
| UpdateStmt

// All possible expression entities.
type Expr = BetweenExpr
| BinaryExpr
type Expr = BinaryExpr
| CallExpr
| CastExpr
| CoalesceExpr
Expand All @@ -35,18 +34,16 @@ type Expr = BetweenExpr
| CurrentTimeExpr
| CurrentTimestampExpr
| Identifier
| LikeExpr
| LocalTimeExpr
| LocalTimestampExpr
| NextValueExpr
| NoExpr
| NullExpr
| NullIfExpr
| Parameter
| Predicate
| QualifiedAsteriskExpr
| QueryExpression
| RowExpr
| SimilarExpr
| SubstringExpr
| TrimExpr
| TruthExpr
Expand All @@ -60,7 +57,7 @@ fn (e Expr) str() string {

fn (e Expr) pstr(params map[string]Value) string {
return match e {
BetweenExpr {
Predicate {
e.pstr(params)
}
BinaryExpr {
Expand Down Expand Up @@ -96,9 +93,6 @@ fn (e Expr) pstr(params map[string]Value) string {
Identifier {
e.str()
}
LikeExpr {
e.pstr(params)
}
LocalTimeExpr {
e.str()
}
Expand All @@ -111,9 +105,6 @@ fn (e Expr) pstr(params map[string]Value) string {
NoExpr {
e.str()
}
NullExpr {
e.pstr(params)
}
NullIfExpr {
e.pstr(params)
}
Expand All @@ -129,9 +120,6 @@ fn (e Expr) pstr(params map[string]Value) string {
RowExpr {
e.pstr(params)
}
SimilarExpr {
e.pstr(params)
}
SubstringExpr {
e.pstr(params)
}
Expand Down Expand Up @@ -251,24 +239,6 @@ struct UpdateStmt {
where Expr
}

// NullExpr for "IS NULL" and "IS NOT NULL".
struct NullExpr {
expr Expr
not bool
}

fn (e NullExpr) str() string {
return e.pstr(map[string]Value{})
}

fn (e NullExpr) pstr(params map[string]Value) string {
if e.not {
return '${e.expr.pstr(params)} IS NOT NULL'
}

return '${e.expr.pstr(params)} IS NULL'
}

// IdentifierChain wraps a single string that contains the chain of one or more
// identifiers, such as: "Foo".bar."BAZ"
struct IdentifierChain {
Expand Down Expand Up @@ -619,11 +589,6 @@ fn (e CallExpr) pstr(params map[string]Value) string {
return '${e.function_name}(${args})'
}

struct ComparisonPredicatePart2 {
op string
expr Expr
}

struct TableExpression {
from_clause TableReference
where_clause Expr
Expand Down Expand Up @@ -698,26 +663,6 @@ struct CommitStmt {
struct RollbackStmt {
}

struct BetweenExpr {
not bool
symmetric bool
expr Expr
left Expr
right Expr
}

fn (e BetweenExpr) pstr(params map[string]Value) string {
return '${e.expr.pstr(params)} ' + if e.not {
'NOT '
} else {
''
} + 'BETWEEN ' + if e.symmetric {
'SYMMETRIC '
} else {
''
} + '${e.left.pstr(params)} AND ${e.right.pstr(params)}'
}

struct QueryExpression {
body SimpleTable
fetch Expr
Expand All @@ -742,36 +687,6 @@ fn (e RowExpr) pstr(params map[string]Value) string {
return 'ROW(${values.join(', ')})'
}

// LikeExpr for "LIKE" and "NOT LIKE".
struct LikeExpr {
left Expr
right Expr
not bool
}

fn (e LikeExpr) pstr(params map[string]Value) string {
if e.not {
return '${e.left.pstr(params)} NOT LIKE ${e.right.pstr(params)}'
}

return '${e.left.pstr(params)} LIKE ${e.right.pstr(params)}'
}

// SimilarExpr for "SIMILAR TO" and "NOT SIMILAR TO".
struct SimilarExpr {
left Expr
right Expr
not bool
}

fn (e SimilarExpr) pstr(params map[string]Value) string {
if e.not {
return '${e.left.pstr(params)} NOT SIMILAR TO ${e.right.pstr(params)}'
}

return '${e.left.pstr(params)} SIMILAR TO ${e.right.pstr(params)}'
}

struct SortSpecification {
expr Expr
is_asc bool
Expand Down Expand Up @@ -886,7 +801,7 @@ fn (e TrimExpr) pstr(params map[string]Value) string {
return 'TRIM(${e.specification} ${e.character.pstr(params)} FROM ${e.source.pstr(params)})'
}

// UntypedNullExpr (not to be confused with NullExpr) represents an untyped
// UntypedNullExpr (not to be confused with NullPredicate) represents an untyped
// NULL. This exists as an expression (rather than a special value) because it's
// devoid of a type until it's used in an actual expression. Also, having it use
// it;s own SQLType creates a lot of branches in the codebase that require "this
Expand Down
136 changes: 6 additions & 130 deletions vsql/eval.v
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

module vsql

import regex

// A ExprOperation executes expressions for each row.
struct ExprOperation {
mut:
Expand Down Expand Up @@ -113,7 +111,10 @@ fn eval_as_type(conn &Connection, data Row, e Expr, params map[string]Value) !Ty
CountAllExpr, NextValueExpr {
return new_type('INTEGER', 0, 0)
}
BetweenExpr, NullExpr, TruthExpr, LikeExpr, SimilarExpr {
Predicate {
return e.eval_type(conn, data, params)
}
TruthExpr {
return new_type('BOOLEAN', 0, 0)
}
Parameter {
Expand Down Expand Up @@ -176,8 +177,8 @@ fn eval_as_type(conn &Connection, data Row, e Expr, params map[string]Value) !Ty

fn eval_as_value(mut conn Connection, data Row, e Expr, params map[string]Value) !Value {
match e {
BetweenExpr {
return eval_between(mut conn, data, e, params)
Predicate {
return e.eval(mut conn, data, params)
}
BinaryExpr {
return eval_binary(mut conn, data, e, params)
Expand All @@ -197,24 +198,15 @@ fn eval_as_value(mut conn Connection, data Row, e Expr, params map[string]Value)
Identifier {
return eval_identifier(data, e)
}
LikeExpr {
return eval_like(mut conn, data, e, params)
}
NextValueExpr {
return eval_next_value(mut conn, data, e, params)
}
NullExpr {
return eval_null(mut conn, data, e, params)
}
NullIfExpr {
return eval_nullif(mut conn, data, e, params)
}
Parameter {
return params[e.name] or { return sqlstate_42p02(e.name) }
}
SimilarExpr {
return eval_similar(mut conn, data, e, params)
}
SubstringExpr {
return eval_substring(mut conn, data, e, params)
}
Expand Down Expand Up @@ -376,16 +368,6 @@ fn eval_next_value(mut conn Connection, data Row, e NextValueExpr, params map[st
return new_bigint_value(next)
}

fn eval_null(mut conn Connection, data Row, e NullExpr, params map[string]Value) !Value {
value := eval_as_value(mut conn, data, e.expr, params)!

if e.not {
return new_boolean_value(!value.is_null)
}

return new_boolean_value(value.is_null)
}

fn eval_nullif(mut conn Connection, data Row, e NullIfExpr, params map[string]Value) !Value {
a := eval_as_value(mut conn, data, e.a, params)!
b := eval_as_value(mut conn, data, e.b, params)!
Expand Down Expand Up @@ -473,28 +455,6 @@ fn eval_trim(mut conn Connection, data Row, e TrimExpr, params map[string]Value)
return new_varchar_value(source.string_value().trim(character.string_value()))
}

fn eval_like(mut conn Connection, data Row, e LikeExpr, params map[string]Value) !Value {
left := eval_as_value(mut conn, data, e.left, params)!
right := eval_as_value(mut conn, data, e.right, params)!

// Make sure we escape any regexp characters.
escaped_regex := right.string_value().replace('+', '\\+').replace('?', '\\?').replace('*',
'\\*').replace('|', '\\|').replace('.', '\\.').replace('(', '\\(').replace(')',
'\\)').replace('[', '\\[').replace('{', '\\{').replace('_', '.').replace('%',
'.*')

mut re := regex.regex_opt('^${escaped_regex}$') or {
return error('cannot compile regexp: ^${escaped_regex}$: ${err}')
}
result := re.matches_string(left.string_value())

if e.not {
return new_boolean_value(!result)
}

return new_boolean_value(result)
}

fn eval_substring(mut conn Connection, data Row, e SubstringExpr, params map[string]Value) !Value {
value := eval_as_value(mut conn, data, e.value, params)!
from := int((eval_as_value(mut conn, data, e.from, params)!).as_int() - 1)
Expand Down Expand Up @@ -526,38 +486,6 @@ fn eval_substring(mut conn Connection, data Row, e SubstringExpr, params map[str
return new_varchar_value(value.string_value().substr(from, from + @for))
}

fn eval_binary_comparison(e BinaryExpr, left Value, right Value) !Value {
cmp := compare(left, right)!
if cmp == .is_unknown {
return new_unknown_value()
}

return new_boolean_value(match e.op {
'=' {
cmp == .is_equal
}
'<>' {
cmp != .is_equal
}
'>' {
cmp == .is_greater
}
'<' {
cmp == .is_less
}
'>=' {
cmp == .is_greater || cmp == .is_equal
}
'<=' {
cmp == .is_less || cmp == .is_equal
}
else {
// This should not be possible, but it's to satisfy the required else.
panic('invalid binary operator: ${e.op}')
}
})
}

fn cast_approximate(v Value, want SQLType) !Value {
if v.typ.typ == .is_numeric && v.typ.size == 0 {
match want {
Expand Down Expand Up @@ -589,12 +517,6 @@ fn eval_binary(mut conn Connection, data Row, e BinaryExpr, params map[string]Va
mut left := eval_as_value(mut conn, data, e.left, params)!
mut right := eval_as_value(mut conn, data, e.right, params)!

// Comparison predicates should rely on compare(), this will handle any
// implicit casting.
if e.op == '=' || e.op == '<>' || e.op == '>' || e.op == '<' || e.op == '>=' || e.op == '<=' {
return eval_binary_comparison(e, left, right)
}

// There is a special case we need to deal with when using literals against
// other approximate types.
//
Expand Down Expand Up @@ -630,52 +552,6 @@ fn eval_unary(mut conn Connection, data Row, e UnaryExpr, params map[string]Valu
return sqlstate_42883('operator does not exist: ${key}')
}

fn eval_between(mut conn Connection, data Row, e BetweenExpr, params map[string]Value) !Value {
expr := eval_as_value(mut conn, data, e.expr, params)!
mut left := eval_as_value(mut conn, data, e.left, params)!
mut right := eval_as_value(mut conn, data, e.right, params)!

// SYMMETRIC operands might need to be swapped.
cmp := compare(left, right)!
if e.symmetric && cmp == .is_greater {
left, right = right, left
}

lower := compare(expr, left)!
upper := compare(expr, right)!

if lower == .is_unknown || upper == .is_unknown {
return new_unknown_value()
}

mut result := (lower == .is_greater || lower == .is_equal)
&& (upper == .is_less || upper == .is_equal)

if e.not {
result = !result
}

return new_boolean_value(result)
}

fn eval_similar(mut conn Connection, data Row, e SimilarExpr, params map[string]Value) !Value {
left := eval_as_value(mut conn, data, e.left, params)!
right := eval_as_value(mut conn, data, e.right, params)!

regexp := '^${right.string_value().replace('.', '\\.').replace('_', '.').replace('%',
'.*')}$'
mut re := regex.regex_opt(regexp) or {
return error('cannot compile regexp: ${regexp}: ${err}')
}
result := re.matches_string(left.string_value())

if e.not {
return new_boolean_value(!result)
}

return new_boolean_value(result)
}

// eval_as_nullable_value is a broader version of eval_as_value that also takes
// the known destination type as so allows for untyped NULLs.
//
Expand Down
Loading

0 comments on commit 9c801c0

Please sign in to comment.