From 9c801c0053fea99576ff9b3a12a3962358360883 Mon Sep 17 00:00:00 2001 From: Elliot Chance Date: Wed, 25 Oct 2023 10:55:40 -0400 Subject: [PATCH] chore: Refactor (#167) As a longer term effort to remove ast.v and eval.v, this moves and its subtypes into their respective locations. This also fixes a bug where the Makefile will no regenerate the grammar.bnf. --- Makefile | 4 +- vsql/ast.v | 93 +--------------------- vsql/eval.v | 136 ++------------------------------ vsql/expr.v | 37 +-------- vsql/grammar.v | 93 +++++++++++++++++++--- vsql/planner.v | 18 +++-- vsql/std_between_predicate.v | 76 ++++++++++++++++-- vsql/std_comparison_predicate.v | 69 +++++++++++++++- vsql/std_like_predicate.v | 70 +++++++++++++--- vsql/std_null_predicate.v | 42 +++++++++- vsql/std_predicate.v | 77 ++++++++++++++++-- vsql/std_similar_predicate.v | 64 +++++++++++++-- 12 files changed, 470 insertions(+), 309 deletions(-) diff --git a/Makefile b/Makefile index baf04e4..d0ac21c 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/vsql/ast.v b/vsql/ast.v index 9f6f6ce..cc2bc22 100644 --- a/vsql/ast.v +++ b/vsql/ast.v @@ -23,8 +23,7 @@ type Stmt = AlterSequenceStmt | UpdateStmt // All possible expression entities. -type Expr = BetweenExpr - | BinaryExpr +type Expr = BinaryExpr | CallExpr | CastExpr | CoalesceExpr @@ -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 @@ -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 { @@ -96,9 +93,6 @@ fn (e Expr) pstr(params map[string]Value) string { Identifier { e.str() } - LikeExpr { - e.pstr(params) - } LocalTimeExpr { e.str() } @@ -111,9 +105,6 @@ fn (e Expr) pstr(params map[string]Value) string { NoExpr { e.str() } - NullExpr { - e.pstr(params) - } NullIfExpr { e.pstr(params) } @@ -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) } @@ -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 { @@ -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 @@ -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 @@ -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 @@ -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 diff --git a/vsql/eval.v b/vsql/eval.v index 07726a4..46946b2 100644 --- a/vsql/eval.v +++ b/vsql/eval.v @@ -2,8 +2,6 @@ module vsql -import regex - // A ExprOperation executes expressions for each row. struct ExprOperation { mut: @@ -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 { @@ -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) @@ -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) } @@ -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)! @@ -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) @@ -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 { @@ -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. // @@ -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. // diff --git a/vsql/expr.v b/vsql/expr.v index 648bd95..7da0e68 100644 --- a/vsql/expr.v +++ b/vsql/expr.v @@ -12,10 +12,8 @@ fn expr_is_agg(conn &Connection, e Expr, row Row, params map[string]Value) !bool return nested_agg_unsupported(e) } } - BetweenExpr { - if expr_is_agg(conn, e.left, row, params)! || expr_is_agg(conn, e.right, row, params)! { - return nested_agg_unsupported(e) - } + Predicate { + return e.is_agg(conn, row, params) } CallExpr { mut arg_types := []Type{} @@ -43,16 +41,6 @@ fn expr_is_agg(conn &Connection, e Expr, row Row, params map[string]Value) !bool UntypedNullExpr, NextValueExpr, CurrentCatalogExpr, CurrentSchemaExpr { return false } - LikeExpr { - if expr_is_agg(conn, e.left, row, params)! { - return nested_agg_unsupported(e) - } - } - NullExpr { - if expr_is_agg(conn, e.expr, row, params)! { - return nested_agg_unsupported(e) - } - } CoalesceExpr { for expr in e.exprs { if expr_is_agg(conn, expr, row, params)! { @@ -70,11 +58,6 @@ fn expr_is_agg(conn &Connection, e Expr, row Row, params map[string]Value) !bool return nested_agg_unsupported(e) } } - SimilarExpr { - if expr_is_agg(conn, e.left, row, params)! { - return nested_agg_unsupported(e) - } - } SubstringExpr { if expr_is_agg(conn, e.value, row, params)! || expr_is_agg(conn, e.from, row, params)! || expr_is_agg(conn, e.@for, row, params)! { @@ -124,9 +107,8 @@ fn resolve_identifiers(conn &Connection, e Expr, tables map[string]Table) !Expr return BinaryExpr{resolve_identifiers(conn, e.left, tables)!, e.op, resolve_identifiers(conn, e.right, tables)!} } - BetweenExpr { - return BetweenExpr{e.not, e.symmetric, resolve_identifiers(conn, e.expr, tables)!, resolve_identifiers(conn, - e.left, tables)!, resolve_identifiers(conn, e.right, tables)!} + Predicate { + return e.resolve_identifiers(conn, tables) } CallExpr { return CallExpr{e.function_name, resolve_identifiers_exprs(conn, e.args, tables)!} @@ -148,13 +130,6 @@ fn resolve_identifiers(conn &Connection, e Expr, tables map[string]Table) !Expr // TODO(elliotchance): Need tests for table qualifier not existing. return conn.resolve_identifier(e) } - LikeExpr { - return LikeExpr{resolve_identifiers(conn, e.left, tables)!, resolve_identifiers(conn, - e.right, tables)!, e.not} - } - NullExpr { - return NullExpr{resolve_identifiers(conn, e.expr, tables)!, e.not} - } NullIfExpr { return NullIfExpr{resolve_identifiers(conn, e.a, tables)!, resolve_identifiers(conn, e.b, tables)!} @@ -168,10 +143,6 @@ fn resolve_identifiers(conn &Connection, e Expr, tables map[string]Table) !Expr CoalesceExpr { return CoalesceExpr{e.exprs.map(resolve_identifiers(conn, it, tables)!)} } - SimilarExpr { - return SimilarExpr{resolve_identifiers(conn, e.left, tables)!, resolve_identifiers(conn, - e.right, tables)!, e.not} - } SubstringExpr { return SubstringExpr{resolve_identifiers(conn, e.value, tables)!, resolve_identifiers(conn, e.from, tables)!, resolve_identifiers(conn, e.@for, tables)!, e.using} diff --git a/vsql/grammar.v b/vsql/grammar.v index d6c2199..b5ea7bf 100644 --- a/vsql/grammar.v +++ b/vsql/grammar.v @@ -4,7 +4,9 @@ module vsql -type EarleyValue = BetweenExpr +type EarleyValue = BetweenPredicate + | CharacterLikePredicate + | ComparisonPredicate | ComparisonPredicatePart2 | Correlation | CreateTableStmt @@ -13,7 +15,7 @@ type EarleyValue = BetweenExpr | Identifier | IdentifierChain | InsertStmt - | LikeExpr + | NullPredicate | QualifiedAsteriskExpr | QualifiedJoin | QueryExpression @@ -24,7 +26,7 @@ type EarleyValue = BetweenExpr | SequenceGeneratorOption | SequenceGeneratorRestartOption | SequenceGeneratorStartWithOption - | SimilarExpr + | SimilarPredicate | SimpleTable | SortSpecification | Stmt @@ -1088,6 +1090,21 @@ fn get_grammar() map[string]EarleyRule { mut rule_predefined_type_ := &EarleyRule{ name: '' } + mut rule_predicate_1_ := &EarleyRule{ + name: '' + } + mut rule_predicate_2_ := &EarleyRule{ + name: '' + } + mut rule_predicate_3_ := &EarleyRule{ + name: '' + } + mut rule_predicate_4_ := &EarleyRule{ + name: '' + } + mut rule_predicate_5_ := &EarleyRule{ + name: '' + } mut rule_predicate_ := &EarleyRule{ name: '' } @@ -7965,32 +7982,62 @@ fn get_grammar() map[string]EarleyRule { }, ]} - rule_predicate_.productions << &EarleyProduction{[ + rule_predicate_1_.productions << &EarleyProduction{[ &EarleyRuleOrString{ rule: rule_comparison_predicate_ }, ]} - rule_predicate_.productions << &EarleyProduction{[ + + rule_predicate_2_.productions << &EarleyProduction{[ &EarleyRuleOrString{ rule: rule_between_predicate_ }, ]} - rule_predicate_.productions << &EarleyProduction{[ + + rule_predicate_3_.productions << &EarleyProduction{[ &EarleyRuleOrString{ rule: rule_like_predicate_ }, ]} - rule_predicate_.productions << &EarleyProduction{[ + + rule_predicate_4_.productions << &EarleyProduction{[ &EarleyRuleOrString{ rule: rule_similar_predicate_ }, ]} - rule_predicate_.productions << &EarleyProduction{[ + + rule_predicate_5_.productions << &EarleyProduction{[ &EarleyRuleOrString{ rule: rule_null_predicate_ }, ]} + rule_predicate_.productions << &EarleyProduction{[ + &EarleyRuleOrString{ + rule: rule_predicate_1_ + }, + ]} + rule_predicate_.productions << &EarleyProduction{[ + &EarleyRuleOrString{ + rule: rule_predicate_2_ + }, + ]} + rule_predicate_.productions << &EarleyProduction{[ + &EarleyRuleOrString{ + rule: rule_predicate_3_ + }, + ]} + rule_predicate_.productions << &EarleyProduction{[ + &EarleyRuleOrString{ + rule: rule_predicate_4_ + }, + ]} + rule_predicate_.productions << &EarleyProduction{[ + &EarleyRuleOrString{ + rule: rule_predicate_5_ + }, + ]} + rule_preparable_sql_data_statement_.productions << &EarleyProduction{[ &EarleyRuleOrString{ rule: rule_delete_statement_searched_ @@ -13356,6 +13403,11 @@ fn get_grammar() map[string]EarleyRule { rules[''] = rule_power_function_ rules[''] = rule_precision_ rules[''] = rule_predefined_type_ + rules[''] = rule_predicate_1_ + rules[''] = rule_predicate_2_ + rules[''] = rule_predicate_3_ + rules[''] = rule_predicate_4_ + rules[''] = rule_predicate_5_ rules[''] = rule_predicate_ rules[''] = rule_preparable_sql_data_statement_ rules[''] = rule_preparable_sql_schema_statement_ @@ -14121,7 +14173,7 @@ fn parse_ast_name(children []EarleyValue, name string) ![]EarleyValue { } '' { return [ - EarleyValue(parse_between(children[0] as Expr, children[1] as BetweenExpr)!), + EarleyValue(parse_between(children[0] as Expr, children[1] as BetweenPredicate)!), ] } '' { @@ -14193,7 +14245,7 @@ fn parse_ast_name(children []EarleyValue, name string) ![]EarleyValue { } '' { return [ - EarleyValue(parse_like_pred(children[0] as Expr, children[1] as LikeExpr)!), + EarleyValue(parse_like_pred(children[0] as Expr, children[1] as CharacterLikePredicate)!), ] } '' { @@ -14665,6 +14717,25 @@ fn parse_ast_name(children []EarleyValue, name string) ![]EarleyValue { EarleyValue(parse_power(children[2] as Expr, children[4] as Expr)!), ] } + '' { + return [ + EarleyValue(parse_predicate_1(children[0] as ComparisonPredicate)!), + ] + } + '' { + return [EarleyValue(parse_predicate_2(children[0] as BetweenPredicate)!)] + } + '' { + return [ + EarleyValue(parse_predicate_3(children[0] as CharacterLikePredicate)!), + ] + } + '' { + return [EarleyValue(parse_predicate_4(children[0] as SimilarPredicate)!)] + } + '' { + return [EarleyValue(parse_predicate_5(children[0] as NullPredicate)!)] + } '' { return [ EarleyValue(parse_qualified_asterisk(children[0] as IdentifierChain, children[2] as string)!), @@ -14880,7 +14951,7 @@ fn parse_ast_name(children []EarleyValue, name string) ![]EarleyValue { } '' { return [ - EarleyValue(parse_similar_pred(children[0] as Expr, children[1] as SimilarExpr)!), + EarleyValue(parse_similar_pred(children[0] as Expr, children[1] as SimilarPredicate)!), ] } '' { diff --git a/vsql/planner.v b/vsql/planner.v index a5dc04d..f64b38a 100644 --- a/vsql/planner.v +++ b/vsql/planner.v @@ -112,14 +112,16 @@ fn create_select_plan_without_join(body SelectStmt, from_clause TablePrimary, of table = catalog.storage.tables[table_name_id] // This is a special case to handle "PRIMARY KEY = INTEGER". - if table.primary_key.len > 0 && where is BinaryExpr { - left := where.left - right := where.right - if where.op == '=' && left is Identifier { - if left.sub_entity_name == table.primary_key[0] { - covered_by_pk = true - plan.operations << new_primary_key_operation(table, right, - right, params, c) + if table.primary_key.len > 0 && where is Predicate { + if where is ComparisonPredicate { + left := where.left + right := where.right + if where.op == '=' && left is Identifier { + if left.sub_entity_name == table.primary_key[0] { + covered_by_pk = true + plan.operations << new_primary_key_operation(table, right, + right, params, c) + } } } } diff --git a/vsql/std_between_predicate.v b/vsql/std_between_predicate.v index a20e50d..d0c726c 100644 --- a/vsql/std_between_predicate.v +++ b/vsql/std_between_predicate.v @@ -4,10 +4,10 @@ module vsql // Format //~ -//~ /* Expr */ ::= +//~ /* BetweenPredicate */ ::= //~ -> between //~ -//~ /* BetweenExpr */ ::= +//~ /* BetweenPredicate */ ::= //~ //~ AND -> between1 //~ | @@ -23,8 +23,70 @@ module vsql //~ SYMMETRIC -> yes //~ | ASYMMETRIC -> no -fn parse_between(expr Expr, between BetweenExpr) !Expr { - return BetweenExpr{ +struct BetweenPredicate { + not bool + symmetric bool + expr Expr + left Expr + right Expr +} + +fn (e BetweenPredicate) 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)}' +} + +fn (e BetweenPredicate) eval(mut conn Connection, data Row, 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 (e BetweenPredicate) is_agg(conn &Connection, row Row, params map[string]Value) !bool { + if expr_is_agg(conn, e.left, row, params)! || expr_is_agg(conn, e.right, row, params)! { + return nested_agg_unsupported(Predicate(e)) + } + + return false +} + +fn (e BetweenPredicate) resolve_identifiers(conn &Connection, tables map[string]Table) !Expr { + return Predicate(BetweenPredicate{e.not, e.symmetric, resolve_identifiers(conn, e.expr, + tables)!, resolve_identifiers(conn, e.left, tables)!, resolve_identifiers(conn, + e.right, tables)!}) +} + +fn parse_between(expr Expr, between BetweenPredicate) !BetweenPredicate { + return BetweenPredicate{ not: between.not symmetric: between.symmetric expr: expr @@ -33,13 +95,13 @@ fn parse_between(expr Expr, between BetweenExpr) !Expr { } } -fn parse_between1(is_true bool, left Expr, right Expr) !BetweenExpr { +fn parse_between1(is_true bool, left Expr, right Expr) !BetweenPredicate { // false between ASYMMETRIC by default. return parse_between2(is_true, false, left, right) } -fn parse_between2(is_true bool, symmetric bool, left Expr, right Expr) !BetweenExpr { - return BetweenExpr{ +fn parse_between2(is_true bool, symmetric bool, left Expr, right Expr) !BetweenPredicate { + return BetweenPredicate{ not: !is_true symmetric: symmetric left: left diff --git a/vsql/std_comparison_predicate.v b/vsql/std_comparison_predicate.v index 056d067..bc498db 100644 --- a/vsql/std_comparison_predicate.v +++ b/vsql/std_comparison_predicate.v @@ -4,7 +4,7 @@ module vsql // Format //~ -//~ /* Expr */ ::= +//~ /* ComparisonPredicate */ ::= //~ -> comparison //~ //~ /* ComparisonPredicatePart2 */ ::= @@ -18,10 +18,73 @@ module vsql //~ | //~ | +struct ComparisonPredicate { + left Expr + op string + right Expr +} + +fn (e ComparisonPredicate) pstr(params map[string]Value) string { + return '${e.left.pstr(params)} ${e.op} ${e.right.pstr(params)}' +} + +fn (e ComparisonPredicate) eval(mut conn Connection, data Row, params map[string]Value) !Value { + mut left := eval_as_value(mut conn, data, e.left, params)! + mut right := eval_as_value(mut conn, data, e.right, params)! + + 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 (e ComparisonPredicate) is_agg(conn &Connection, row Row, params map[string]Value) !bool { + if expr_is_agg(conn, e.left, row, params)! || expr_is_agg(conn, e.right, row, params)! { + return nested_agg_unsupported(Predicate(e)) + } + + return false +} + +fn (e ComparisonPredicate) resolve_identifiers(conn &Connection, tables map[string]Table) !Expr { + return Predicate(ComparisonPredicate{resolve_identifiers(conn, e.left, tables)!, e.op, resolve_identifiers(conn, + e.right, tables)!}) +} + +struct ComparisonPredicatePart2 { + op string + expr Expr +} + fn parse_comparison_part(op string, expr Expr) !ComparisonPredicatePart2 { return ComparisonPredicatePart2{op, expr} } -fn parse_comparison(expr Expr, comp ComparisonPredicatePart2) !Expr { - return BinaryExpr{expr, comp.op, comp.expr} +fn parse_comparison(expr Expr, comp ComparisonPredicatePart2) !ComparisonPredicate { + return ComparisonPredicate{expr, comp.op, comp.expr} } diff --git a/vsql/std_like_predicate.v b/vsql/std_like_predicate.v index 037ecc3..069fa97 100644 --- a/vsql/std_like_predicate.v +++ b/vsql/std_like_predicate.v @@ -2,29 +2,81 @@ module vsql +import regex + // Format //~ -//~ /* Expr */ ::= +//~ /* CharacterLikePredicate */ ::= //~ //~ -//~ /* Expr */ ::= +//~ /* CharacterLikePredicate */ ::= //~ -> like_pred //~ -//~ /* LikeExpr */ ::= +//~ /* CharacterLikePredicate */ ::= //~ LIKE -> like //~ | NOT LIKE -> not_like //~ //~ /* Expr */ ::= //~ -fn parse_like_pred(left Expr, like LikeExpr) !Expr { - return LikeExpr{left, like.right, like.not} +// CharacterLikePredicate for "LIKE" and "NOT LIKE". +struct CharacterLikePredicate { + left Expr + right Expr + not bool +} + +fn (e CharacterLikePredicate) 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)}' +} + +fn (e CharacterLikePredicate) eval(mut conn Connection, data Row, 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 (e CharacterLikePredicate) is_agg(conn &Connection, row Row, params map[string]Value) !bool { + if expr_is_agg(conn, e.left, row, params)! { + return nested_agg_unsupported(Predicate(e)) + } + + return false +} + +fn (e CharacterLikePredicate) resolve_identifiers(conn &Connection, tables map[string]Table) !Expr { + return Predicate(CharacterLikePredicate{resolve_identifiers(conn, e.left, tables)!, resolve_identifiers(conn, + e.right, tables)!, e.not}) +} + +fn parse_like_pred(left Expr, like CharacterLikePredicate) !CharacterLikePredicate { + return CharacterLikePredicate{left, like.right, like.not} } -fn parse_like(expr Expr) !LikeExpr { - return LikeExpr{NoExpr{}, expr, false} +fn parse_like(expr Expr) !CharacterLikePredicate { + return CharacterLikePredicate{NoExpr{}, expr, false} } -fn parse_not_like(expr Expr) !LikeExpr { - return LikeExpr{NoExpr{}, expr, true} +fn parse_not_like(expr Expr) !CharacterLikePredicate { + return CharacterLikePredicate{NoExpr{}, expr, true} } diff --git a/vsql/std_null_predicate.v b/vsql/std_null_predicate.v index 8300938..4b3d399 100644 --- a/vsql/std_null_predicate.v +++ b/vsql/std_null_predicate.v @@ -4,13 +4,49 @@ module vsql // Format //~ -//~ /* Expr */ ::= +//~ /* NullPredicate */ ::= //~ -> null_predicate //~ //~ /* bool */ ::= //~ IS NULL -> yes //~ | IS NOT NULL -> no -fn parse_null_predicate(expr Expr, is_null bool) !Expr { - return NullExpr{expr, !is_null} +// NullPredicate for "IS NULL" and "IS NOT NULL". +struct NullPredicate { + expr Expr + not bool +} + +fn (e NullPredicate) pstr(params map[string]Value) string { + if e.not { + return '${e.expr.pstr(params)} IS NOT NULL' + } + + return '${e.expr.pstr(params)} IS NULL' +} + +fn (e NullPredicate) eval(mut conn Connection, data Row, 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 (e NullPredicate) is_agg(conn &Connection, row Row, params map[string]Value) !bool { + if expr_is_agg(conn, e.expr, row, params)! { + return nested_agg_unsupported(Predicate(e)) + } + + return false +} + +fn (e NullPredicate) resolve_identifiers(conn &Connection, tables map[string]Table) !Expr { + return Predicate(NullPredicate{resolve_identifiers(conn, e.expr, tables)!, e.not}) +} + +fn parse_null_predicate(expr Expr, is_null bool) !NullPredicate { + return NullPredicate{expr, !is_null} } diff --git a/vsql/std_predicate.v b/vsql/std_predicate.v index 0afbc40..3973d4e 100644 --- a/vsql/std_predicate.v +++ b/vsql/std_predicate.v @@ -5,8 +5,75 @@ module vsql // Format //~ //~ /* Expr */ ::= -//~ -//~ | -//~ | -//~ | -//~ | +//~ -> predicate_1 +//~ | -> predicate_2 +//~ | -> predicate_3 +//~ | -> predicate_4 +//~ | -> predicate_5 + +type Predicate = BetweenPredicate + | CharacterLikePredicate + | ComparisonPredicate + | NullPredicate + | SimilarPredicate + +fn (e Predicate) pstr(params map[string]Value) string { + return match e { + ComparisonPredicate, BetweenPredicate, CharacterLikePredicate, SimilarPredicate, + NullPredicate { + e.pstr(params) + } + } +} + +fn (e Predicate) eval(mut conn Connection, data Row, params map[string]Value) !Value { + return match e { + ComparisonPredicate, BetweenPredicate, CharacterLikePredicate, SimilarPredicate, + NullPredicate { + e.eval(mut conn, data, params)! + } + } +} + +fn (e Predicate) eval_type(conn &Connection, data Row, params map[string]Value) !Type { + // The result of a predicate is always a BOOLEAN. + return new_type('BOOLEAN', 0, 0) +} + +fn (e Predicate) is_agg(conn &Connection, row Row, params map[string]Value) !bool { + return match e { + ComparisonPredicate, BetweenPredicate, CharacterLikePredicate, SimilarPredicate, + NullPredicate { + e.is_agg(conn, row, params)! + } + } +} + +fn (e Predicate) resolve_identifiers(conn &Connection, tables map[string]Table) !Expr { + return match e { + ComparisonPredicate, BetweenPredicate, CharacterLikePredicate, SimilarPredicate, + NullPredicate { + e.resolve_identifiers(conn, tables)! + } + } +} + +fn parse_predicate_1(predicate ComparisonPredicate) !Expr { + return Predicate(predicate) +} + +fn parse_predicate_2(predicate BetweenPredicate) !Expr { + return Predicate(predicate) +} + +fn parse_predicate_3(predicate CharacterLikePredicate) !Expr { + return Predicate(predicate) +} + +fn parse_predicate_4(predicate SimilarPredicate) !Expr { + return Predicate(predicate) +} + +fn parse_predicate_5(predicate NullPredicate) !Expr { + return Predicate(predicate) +} diff --git a/vsql/std_similar_predicate.v b/vsql/std_similar_predicate.v index 6115ec7..e56c396 100644 --- a/vsql/std_similar_predicate.v +++ b/vsql/std_similar_predicate.v @@ -2,26 +2,74 @@ module vsql +import regex + // Format //~ -//~ /* Expr */ ::= +//~ /* SimilarPredicate */ ::= //~ -> similar_pred //~ -//~ /* SimilarExpr */ ::= +//~ /* SimilarPredicate */ ::= //~ SIMILAR TO -> similar //~ | NOT SIMILAR TO -> not_similar //~ //~ /* Expr */ ::= //~ -fn parse_similar_pred(left Expr, like SimilarExpr) !Expr { - return SimilarExpr{left, like.right, like.not} +// SimilarPredicate for "SIMILAR TO" and "NOT SIMILAR TO". +struct SimilarPredicate { + left Expr + right Expr + not bool +} + +fn (e SimilarPredicate) 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)}' +} + +fn (e SimilarPredicate) eval(mut conn Connection, data Row, 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) +} + +fn (e SimilarPredicate) is_agg(conn &Connection, row Row, params map[string]Value) !bool { + if expr_is_agg(conn, e.left, row, params)! { + return nested_agg_unsupported(Predicate(e)) + } + + return false +} + +fn (e SimilarPredicate) resolve_identifiers(conn &Connection, tables map[string]Table) !Expr { + return Predicate(SimilarPredicate{resolve_identifiers(conn, e.left, tables)!, resolve_identifiers(conn, + e.right, tables)!, e.not}) +} + +fn parse_similar_pred(left Expr, like SimilarPredicate) !SimilarPredicate { + return SimilarPredicate{left, like.right, like.not} } -fn parse_similar(expr Expr) !SimilarExpr { - return SimilarExpr{NoExpr{}, expr, false} +fn parse_similar(expr Expr) !SimilarPredicate { + return SimilarPredicate{NoExpr{}, expr, false} } -fn parse_not_similar(expr Expr) !SimilarExpr { - return SimilarExpr{NoExpr{}, expr, true} +fn parse_not_similar(expr Expr) !SimilarPredicate { + return SimilarPredicate{NoExpr{}, expr, true} }