diff --git a/lib/sql.ml b/lib/sql.ml index 1175cde1..540af6ae 100644 --- a/lib/sql.ml +++ b/lib/sql.ml @@ -656,7 +656,7 @@ type col_name = { tname : table_name option; } [@@deriving show] type logical_op = And | Or | Xor [@@deriving show] -type comparison_op = Comp_equal | Comp_num_cmp | Comp_num_eq | Not_distinct_op | Is_null | Is_not_null [@@deriving eq, show] +type comparison_op = Comp_equal | Comp_num_cmp | Comp_text_cmp | Comp_num_eq | Not_distinct_op | Is_null | Is_not_null [@@deriving eq, show] type null_handling_fn_kind = Coalesce of Type.tyvar * Type.tyvar | Null_if | If_null [@@deriving show] type source_alias = { table_name : table_name; column_aliases : schema option } [@@deriving show] type select_row_locking_kind = For_update | For_share [@@deriving show] @@ -1176,7 +1176,10 @@ let () = "uuid_short" |> monomorphic int []; "is_uuid" |> monomorphic bool [text]; "makedate" |> monomorphic datetime [int; int]; - (* + "similarity" |> monomorphic float [text; text]; + "word_similarity" |> monomorphic float [text; text]; + "strict_word_similarity" |> monomorphic float [text; text]; + (* Any is used instead of Var because MySQL JSON functions have unique semantics: 1. ACCEPT ANY DATA TYPE: MySQL JSON functions accept values of any type diff --git a/lib/sql_lexer.mll b/lib/sql_lexer.mll index a570ec6a..654ffe37 100644 --- a/lib/sql_lexer.mll +++ b/lib/sql_lexer.mll @@ -282,8 +282,8 @@ let cmnt = "--" | "//" | "#" rule ruleStatement = parse | ['\n' ' ' '\r' '\t']+ as tok { `Space tok } | cmnt wsp* "[sqlgg]" wsp+ (ident+ as n) wsp* "=" wsp* ([^'\n']* as v) '\n' { `Props [(n, String.trim v)] } - | cmnt wsp* "@" (ident+ as name) wsp* "|" ([^'\n']* as v) '\n' - { + | cmnt wsp* "@" (ident+ as name) wsp* "|" ([^'\n']* as v) '\n' + { let props = rulePropList [] (Lexing.from_string v) in let all_props = ("name", name) :: props in `Props all_props @@ -302,8 +302,8 @@ rule ruleStatement = parse (* Parse a list of key:value properties *) and rulePropList acc = parse - | wsp* (ident+ as prop) wsp* ':' wsp* ([^',' '\n']+ as value) wsp* - { + | wsp* (ident+ as prop) wsp* ':' wsp* ([^',' '\n']+ as value) wsp* + { let new_acc = (String.trim prop, String.trim value) :: acc in rulePropListNext new_acc lexbuf } @@ -312,7 +312,7 @@ rulePropList acc = parse | _ { error lexbuf "rulePropList" } (* Parse continuation after comma *) and -rulePropListNext acc = parse +rulePropListNext acc = parse | ',' { rulePropList acc lexbuf } | wsp* '\n' { List.rev acc } | wsp* eof { List.rev acc } @@ -346,13 +346,15 @@ ruleMain = parse | "+" { PLUS } | "-" { MINUS } - | "/" | "%" { NUM_DIV_OP } + | "/" | "%" { NUM_DIV_OP } (* FIXME: in PostgreSQL, "%" is both int modulo and a trigram comparison operator *) | "<<" | ">>" { NUM_BIT_SHIFT } | "|" { NUM_BIT_OR } | "&" { NUM_BIT_AND } | ">" | ">=" | "<=" | "<" { NUM_CMP_OP } | "<>" | "!=" | "==" { NUM_EQ_OP } | "<=>" { NOT_DISTINCT_OP } + | "<%" | "%>" | "<<%" | "%>>" { TEXT_CMP_OP } + | "<->" | "<<->" | "<->>" | "<<<->" | "<->>>" { TEXT_DIST_OP } | "->" { JSON_EXTRACT_OP } | "->>" { JSON_UNQUOTE_EXTRACT_OP } diff --git a/lib/sql_parser.mly b/lib/sql_parser.mly index 2f2b5839..e3ab6bc9 100644 --- a/lib/sql_parser.mly +++ b/lib/sql_parser.mly @@ -53,6 +53,7 @@ %token GROUP_CONCAT SEPARATOR %token JSON_ARRAYAGG %token NUM_DIV_OP NUM_EQ_OP NUM_CMP_OP PLUS MINUS NOT_DISTINCT_OP NUM_BIT_SHIFT NUM_BIT_OR NUM_BIT_AND +%token TEXT_CMP_OP TEXT_DIST_OP %token JSON_EXTRACT_OP JSON_UNQUOTE_EXTRACT_OP %token T_INTEGER %token T_TEXT T_TINYTEXT T_MEDIUMTEXT T_LONGTEXT T_CHAR T_VARCHAR T_VARCHAR2 @@ -73,7 +74,7 @@ %nonassoc NOT %nonassoc BETWEEN CASE (* WHEN THEN ELSE *) (* never useful *) %nonassoc EQUAL NUM_EQ_OP NOT_DISTINCT_OP IS LIKE LIKE_OP IN -%nonassoc NUM_CMP_OP +%nonassoc NUM_CMP_OP TEXT_CMP_OP TEXT_DIST_OP %left NUM_BIT_OR %left NUM_BIT_AND %left NUM_BIT_SHIFT @@ -495,6 +496,7 @@ expr: e1=expr numeric_bin_op e2=expr %prec PLUS { Fun { fn_name = "numeric_bin_op"; kind = (Ret (Source_type.depends Any)); parameters = [e1;e2]; is_over_clause = false } } (* TODO default Int *) | MOD LPAREN e1=expr COMMA e2=expr RPAREN { Fun { fn_name = "mod"; kind = (Ret (Source_type.depends Any)); parameters = [e1;e2]; is_over_clause = false } } (* mysql special *) | e1=expr NUM_DIV_OP e2=expr %prec PLUS { Fun { fn_name = "num_div"; kind = (Ret (Source_type.depends Float)); parameters = [e1;e2]; is_over_clause = false } } + | e1=expr TEXT_DIST_OP e2=expr { Fun { fn_name = "text_dist"; kind = (Ret (Source_type.depends Float)); parameters = [e1;e2]; is_over_clause = false } } | e1=expr DIV e2=expr %prec PLUS { Fun { fn_name = "div"; kind = (Ret (Source_type.depends Int)); parameters = [e1;e2]; is_over_clause = false } } | e1=expr bool_op=boolean_bin_op e2=expr %prec AND { Fun { fn_name = "boolean_bin_op"; kind = (Logical bool_op); parameters = [e1;e2]; is_over_clause = false } } | e1=expr comp_op=comparison_op anyall? e2=expr %prec EQUAL { Fun { fn_name = "comparison"; kind = Comparison comp_op; parameters = [e1; e2]; is_over_clause = false } } @@ -641,6 +643,7 @@ numeric_bin_op: PLUS | MINUS | ASTERISK | MOD | NUM_BIT_OR | NUM_BIT_AND | NUM_B comparison_op: | EQUAL { Comp_equal } | NUM_CMP_OP { Comp_num_cmp } + | TEXT_CMP_OP { Comp_text_cmp } | NOT_DISTINCT_OP { Not_distinct_op } | NUM_EQ_OP { (* it would be nice to go into num_eq_op,