-- SiftQL errors: precise 1-based character positions, syntax vs limit codes CREATE EXTENSION pg_sift; \set VERBOSITY terse SELECT sift.parse_query('a OR (b'); ERROR: sift query: unclosed "(" at position 6 SELECT sift.parse_query(repeat('a ', 257)); ERROR: sift query: more than 256 terms at position 513 CREATE FUNCTION perr(q text) RETURNS text LANGUAGE plpgsql AS $$ BEGIN RETURN 'ok: ' || sift.parse_query(q); EXCEPTION WHEN syntax_error OR program_limit_exceeded THEN RETURN sqlstate || ': ' || sqlerrm; END $$; -- syntax errors SELECT q, perr(q) FROM (VALUES (''), (' '), (E'\t\n'), ('a OR'), ('OR a'), ('AND a'), ('a AND'), ('a OR OR b'), ('a AND AND b'), ('a AND OR b'), ('NOT'), ('a NOT'), ('a NOT NOT'), ('NOT a'), ('NOT a NOT b'), ('a OR NOT b'), ('(NOT a) OR b'), ('x (NOT b NOT c)^2'), ('NOT (a b)'), ('(a'), ('a)'), ('a))'), ('()'), ('(a OR)'), ('{}'), ('{a'), ('a}'), ('{a "b"}'), ('{a (b)}'), ('{a OR b}'), ('{a NOT b}'), ('{a^2}'), ('{{a}}'), ('"abc'), ('a "b'), ('/abc'), ('//'), ('a\'), ('"a\'), ('/a\/'), ('a ^2'), ('^2'), ('a^'), ('a^x'), ('a^2x'), ('a^-1'), ('a^+1'), ('a^2^3'), ('a^1e'), ('a^.'), ('a^1..2'), ('a^2~'), ('"a"^'), ('~a'), ('a ~1'), ('a~3'), ('a~0'), ('a~12'), ('a~x'), ('a~~'), ('"a"~2'), ('(a)~1'), ('/a/~'), ('a*~'), ('a?~2'), ('*'), ('?'), ('*?*'), ('a OR *'), ('/a(/'), ('/a)|(b/'), ('/[a/'), ('/a{2,1}/'), ('/(?q)a/'), ('/***=a/'), ('/\/'), ('x /a(/^2') ) v(q); q | perr -------------------+---------------------------------------------------------------------------------------------------------- | 42601: sift query: empty query at position 1 | 42601: sift query: empty query at position 4 +| 42601: sift query: empty query at position 3 | a OR | 42601: sift query: expected a term after OR, found end of query at position 5 OR a | 42601: sift query: unexpected OR at position 1 AND a | 42601: sift query: unexpected AND at position 1 a AND | 42601: sift query: expected a term after AND, found end of query at position 6 a OR OR b | 42601: sift query: expected a term after OR, found OR at position 6 a AND AND b | 42601: sift query: expected a term after AND, found AND at position 7 a AND OR b | 42601: sift query: expected a term after AND, found OR at position 7 NOT | 42601: sift query: expected a term after NOT, found end of query at position 4 a NOT | 42601: sift query: expected a term after NOT, found end of query at position 6 a NOT NOT | 42601: sift query: expected a term after NOT, found end of query at position 10 NOT a | 42601: sift query: NOT needs a positive term in the same AND group at position 1 NOT a NOT b | 42601: sift query: NOT needs a positive term in the same AND group at position 1 a OR NOT b | 42601: sift query: NOT needs a positive term in the same AND group at position 6 (NOT a) OR b | 42601: sift query: NOT needs a positive term in the same AND group at position 2 x (NOT b NOT c)^2 | 42601: sift query: NOT needs a positive term in the same AND group at position 4 NOT (a b) | 42601: sift query: NOT needs a positive term in the same AND group at position 1 (a | 42601: sift query: unclosed "(" at position 1 a) | 42601: sift query: unexpected ")" at position 2 a)) | 42601: sift query: unexpected ")" at position 2 () | 42601: sift query: empty group at position 1 (a OR) | 42601: sift query: expected a term after OR, found ")" at position 6 {} | 42601: sift query: empty { } at position 1 {a | 42601: sift query: unclosed "{" at position 1 a} | 42601: sift query: unexpected "}" at position 2 {a "b"} | 42601: sift query: { } may only hold words, patterns and regular expressions, found phrase at position 4 {a (b)} | 42601: sift query: { } may only hold words, patterns and regular expressions, found "(" at position 4 {a OR b} | 42601: sift query: { } may only hold words, patterns and regular expressions, found OR at position 4 {a NOT b} | 42601: sift query: { } may only hold words, patterns and regular expressions, found NOT at position 4 {a^2} | 42601: sift query: { } may only hold words, patterns and regular expressions, found "^" at position 3 {{a}} | 42601: sift query: { } may only hold words, patterns and regular expressions, found "{" at position 2 "abc | 42601: sift query: unterminated phrase at position 1 a "b | 42601: sift query: unterminated phrase at position 3 /abc | 42601: sift query: unterminated regular expression at position 1 // | 42601: sift query: empty regular expression at position 1 a\ | 42601: sift query: escape character at end of query at position 2 "a\ | 42601: sift query: unterminated phrase at position 1 /a\/ | 42601: sift query: unterminated regular expression at position 1 a ^2 | 42601: sift query: "^" must directly follow a term or group at position 3 ^2 | 42601: sift query: unexpected "^" at position 1 a^ | 42601: sift query: boost must be a non-negative number at position 3 a^x | 42601: sift query: boost must be a non-negative number at position 3 a^2x | 42601: sift query: boost must be a non-negative number at position 3 a^-1 | 42601: sift query: boost must be a non-negative number at position 3 a^+1 | 42601: sift query: boost must be a non-negative number at position 3 a^2^3 | 42601: sift query: boost must be a non-negative number at position 3 a^1e | 42601: sift query: boost must be a non-negative number at position 3 a^. | 42601: sift query: boost must be a non-negative number at position 3 a^1..2 | 42601: sift query: boost must be a non-negative number at position 3 a^2~ | 42601: sift query: boost must be a non-negative number at position 3 "a"^ | 42601: sift query: boost must be a non-negative number at position 5 ~a | 42601: sift query: "~" must directly follow a word at position 1 a ~1 | 42601: sift query: "~" must directly follow a word at position 3 a~3 | 42601: sift query: fuzzy distance must be 1 or 2 at position 2 a~0 | 42601: sift query: fuzzy distance must be 1 or 2 at position 2 a~12 | 42601: sift query: fuzzy distance must be 1 or 2 at position 2 a~x | 42601: sift query: unexpected character after "~" at position 3 a~~ | 42601: sift query: unexpected character after "~" at position 3 "a"~2 | 42601: sift query: "~" must directly follow a word at position 4 (a)~1 | 42601: sift query: "~" must directly follow a word at position 4 /a/~ | 42601: sift query: "~" must directly follow a word at position 4 a*~ | 42601: sift query: fuzzy matching does not apply to wildcard patterns at position 3 a?~2 | 42601: sift query: fuzzy matching does not apply to wildcard patterns at position 3 * | 42601: sift query: wildcard pattern needs a literal character at position 1 ? | 42601: sift query: wildcard pattern needs a literal character at position 1 *?* | 42601: sift query: wildcard pattern needs a literal character at position 1 a OR * | 42601: sift query: wildcard pattern needs a literal character at position 6 /a(/ | 42601: sift query: invalid regular expression: parentheses () not balanced at position 1 /a)|(b/ | 42601: sift query: invalid regular expression: parentheses () not balanced at position 1 /[a/ | 42601: sift query: invalid regular expression: brackets [] not balanced at position 1 /a{2,1}/ | 42601: sift query: invalid regular expression: invalid repetition count(s) at position 1 /(?q)a/ | 42601: sift query: invalid regular expression: quantifier operand invalid at position 1 /***=a/ | 42601: sift query: invalid regular expression: quantifier operand invalid at position 1 /\/ | 42601: sift query: unterminated regular expression at position 1 x /a(/^2 | 42601: sift query: invalid regular expression: parentheses () not balanced at position 3 (77 rows) -- positions count characters, not bytes SELECT q, perr(q) FROM (VALUES ('ünïcödé "ok'), ('жук OR'), ('日本 語 )'), ('ü^x'), ('éé~5') ) v(q); q | perr -------------+------------------------------------------------------------------------------- ünïcödé "ok | 42601: sift query: unterminated phrase at position 9 жук OR | 42601: sift query: expected a term after OR, found end of query at position 7 日本 語 ) | 42601: sift query: unexpected ")" at position 6 ü^x | 42601: sift query: boost must be a non-negative number at position 3 éé~5 | 42601: sift query: fuzzy distance must be 1 or 2 at position 3 (5 rows) -- limits SELECT q, perr(q) FROM (VALUES ('a^10000.01'), ('a^1e9'), ('a^99999999999999999999999999999999999999999'), ('(a^100)^101'), ('((a^10)^10)^100.5'), ('(((((((((((((((((((((((((((((((((a)))))))))))))))))))))))))))))))))') ) v(q); q | perr ---------------------------------------------------------------------+-------------------------------------------------------------------- a^10000.01 | 54000: sift query: boost must be between 0 and 10000 at position 3 a^1e9 | 54000: sift query: boost must be between 0 and 10000 at position 3 a^99999999999999999999999999999999999999999 | 54000: sift query: boost must be between 0 and 10000 at position 3 (a^100)^101 | 54000: sift query: combined boost exceeds 10000 at position 8 ((a^10)^10)^100.5 | 54000: sift query: combined boost exceeds 10000 at position 12 (((((((((((((((((((((((((((((((((a))))))))))))))))))))))))))))))))) | 54000: sift query: nesting deeper than 32 at position 33 (6 rows) SELECT perr(repeat('(', 32) || 'a' || repeat(')', 32)) AS depth_32; depth_32 ---------- ok: a (1 row) SELECT perr(repeat('(', 33) || 'a' || repeat(')', 33)) AS depth_33; depth_33 ---------------------------------------------------------- 54000: sift query: nesting deeper than 32 at position 33 (1 row) SELECT perr(repeat('a ', 256)) LIKE 'ok: a^256' AS leaves_256; leaves_256 ------------ t (1 row) SELECT perr(repeat('a ', 257)) AS leaves_257; leaves_257 -------------------------------------------------------- 54000: sift query: more than 256 terms at position 513 (1 row) SELECT perr('{' || repeat('a ', 257) || '}') AS brace_leaves; brace_leaves -------------------------------------------------------- 54000: sift query: more than 256 terms at position 514 (1 row) SELECT perr('"' || repeat('w ', 32) || '"') LIKE 'ok: %' AS phrase_32; phrase_32 ----------- t (1 row) SELECT perr('x "' || repeat('w ', 129) || '"') AS phrase_129; phrase_129 --------------------------------------------------------------- 54000: sift query: phrase longer than 128 terms at position 3 (1 row) SELECT perr('x ' || repeat('w-', 129)) AS word_phrase_129; word_phrase_129 --------------------------------------------------------------- 54000: sift query: phrase longer than 128 terms at position 3 (1 row) -- hostile input fails fast and cleanly SELECT perr(repeat('(', 1000000)) AS deep_parens; deep_parens ------------------------------- 54000: sift query is too long (1 row) SELECT perr(repeat('{', 100000)) AS deep_braces; deep_braces ------------------------------------------------------------------------------------------------------- 42601: sift query: { } may only hold words, patterns and regular expressions, found "{" at position 2 (1 row) SELECT perr(repeat('NOT ', 60000) || 'a b') AS not_chain; not_chain ------------- ok: a AND b (1 row) SELECT perr(repeat('x', 1000000)) AS huge_term; huge_term ------------------------------- 54000: sift query is too long (1 row) SELECT perr(repeat('x', 262144)) AS max_size_term; max_size_term ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ok: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx￿032cda2e95722325 (1 row) SELECT perr(repeat('a OR ', 52428)) AS huge_or; huge_or --------------------------------------------------------- 54000: sift query: more than 256 terms at position 1281 (1 row) SELECT perr(repeat('"', 99999)) AS quotes; quotes -------------------------------------------------------- 54000: sift query: more than 256 terms at position 513 (1 row) SELECT perr(repeat('\', 99999)) AS backslashes; backslashes ----------------------------------------------------------------------- 42601: sift query: escape character at end of query at position 99999 (1 row) SELECT perr(repeat('/a(', 1000) || '/') AS regex_garbage; regex_garbage ----------------------------------------------------------- 54000: sift query: nesting deeper than 32 at position 198 (1 row) SELECT perr(E'\x01\x02\x03\x7f') AS only_control; only_control ---------------------------------------------- 42601: sift query: empty query at position 5 (1 row) SELECT perr(repeat('a^1', 1000)) AS boost_chain; boost_chain ---------------------------------------------------------------------- 42601: sift query: boost must be a non-negative number at position 3 (1 row) SELECT perr(repeat('a*', 100000)) AS wildcard_soup; wildcard_soup --------------- ok: "" (1 row) SELECT perr('x ' || repeat('a?', 128)) AS wildcard_256_bytes, perr('x OR ' || repeat('a?', 127)) LIKE 'ok: x OR a?%' AS wildcard_254_bytes; wildcard_256_bytes | wildcard_254_bytes --------------------+-------------------- ok: "" | t (1 row) -- regular expressions: at most 1000 bytes, complexity bounded by the engine SELECT length(perr('/' || repeat('(a|', 249) || 'bcde' || repeat(')', 249) || '/')) AS regex_1000; regex_1000 ------------ 1006 (1 row) SELECT perr('/' || repeat('(a|', 249) || 'bcdef' || repeat(')', 249) || '/') AS regex_1001; regex_1001 ---------------------------------------------------------------------------- 54000: sift query: regular expression longer than 1000 bytes at position 1 (1 row) SELECT perr('/' || repeat('((a{1,100}){1,100}){1,100}', 3) || '/') AS regex_too_complex; regex_too_complex ------------------------------------------------------------------------------------------------ 42601: sift query: invalid regular expression: regular expression is too complex at position 1 (1 row) -- fields: a name the index lacks lists the fields it has; without fields a colon is text CREATE TABLE qe (t text); CREATE INDEX qe_idx ON qe USING sift (t) WITH (fields = 'name,city'); CREATE FUNCTION ferr(q text) RETURNS text LANGUAGE plpgsql AS $$ BEGIN RETURN 'ok: ' || sift.parse_query(q, 'qe_idx'::regclass); EXCEPTION WHEN syntax_error OR program_limit_exceeded THEN RETURN sqlstate || ': ' || sqlerrm; END $$; SELECT q, ferr(q) FROM (VALUES ('name:a'), ('nope:a'), ('nope:(a b)'), ('name:(a nope:b)'), ('x NOT nope:a'), ('name:{a nope:b}'), ('name:(a "b c")'), ('name:{/a/}'), ('name:(a (b OR nope:c))^2'), ('Name:a') ) v(q); q | ferr --------------------------+-------------------------------------------------------------------------------------------------------------------------------------- name:a | ok: name:a nope:a | 42601: sift query: unknown field "nope" (available fields: name, city) at position 1 nope:(a b) | 42601: sift query: unknown field "nope" (available fields: name, city) at position 7 name:(a nope:b) | 42601: sift query: unknown field "nope" (available fields: name, city) at position 9 x NOT nope:a | 42601: sift query: unknown field "nope" (available fields: name, city) at position 7 name:{a nope:b} | 42601: sift query: unknown field "nope" (available fields: name, city) at position 9 name:(a "b c") | 42601: sift query: a field applies to words, prefixes, patterns and fuzzy terms, not to phrases or regular expressions at position 9 name:{/a/} | 42601: sift query: a field applies to words, prefixes, patterns and fuzzy terms, not to phrases or regular expressions at position 7 name:(a (b OR nope:c))^2 | 42601: sift query: unknown field "nope" (available fields: name, city) at position 15 Name:a | ok: "name a" (10 rows) SELECT q, perr(q) FROM (VALUES ('name:a'), ('name:(a b)'), ('name:{a b}'), ('NOT name:(a b)'), ('x name:(a OR b)^2'), ('name:("a b" c)'), ('name:(a /b/)') ) v(q); q | perr -------------------+-------------------------------------------------------------------------------------------------------------------------------------- name:a | ok: "name a" name:(a b) | ok: "name a" AND "name b" name:{a b} | ok: "name a" OR "name b" NOT name:(a b) | 42601: sift query: NOT needs a positive term in the same AND group at position 1 x name:(a OR b)^2 | ok: x AND ("name a"^2 OR "name b"^2) name:("a b" c) | 42601: sift query: a field applies to words, prefixes, patterns and fuzzy terms, not to phrases or regular expressions at position 7 name:(a /b/) | 42601: sift query: a field applies to words, prefixes, patterns and fuzzy terms, not to phrases or regular expressions at position 9 (7 rows) DROP TABLE qe; DROP FUNCTION ferr; DROP FUNCTION perr; DROP EXTENSION pg_sift;