From: Nikita Glukhov Date: Tue, 16 Jul 2019 17:59:32 +0300 Subject: [PATCH] Ltree syntax improvements --- contrib/ltree/expected/ltree.out | 1081 +++++++++++++++++++++++++++++++++++++- contrib/ltree/ltree.h | 25 +- contrib/ltree/ltree_io.c | 667 +++++++++++++++++------ contrib/ltree/ltxtquery_io.c | 118 +++-- contrib/ltree/sql/ltree.sql | 267 ++++++++++ doc/src/sgml/ltree.sgml | 38 +- 6 files changed, 1984 insertions(+), 212 deletions(-) diff --git a/contrib/ltree/expected/ltree.out b/contrib/ltree/expected/ltree.out index c6d8f3e..0c925b6 100644 --- a/contrib/ltree/expected/ltree.out +++ b/contrib/ltree/expected/ltree.out @@ -1,4 +1,5 @@ CREATE EXTENSION ltree; +SET standard_conforming_strings=on; -- Check whether any of our opclasses fail amvalidate SELECT amname, opcname FROM pg_opclass opc LEFT JOIN pg_am am ON am.oid = opcmethod @@ -53,7 +54,7 @@ SELECT repeat('x', 255)::ltree; SELECT repeat('x', 256)::ltree; ERROR: label string is too long -DETAIL: Label length is 256, must be at most 255, at character 257. +DETAIL: Label length is 256, must be at most 255, at character 1. SELECT ltree2text('1.2.3.34.sdf'); ltree2text -------------- @@ -336,6 +337,11 @@ SELECT lca('1.2.2.3','1.2.3.4.5.6','1'); (1 row) +SELECT ''::lquery; +ERROR: lquery syntax error +LINE 1: SELECT ''::lquery; + ^ +DETAIL: Unexpected end of input. SELECT '1'::lquery; lquery -------- @@ -480,6 +486,16 @@ SELECT 'foo*@@*'::lquery; foo@* (1 row) +SELECT '*'::lquery; + lquery +-------- + * +(1 row) + +SELECT '*{1}|2'::lquery; +ERROR: lquery syntax error at character 5 +LINE 1: SELECT '*{1}|2'::lquery; + ^ SELECT 'qwerty%@*.tu'::lquery; lquery -------------- @@ -516,17 +532,15 @@ SELECT '!.2.3'::lquery; ERROR: lquery syntax error at character 2 LINE 1: SELECT '!.2.3'::lquery; ^ -DETAIL: Empty labels are not allowed. SELECT '1.!.3'::lquery; ERROR: lquery syntax error at character 4 LINE 1: SELECT '1.!.3'::lquery; ^ -DETAIL: Empty labels are not allowed. SELECT '1.2.!'::lquery; -ERROR: lquery syntax error at character 6 +ERROR: lquery syntax error LINE 1: SELECT '1.2.!'::lquery; ^ -DETAIL: Empty labels are not allowed. +DETAIL: Unexpected end of input. SELECT '1.2.3|@.4'::lquery; ERROR: lquery syntax error at character 7 LINE 1: SELECT '1.2.3|@.4'::lquery; @@ -539,7 +553,7 @@ SELECT (repeat('x', 255) || '*@@*')::lquery; SELECT (repeat('x', 256) || '*@@*')::lquery; ERROR: label string is too long -DETAIL: Label length is 256, must be at most 255, at character 257. +DETAIL: Label length is 256, must be at most 255, at character 1. SELECT ('!' || repeat('x', 255))::lquery; lquery ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ @@ -548,7 +562,7 @@ SELECT ('!' || repeat('x', 255))::lquery; SELECT ('!' || repeat('x', 256))::lquery; ERROR: label string is too long -DETAIL: Label length is 256, must be at most 255, at character 258. +DETAIL: Label length is 256, must be at most 255, at character 2. SELECT nlevel('1.2.3.4'); nlevel -------- @@ -8084,3 +8098,1056 @@ SELECT count(*) FROM _ltreetest WHERE t ? '{23.*.1,23.*.2}' ; 15 (1 row) +-- Extended syntax, escaping, quoting etc +-- success +SELECT E'\\.'::ltree; + ltree +------- + "." +(1 row) + +SELECT E'\\ '::ltree; + ltree +------- + " " +(1 row) + +SELECT E'\\\\'::ltree; + ltree +------- + "\\" +(1 row) + +SELECT E'\\a'::ltree; + ltree +------- + a +(1 row) + +SELECT E'\\n'::ltree; + ltree +------- + n +(1 row) + +SELECT E'x\\\\'::ltree; + ltree +------- + "x\\" +(1 row) + +SELECT E'x\\ '::ltree; + ltree +------- + "x " +(1 row) + +SELECT E'x\\.'::ltree; + ltree +------- + "x." +(1 row) + +SELECT E'x\\a'::ltree; + ltree +------- + xa +(1 row) + +SELECT E'x\\n'::ltree; + ltree +------- + xn +(1 row) + +SELECT 'a b.с d'::ltree; +ERROR: ltree syntax error at character 3 +LINE 1: SELECT 'a b.с d'::ltree; + ^ +SELECT '"a b"."с d"'::ltree; + ltree +------------- + "a b"."с d" +(1 row) + +SELECT ' e . f '::ltree; + ltree +------- + e.f +(1 row) + +SELECT ' '::ltree; + ltree +------- + +(1 row) + +SELECT E'\\ g . h\\ '::ltree; + ltree +----------- + " g"."h " +(1 row) + +SELECT E'\\ g'::ltree; + ltree +------- + " g" +(1 row) + +SELECT E' h\\ '::ltree; + ltree +------- + "h " +(1 row) + +SELECT '"g" '::ltree; + ltree +------- + g +(1 row) + +SELECT '"g" . h'::ltree; + ltree +------- + g.h +(1 row) + +SELECT '" g "." h "'::ltree; + ltree +-------------- + " g "." h " +(1 row) + +SELECT '" g " '::ltree; + ltree +-------- + " g " +(1 row) + +SELECT '" g " ." h " '::ltree; + ltree +-------------- + " g "." h " +(1 row) + +SELECT nlevel(E'Bottom\\.Test'::ltree); + nlevel +-------- + 1 +(1 row) + +SELECT subpath(E'Bottom\\.'::ltree, 0, 1); + subpath +----------- + "Bottom." +(1 row) + +SELECT subpath(E'a\\.b', 0, 1); + subpath +--------- + "a.b" +(1 row) + +SELECT subpath(E'a\\..b', 1, 1); + subpath +--------- + b +(1 row) + +SELECT subpath(E'a\\..\\b', 1, 1); + subpath +--------- + b +(1 row) + +SELECT subpath(E'"a b"."с d"'::ltree, 1, 1); + subpath +--------- + "с d" +(1 row) + +SELECT( +'01234567890123456789012345678901234567890123456789' || +'01234567890123456789012345678901234567890123456789' || +'01234567890123456789012345678901234567890123456789' || +'01234567890123456789012345678901234567890123456789' || +'01234567890123456789012345678901234567890123456789' || +'\z\z\z\z\z')::ltree; + ltree +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + 0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789zzzzz +(1 row) + +SELECT(' ' || +'01234567890123456789012345678901234567890123456789' || +'01234567890123456789012345678901234567890123456789' || +'01234567890123456789012345678901234567890123456789' || +'01234567890123456789012345678901234567890123456789' || +'01234567890123456789012345678901234567890123456789' || +'\a\b\c\d\e ')::ltree; + ltree +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + 0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789abcde +(1 row) + +SELECT 'abc\|d'::lquery; + lquery +--------- + "abc|d" +(1 row) + +SELECT 'abc\|d'::ltree ~ 'abc\|d'::lquery; + ?column? +---------- + t +(1 row) + +SELECT 'abc\|d'::ltree ~ 'abc*'::lquery; --true + ?column? +---------- + t +(1 row) + +SELECT 'abc\|d'::ltree ~ 'abc\*'::lquery; --false + ?column? +---------- + f +(1 row) + +SELECT E'abc\\|\\.'::ltree ~ 'abc\|*'::lquery; --true + ?column? +---------- + t +(1 row) + +SELECT E'"\\""'::ltree; + ltree +------- + "\"" +(1 row) + +SELECT '\"'::ltree; + ltree +------- + "\"" +(1 row) + +SELECT E'\\"'::ltree; + ltree +------- + "\"" +(1 row) + +SELECT 'a\"b'::ltree; + ltree +-------- + "a\"b" +(1 row) + +SELECT '"ab"'::ltree; + ltree +------- + ab +(1 row) + +SELECT '"."'::ltree; + ltree +------- + "." +(1 row) + +SELECT E'".\\""'::ltree; + ltree +------- + ".\"" +(1 row) + +SELECT( +'"01234567890123456789012345678901234567890123456789' || +'01234567890123456789012345678901234567890123456789' || +'01234567890123456789012345678901234567890123456789' || +'01234567890123456789012345678901234567890123456789' || +'01234567890123456789012345678901234567890123456789' || +'\z\z\z\z\z"')::ltree; + ltree +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + 0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789zzzzz +(1 row) + +SELECT E'"\\""'::lquery; + lquery +-------- + "\"" +(1 row) + +SELECT '\"'::lquery; + lquery +-------- + "\"" +(1 row) + +SELECT E'\\"'::lquery; + lquery +-------- + "\"" +(1 row) + +SELECT 'a\"b'::lquery; + lquery +-------- + "a\"b" +(1 row) + +SELECT '"ab"'::lquery; + lquery +-------- + ab +(1 row) + +SELECT '"."'::lquery; + lquery +-------- + "." +(1 row) + +SELECT E'".\\""'::lquery; + lquery +-------- + ".\"" +(1 row) + +SELECT( +'"01234567890123456789012345678901234567890123456789' || +'01234567890123456789012345678901234567890123456789' || +'01234567890123456789012345678901234567890123456789' || +'01234567890123456789012345678901234567890123456789' || +'01234567890123456789012345678901234567890123456789' || +'\z\z\z\z\z"')::lquery; + lquery +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + 0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789zzzzz +(1 row) + +SELECT ' e . f '::lquery; + lquery +-------- + e.f +(1 row) + +SELECT ' e | f '::lquery; + lquery +-------- + e|f +(1 row) + +SELECT E'\\ g . h\\ '::lquery; + lquery +----------- + " g"."h " +(1 row) + +SELECT E'\\ g'::lquery; + lquery +-------- + " g" +(1 row) + +SELECT E' h\\ '::lquery; + lquery +-------- + "h " +(1 row) + +SELECT E'"\\ g"'::lquery; + lquery +-------- + " g" +(1 row) + +SELECT E' "h\\ "'::lquery; + lquery +-------- + "h " +(1 row) + +SELECT '" g "." h "'::lquery; + lquery +-------------- + " g "." h " +(1 row) + +SELECT E'\\ g | h\\ '::lquery; + lquery +----------- + " g"|"h " +(1 row) + +SELECT '" g "|" h "'::lquery; + lquery +-------------- + " g "|" h " +(1 row) + +SELECT '"g" '::lquery; + lquery +-------- + g +(1 row) + +SELECT '"g" . h'::lquery; + lquery +-------- + g.h +(1 row) + +SELECT '" g " '::lquery; + lquery +-------- + " g " +(1 row) + +SELECT '" g " ." h " '::lquery; + lquery +-------------- + " g "." h " +(1 row) + +SELECT '" g " | " h " '::lquery; + lquery +-------------- + " g "|" h " +(1 row) + +SELECT(' ' || +'01234567890123456789012345678901234567890123456789' || +'01234567890123456789012345678901234567890123456789' || +'01234567890123456789012345678901234567890123456789' || +'01234567890123456789012345678901234567890123456789' || +'01234567890123456789012345678901234567890123456789' || +'\a\b\c\d\e ')::lquery; + lquery +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + 0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789abcde +(1 row) + +SELECT '"a!b"'::lquery; + lquery +-------- + "a!b" +(1 row) + +SELECT '!"!b"'::lquery; + lquery +-------- + !"!b" +(1 row) + +SELECT '!"{b"'::lquery; + lquery +-------- + !"{b" +(1 row) + +SELECT E'"a\\"b"'::lquery; + lquery +-------- + "a\"b" +(1 row) + +SELECT E'a\\"b'::lquery; + lquery +-------- + "a\"b" +(1 row) + +SELECT E'a\\*b'::lquery; + lquery +-------- + "a*b" +(1 row) + +SELECT E'a\\.b'::lquery; + lquery +-------- + "a.b" +(1 row) + +SELECT E'!\\\\b'::lquery; + lquery +-------- + !"\\b" +(1 row) + +SELECT E'!\\@b'::lquery; + lquery +-------- + !"@b" +(1 row) + +SELECT '"1"'::lquery; + lquery +-------- + 1 +(1 row) + +SELECT '"2.*"'::lquery; + lquery +-------- + "2.*" +(1 row) + +SELECT '!"1"'::lquery; + lquery +-------- + !1 +(1 row) + +SELECT '!"1|"'::lquery; + lquery +-------- + !"1|" +(1 row) + +SELECT '4|3|"2"'::lquery; + lquery +-------- + 4|3|2 +(1 row) + +SELECT '"1".2'::lquery; + lquery +-------- + 1.2 +(1 row) + +SELECT '"1.4"|"3"|2'::lquery; + lquery +----------- + "1.4"|3|2 +(1 row) + +SELECT '"1"."4"|"3"|"2"'::lquery; + lquery +--------- + 1.4|3|2 +(1 row) + +SELECT '"1"."0"'::lquery; + lquery +-------- + 1.0 +(1 row) + +SELECT '"1".0'::lquery; + lquery +-------- + 1.0 +(1 row) + +SELECT '"1".*'::lquery; + lquery +-------- + 1.* +(1 row) + +SELECT '4|"3"|2.*'::lquery; + lquery +--------- + 4|3|2.* +(1 row) + +SELECT '4|"3"|"2.*"'::lquery; + lquery +----------- + 4|3|"2.*" +(1 row) + +SELECT '2."*"'::lquery; + lquery +-------- + 2."*" +(1 row) + +SELECT '"*".1."*"'::lquery; + lquery +----------- + "*".1."*" +(1 row) + +SELECT '"*.4"|3|2.*'::lquery; + lquery +------------- + "*.4"|3|2.* +(1 row) + +SELECT '"*.4"|3|"2.*"'::lquery; + lquery +--------------- + "*.4"|3|"2.*" +(1 row) + +SELECT '1.*.4|3|2.*{,4}'::lquery; + lquery +----------------- + 1.*.4|3|2.*{,4} +(1 row) + +SELECT '1.*.4|3|2.*{1,}'::lquery; + lquery +----------------- + 1.*.4|3|2.*{1,} +(1 row) + +SELECT '1.*.4|3|2.*{1}'::lquery; + lquery +---------------- + 1.*.4|3|2.*{1} +(1 row) + +SELECT '"qwerty"%@*.tu'::lquery; + lquery +-------------- + qwerty%@*.tu +(1 row) + +SELECT '1.*.4|3|"2".*{1,4}'::lquery; + lquery +------------------ + 1.*.4|3|2.*{1,4} +(1 row) + +SELECT '1."*".4|3|"2".*{1,4}'::lquery; + lquery +-------------------- + 1."*".4|3|2.*{1,4} +(1 row) + +SELECT '\%\ \@'::lquery; + lquery +-------- + "% @" +(1 row) + +SELECT '"\% \@"'::lquery; + lquery +-------- + "% @" +(1 row) + +SELECT E'\\aa.b.c.d.e'::ltree ~ 'A@.b.c.d.e'; + ?column? +---------- + f +(1 row) + +SELECT E'a\\a.b.c.\\d.e'::ltree ~ 'A*.b.c.d.e'; + ?column? +---------- + f +(1 row) + +SELECT E'a\\a.b.c.\\d.e'::ltree ~ E'A*@.b.c.d.\\e'; + ?column? +---------- + t +(1 row) + +SELECT E'a\\a.b.c.\\d.e'::ltree ~ E'A*@|\\g.b.c.d.e'; + ?column? +---------- + t +(1 row) + +--ltxtquery +SELECT '!"tree" & aWdf@*'::ltxtquery; + ltxtquery +---------------- + !tree & aWdf@* +(1 row) + +SELECT '"!tree" & aWdf@*'::ltxtquery; + ltxtquery +------------------ + "!tree" & aWdf@* +(1 row) + +SELECT E'tr\\ee'::ltree @ E'\\t\\r\\e\\e'::ltxtquery; + ?column? +---------- + t +(1 row) + +SELECT E'tr\\ee.awd\\fg'::ltree @ E'tre\\e & a\\Wdf@*'::ltxtquery; + ?column? +---------- + t +(1 row) + +SELECT 'tree & aw_qw%*'::ltxtquery; + ltxtquery +---------------- + tree & aw_qw%* +(1 row) + +SELECT 'tree."awdfg"'::ltree @ E'tree & a\\Wdf@*'::ltxtquery; + ?column? +---------- + t +(1 row) + +SELECT 'tree."awdfg"'::ltree @ E'tree & "a\\Wdf"@*'::ltxtquery; + ?column? +---------- + t +(1 row) + +SELECT 'tree.awdfg_qwerty'::ltree @ 'tree & aw_qw%*'::ltxtquery; + ?column? +---------- + t +(1 row) + +SELECT 'tree.awdfg_qwerty'::ltree @ 'tree & "aw_rw"%*'::ltxtquery; + ?column? +---------- + f +(1 row) + +SELECT 'tree.awdfg_qwerty'::ltree @ E'tree & "aw\\_qw"%*'::ltxtquery; + ?column? +---------- + t +(1 row) + +SELECT 'tree.awdfg_qwerty'::ltree @ E'tree & aw\\_qw%*'::ltxtquery; + ?column? +---------- + t +(1 row) + +SELECT E'"a\\"b"'::ltxtquery; + ltxtquery +----------- + "a\"b" +(1 row) + +SELECT '"a!b"'::ltxtquery; + ltxtquery +----------- + "a!b" +(1 row) + +SELECT E'a\\"'::ltxtquery; + ltxtquery +----------- + "a\"" +(1 row) + +SELECT E'a\\!'::ltxtquery; + ltxtquery +----------- + "a!" +(1 row) + +SELECT E'a\\"b'::ltxtquery; + ltxtquery +----------- + "a\"b" +(1 row) + +SELECT E'a\\!b'::ltxtquery; + ltxtquery +----------- + "a!b" +(1 row) + +SELECT E'a\\%b'::ltxtquery; + ltxtquery +----------- + "a%b" +(1 row) + +SELECT E'\\"b'::ltxtquery; + ltxtquery +----------- + "\"b" +(1 row) + +SELECT E'\\*b'::ltxtquery; + ltxtquery +----------- + "*b" +(1 row) + +SELECT E'"\\"b"'::ltxtquery; + ltxtquery +----------- + "\"b" +(1 row) + +SELECT E'"a\\""'::ltxtquery; + ltxtquery +----------- + "a\"" +(1 row) + +SELECT '"!b" | "%b"'::ltxtquery; + ltxtquery +------------- + "!b" | "%b" +(1 row) + +SELECT '"a!" | "a%"'::ltxtquery; + ltxtquery +------------- + "a!" | "a%" +(1 row) + +--failures +SELECT E'\\'::ltree; +ERROR: ltree syntax error at character 1 +LINE 1: SELECT E'\\'::ltree; + ^ +DETAIL: Unclosed escape sequence +SELECT E'n\\'::ltree; +ERROR: ltree syntax error at character 2 +LINE 1: SELECT E'n\\'::ltree; + ^ +DETAIL: Unclosed escape sequence +SELECT '"'::ltree; +ERROR: ltree syntax error at character 1 +LINE 1: SELECT '"'::ltree; + ^ +DETAIL: Unclosed quote +SELECT '"a'::ltree; +ERROR: ltree syntax error at character 1 +LINE 1: SELECT '"a'::ltree; + ^ +DETAIL: Unclosed quote +SELECT '""'::ltree; +ERROR: ltree syntax error at character 1 +LINE 1: SELECT '""'::ltree; + ^ +DETAIL: Empty labels are not allowed. +SELECT 'a"b'::ltree; +ERROR: ltree syntax error at character 2 +LINE 1: SELECT 'a"b'::ltree; + ^ +DETAIL: Unclosed quote +SELECT E'\\"ab"'::ltree; +ERROR: ltree syntax error at character 5 +LINE 1: SELECT E'\\"ab"'::ltree; + ^ +DETAIL: Unclosed quote +SELECT '"a"."a'::ltree; +ERROR: ltree syntax error at character 5 +LINE 1: SELECT '"a"."a'::ltree; + ^ +DETAIL: Unclosed quote +SELECT '"a."a"'::ltree; +ERROR: ltree syntax error at character 5 +LINE 1: SELECT '"a."a"'::ltree; + ^ +SELECT '"".a'::ltree; +ERROR: ltree syntax error at character 1 +LINE 1: SELECT '"".a'::ltree; + ^ +DETAIL: Empty labels are not allowed. +SELECT 'a.""'::ltree; +ERROR: ltree syntax error at character 3 +LINE 1: SELECT 'a.""'::ltree; + ^ +DETAIL: Empty labels are not allowed. +SELECT '"".""'::ltree; +ERROR: ltree syntax error at character 1 +LINE 1: SELECT '"".""'::ltree; + ^ +DETAIL: Empty labels are not allowed. +SELECT '""'::lquery; +ERROR: lquery syntax error at character 1 +LINE 1: SELECT '""'::lquery; + ^ +DETAIL: Empty labels are not allowed. +SELECT '"".""'::lquery; +ERROR: lquery syntax error at character 1 +LINE 1: SELECT '"".""'::lquery; + ^ +DETAIL: Empty labels are not allowed. +SELECT 'a.""'::lquery; +ERROR: lquery syntax error at character 3 +LINE 1: SELECT 'a.""'::lquery; + ^ +DETAIL: Empty labels are not allowed. +SELECT ' . '::ltree; +ERROR: ltree syntax error at character 2 +LINE 1: SELECT ' . '::ltree; + ^ +SELECT ' . '::lquery; +ERROR: lquery syntax error at character 2 +LINE 1: SELECT ' . '::lquery; + ^ +SELECT ' | '::lquery; +ERROR: lquery syntax error at character 2 +LINE 1: SELECT ' | '::lquery; + ^ +SELECT( +'01234567890123456789012345678901234567890123456789' || +'01234567890123456789012345678901234567890123456789' || +'01234567890123456789012345678901234567890123456789' || +'01234567890123456789012345678901234567890123456789' || +'01234567890123456789012345678901234567890123456789' || +'z\z\z\z\z\z')::ltree; +ERROR: label string is too long +DETAIL: Label length is 256, must be at most 255, at character 1. +SELECT( +'"01234567890123456789012345678901234567890123456789' || +'01234567890123456789012345678901234567890123456789' || +'01234567890123456789012345678901234567890123456789' || +'01234567890123456789012345678901234567890123456789' || +'01234567890123456789012345678901234567890123456789' || +'\z\z\z\z\z\z"')::ltree; +ERROR: label string is too long +DETAIL: Label length is 256, must be at most 255, at character 1. +SELECT '"'::lquery; +ERROR: lquery syntax error at character 1 +LINE 1: SELECT '"'::lquery; + ^ +DETAIL: Unclosed quote +SELECT '"a'::lquery; +ERROR: lquery syntax error at character 1 +LINE 1: SELECT '"a'::lquery; + ^ +DETAIL: Unclosed quote +SELECT '"a"."a'::lquery; +ERROR: lquery syntax error at character 5 +LINE 1: SELECT '"a"."a'::lquery; + ^ +DETAIL: Unclosed quote +SELECT '"a."a"'::lquery; +ERROR: lquery syntax error at character 5 +LINE 1: SELECT '"a."a"'::lquery; + ^ +SELECT E'\\"ab"'::lquery; +ERROR: lquery syntax error at character 5 +LINE 1: SELECT E'\\"ab"'::lquery; + ^ +DETAIL: Unclosed quote +SELECT 'a"b'::lquery; +ERROR: lquery syntax error at character 2 +LINE 1: SELECT 'a"b'::lquery; + ^ +DETAIL: Unclosed quote +SELECT 'a!b'::lquery; +ERROR: lquery syntax error at character 2 +LINE 1: SELECT 'a!b'::lquery; + ^ +SELECT 'a{'::lquery; +ERROR: lquery syntax error +LINE 1: SELECT 'a{'::lquery; + ^ +DETAIL: Unexpected end of input. +SELECT '%b'::lquery; +ERROR: lquery syntax error at character 1 +LINE 1: SELECT '%b'::lquery; + ^ +SELECT '!*b'::lquery; +ERROR: lquery syntax error at character 2 +LINE 1: SELECT '!*b'::lquery; + ^ +SELECT '"foo"bar.baz'::lquery; +ERROR: lquery syntax error at character 6 +LINE 1: SELECT '"foo"bar.baz'::lquery; + ^ +SELECT '"foo bar"@*.baz'::lquery; + lquery +----------------- + "foo bar"@*.baz +(1 row) + +SELECT( +'"01234567890123456789012345678901234567890123456789' || +'01234567890123456789012345678901234567890123456789' || +'01234567890123456789012345678901234567890123456789' || +'01234567890123456789012345678901234567890123456789' || +'01234567890123456789012345678901234567890123456789' || +'\z\z\z\z\z\z"')::lquery; +ERROR: label string is too long +DETAIL: Label length is 256, must be at most 255, at character 1. +SELECT 'a | ""'::ltxtquery; +ERROR: empty labels are forbidden +LINE 1: SELECT 'a | ""'::ltxtquery; + ^ +SELECT '"" & ""'::ltxtquery; +ERROR: empty labels are forbidden +LINE 1: SELECT '"" & ""'::ltxtquery; + ^ +SELECT 'a.""'::ltxtquery; +ERROR: ltxtquery syntax error at character 2 +LINE 1: SELECT 'a.""'::ltxtquery; + ^ +DETAIL: Unquoted special symbol +SELECT '"'::ltxtquery; +ERROR: ltxtquery syntax error at character 1 +LINE 1: SELECT '"'::ltxtquery; + ^ +DETAIL: Unclosed quote +SELECT '"""'::ltxtquery; +ERROR: ltxtquery syntax error at character 3 +LINE 1: SELECT '"""'::ltxtquery; + ^ +DETAIL: Unclosed quote +SELECT '"a'::ltxtquery; +ERROR: ltxtquery syntax error at character 1 +LINE 1: SELECT '"a'::ltxtquery; + ^ +DETAIL: Unclosed quote +SELECT '"a" & "a'::ltxtquery; +ERROR: ltxtquery syntax error at character 7 +LINE 1: SELECT '"a" & "a'::ltxtquery; + ^ +DETAIL: Unclosed quote +SELECT '"a | "a"'::ltxtquery; +ERROR: ltxtquery syntax error at character 7 +LINE 1: SELECT '"a | "a"'::ltxtquery; + ^ +DETAIL: Unquoted special symbol +SELECT '"!tree" & aWdf@*"'::ltxtquery; +ERROR: ltxtquery syntax error at character 17 +LINE 1: SELECT '"!tree" & aWdf@*"'::ltxtquery; + ^ +DETAIL: Unclosed quote +SELECT 'a"b'::ltxtquery; +ERROR: ltxtquery syntax error at character 2 +LINE 1: SELECT 'a"b'::ltxtquery; + ^ +DETAIL: Unclosed quote +SELECT 'a!b'::ltxtquery; +ERROR: ltxtquery syntax error at character 2 +LINE 1: SELECT 'a!b'::ltxtquery; + ^ +DETAIL: Unquoted special symbol +SELECT 'a%b'::ltxtquery; +ERROR: ltxtquery syntax error at character 3 +LINE 1: SELECT 'a%b'::ltxtquery; + ^ +DETAIL: Unquoted special symbol +SELECT '"b'::ltxtquery; +ERROR: ltxtquery syntax error at character 1 +LINE 1: SELECT '"b'::ltxtquery; + ^ +DETAIL: Unclosed quote +SELECT '%b'::ltxtquery; +ERROR: ltxtquery syntax error at character 1 +LINE 1: SELECT '%b'::ltxtquery; + ^ +DETAIL: Unquoted special symbol +SELECT 'a"'::ltxtquery; +ERROR: ltxtquery syntax error at character 2 +LINE 1: SELECT 'a"'::ltxtquery; + ^ +DETAIL: Unclosed quote +SELECT 'a!'::ltxtquery; +ERROR: ltxtquery syntax error at character 2 +LINE 1: SELECT 'a!'::ltxtquery; + ^ +DETAIL: Unquoted special symbol diff --git a/contrib/ltree/ltree.h b/contrib/ltree/ltree.h index 7eac7c9..563d39d 100644 --- a/contrib/ltree/ltree.h +++ b/contrib/ltree/ltree.h @@ -113,8 +113,6 @@ typedef struct #define LQUERY_HASNOT 0x01 -#define ISALNUM(x) ( t_isalpha(x) || t_isdigit(x) || ( pg_mblen(x) == 1 && t_iseq((x), '_') ) ) - /* full text query */ /* @@ -161,6 +159,24 @@ typedef struct #define VALTRUE 6 /* for stop words */ #define VALFALSE 7 +typedef enum ltree_token +{ + LTREE_TOK_END, + LTREE_TOK_SPACE, + LTREE_TOK_LABEL, + LTREE_TOK_DOT, + LTREE_TOK_ASTERISK, + LTREE_TOK_NOT, + LTREE_TOK_OR, + LTREE_TOK_AND, + LTREE_TOK_AT, + LTREE_TOK_PERCENT, + LTREE_TOK_LBRACE, + LTREE_TOK_RBRACE, + LTREE_TOK_LPAREN, + LTREE_TOK_RPAREN, + LTREE_TOK_COMMA +} ltree_token; /* use in array iterator */ Datum ltree_isparent(PG_FUNCTION_ARGS); @@ -197,6 +213,11 @@ bool compare_subnode(ltree_level *t, char *q, int len, int (*cmpptr) (const char *, const char *, size_t), bool anyend); ltree *lca_inner(ltree **a, int len); int ltree_strncasecmp(const char *a, const char *b, size_t s); +int extra_bytes_for_escaping(const char *start, const int len); +void copy_level(char *dst, const char *src, int len, int extra_bytes); +void copy_unescaped(char *dst, const char *src, int len); +ltree_token ltree_get_token(const char *ptr, const char *datatype_name, int pos, + int *len, int *wlen, int *escaped_count); /* fmgr macros for ltree objects */ #define DatumGetLtreeP(X) ((ltree *) PG_DETOAST_DATUM(X)) diff --git a/contrib/ltree/ltree_io.c b/contrib/ltree/ltree_io.c index 15115cb..d9d323c 100644 --- a/contrib/ltree/ltree_io.c +++ b/contrib/ltree/ltree_io.c @@ -24,11 +24,321 @@ typedef struct #define LTPRS_WAITNAME 0 #define LTPRS_WAITDELIM 1 -static void finish_nodeitem(nodeitem *lptr, const char *ptr, - bool is_lquery, int pos); +static void finish_nodeitem(nodeitem *lptr, const char *ptr, int len, int wlen, + int escapes, bool is_lquery, int pos); /* + * Calculating the number of literals in the string to be parsed. + * + * For ltree, returns a number of not escaped delimiters (dots). If pORs is + * not NULL, calculates the number of alternate templates (used in lquery + * parsing). The function can return more levels than is really necessesary, + * it will be corrected during the real parsing process. + */ +static void +count_parts_ors(const char *ptr, int *plevels, int *pORs) +{ + bool quote = false; + bool escaping = false; + + while (*ptr) + { + if (escaping) + escaping = false; + else if (t_iseq(ptr, '\\')) + escaping = true; + else if (quote) + { + if (t_iseq(ptr, '"')) + quote = false; + } + else + { + if (t_iseq(ptr, '"')) + quote = true; + else if (t_iseq(ptr, '.')) + (*plevels)++; + else if (t_iseq(ptr, '|') && pORs != NULL) + (*pORs)++; + } + + ptr += pg_mblen(ptr); + } + + (*plevels)++; + if (pORs != NULL) + (*pORs)++; +} + +/* + * Char-by-char copying from "src" to "dst" representation removing escaping. + * Total amount of copied bytes is "len". + */ +void +copy_unescaped(char *dst, const char *src, int len) +{ + const char *dst_end = dst + len; + bool escaping = false; + + while (dst < dst_end && *src) + { + int charlen; + + if (t_iseq(src, '\\') && !escaping) + { + escaping = true; + src++; + continue; + } + + charlen = pg_mblen(src); + + if (dst + charlen > dst_end) + elog(ERROR, "internal error during splitting levels"); + + memcpy(dst, src, charlen); + src += charlen; + dst += charlen; + escaping = false; + } + + if (dst != dst_end) + elog(ERROR, "internal error during splitting levels"); +} + +static bool +is_quoted_char(const char *ptr) +{ + return !(t_isalpha(ptr) || t_isdigit(ptr) || t_iseq(ptr, '_')); +} + +static bool +is_escaped_char(const char *ptr) +{ + return t_iseq(ptr, '"') || t_iseq(ptr, '\\'); +} + +/* + * Function calculating extra bytes needed for quoting/escaping of special + * characters. + * + * If there are no special characters, return 0. + * If there are any special symbol, we need initial and final quote, return 2. + * If there are any quotes or backslashes, we need to escape all of them and + * also initial and final quote, so return 2 + number of quotes/backslashes. + */ +int +extra_bytes_for_escaping(const char *start, const int len) +{ + const char *ptr = start; + const char *end = start + len; + int escapes = 0; + bool quotes = false; + + if (len == 0) + return 2; + + while (ptr < end && *ptr) + { + if (is_escaped_char(ptr)) + escapes++; + else if (is_quoted_char(ptr)) + quotes = true; + + ptr += pg_mblen(ptr); + } + + if (ptr > end) + elog(ERROR, "internal error during merging levels"); + + return (escapes > 0) ? escapes + 2 : quotes ? 2 : 0; +} + +/* + * Copy "src" to "dst" escaping backslashes and quotes. + * + * Return number of escaped characters. + */ +static int +copy_escaped(char *dst, const char *src, int len) +{ + const char *src_end = src + len; + int escapes = 0; + + while (src < src_end && *src) + { + int charlen = pg_mblen(src); + + if (is_escaped_char(src)) + { + *dst++ = '\\'; + escapes++; + } + + if (src + charlen > src_end) + elog(ERROR, "internal error during merging levels"); + + memcpy(dst, src, charlen); + src += charlen; + dst += charlen; + } + + return escapes; +} + +/* + * Copy "src" to "dst" possibly adding surrounding quotes and escaping + * backslashes and internal quotes. + * + * "extra_bytes" is a value calculated by extra_bytes_for_escaping(). + */ +void +copy_level(char *dst, const char *src, int len, int extra_bytes) +{ + if (extra_bytes == 0) /* no quotes and escaping */ + memcpy(dst, src, len); + else if (extra_bytes == 2) /* only quotes, no escaping */ + { + *dst = '"'; + memcpy(dst + 1, src, len); + dst[len + 1] = '"'; + } + else /* quotes and escaping */ + { + *dst = '"'; + copy_escaped(dst + 1, src, len); + dst[len + extra_bytes - 1] = '"'; + } +} + +/* + * Read next token from input string "str". + * + * Output parameteres: + * "len" - token length in bytes. + * "wlen" - token length in characters. + * "escaped_count" - number of escaped characters in LTREE_TOK_LABEL token. + */ +ltree_token +ltree_get_token(const char *str, const char *datatype_name, int pos, + int *len, int *wlen, int *escaped_count) +{ + const char *ptr = str; + int charlen; + bool quoted = false; + bool escaped = false; + + *escaped_count = 0; + *len = 0; + *wlen = 0; + + if (!*ptr) + return LTREE_TOK_END; + + charlen = pg_mblen(ptr); + + if (t_isspace(ptr)) + { + ++*wlen; + ptr += charlen; + + while (*ptr && t_isspace(ptr)) + { + ptr += pg_mblen(ptr); + ++*wlen; + } + + *len = ptr - str; + return LTREE_TOK_SPACE; + } + + if (charlen == 1 && strchr(".*!|&@%{}(),", *ptr)) + { + *wlen = *len = 1; + + if (t_iseq(ptr, '.')) + return LTREE_TOK_DOT; + else if (t_iseq(ptr, '*')) + return LTREE_TOK_ASTERISK; + else if (t_iseq(ptr, '!')) + return LTREE_TOK_NOT; + else if (t_iseq(ptr, '|')) + return LTREE_TOK_OR; + else if (t_iseq(ptr, '&')) + return LTREE_TOK_AND; + else if (t_iseq(ptr, '@')) + return LTREE_TOK_AT; + else if (t_iseq(ptr, '%')) + return LTREE_TOK_PERCENT; + else if (t_iseq(ptr, ',')) + return LTREE_TOK_COMMA; + else if (t_iseq(ptr, '{')) + return LTREE_TOK_LBRACE; + else if (t_iseq(ptr, '}')) + return LTREE_TOK_RBRACE; + else if (t_iseq(ptr, '(')) + return LTREE_TOK_LPAREN; + else if (t_iseq(ptr, ')')) + return LTREE_TOK_RPAREN; + else + elog(ERROR, "invalid special character"); + } + else if (t_iseq(ptr, '\\')) + escaped = true; + else if (t_iseq(ptr, '"')) + quoted = true; + else if (is_quoted_char(ptr)) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("%s syntax error at character %d", datatype_name, pos), + errdetail("Unexpected character"))); + + for (ptr += charlen, ++*wlen; *ptr; ptr += charlen, ++*wlen) + { + charlen = pg_mblen(ptr); + + if (escaped) + { + ++*escaped_count; + escaped = false; + } + else if (t_iseq(ptr, '\\')) + escaped = true; + else if (quoted) + { + if (t_iseq(ptr, '"')) + { + quoted = false; + ptr += charlen; + ++*wlen; + break; + } + } + else if (is_quoted_char(ptr)) + break; + } + + if (quoted) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("%s syntax error at character %d", + datatype_name, pos), + errdetail("Unclosed quote"))); + + if (escaped) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("%s syntax error at character %d", + datatype_name, pos + *wlen - 1), + errdetail("Unclosed escape sequence"))); + + *len = ptr - str; + + return LTREE_TOK_LABEL; +} + +/* * expects a null terminated string * returns an ltree */ @@ -38,12 +348,13 @@ parse_ltree(const char *buf) const char *ptr; nodeitem *list, *lptr; - int num = 0, + int levels = 0, totallen = 0; int state = LTPRS_WAITNAME; ltree *result; ltree_level *curlevel; - int charlen; + int len; + int wlen; int pos = 1; /* character position for error messages */ #define UNCHAR ereport(ERROR, \ @@ -52,64 +363,51 @@ parse_ltree(const char *buf) pos)) ptr = buf; - while (*ptr) - { - charlen = pg_mblen(ptr); - if (t_iseq(ptr, '.')) - num++; - ptr += charlen; - } + count_parts_ors(ptr, &levels, NULL); - if (num + 1 > LTREE_MAX_LEVELS) + if (levels > LTREE_MAX_LEVELS) ereport(ERROR, (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), errmsg("number of ltree labels (%d) exceeds the maximum allowed (%d)", - num + 1, LTREE_MAX_LEVELS))); - list = lptr = (nodeitem *) palloc(sizeof(nodeitem) * (num + 1)); - ptr = buf; - while (*ptr) + levels, LTREE_MAX_LEVELS))); + list = lptr = (nodeitem *) palloc(sizeof(nodeitem) * (levels)); + + /* + * This block calculates single nodes' settings + */ + for (ptr = buf; *ptr; ptr += len, pos += wlen) { - charlen = pg_mblen(ptr); + int escaped_count; + ltree_token tok = ltree_get_token(ptr, "ltree", pos, + &len, &wlen, &escaped_count); + + if (tok == LTREE_TOK_SPACE) + continue; switch (state) { case LTPRS_WAITNAME: - if (ISALNUM(ptr)) - { - lptr->start = ptr; - lptr->wlen = 0; - state = LTPRS_WAITDELIM; - } - else + if (tok != LTREE_TOK_LABEL) UNCHAR; + + finish_nodeitem(lptr, ptr, len, wlen, escaped_count, false, pos); + totallen += MAXALIGN(lptr->len + LEVEL_HDRSIZE); + lptr++; + + state = LTPRS_WAITDELIM; break; case LTPRS_WAITDELIM: - if (t_iseq(ptr, '.')) - { - finish_nodeitem(lptr, ptr, false, pos); - totallen += MAXALIGN(lptr->len + LEVEL_HDRSIZE); - lptr++; - state = LTPRS_WAITNAME; - } - else if (!ISALNUM(ptr)) + if (tok != LTREE_TOK_DOT) UNCHAR; + + state = LTPRS_WAITNAME; break; default: elog(ERROR, "internal error in ltree parser"); } - - ptr += charlen; - lptr->wlen++; - pos++; } - if (state == LTPRS_WAITDELIM) - { - finish_nodeitem(lptr, ptr, false, pos); - totallen += MAXALIGN(lptr->len + LEVEL_HDRSIZE); - lptr++; - } - else if (!(state == LTPRS_WAITNAME && lptr == list)) + if (state == LTPRS_WAITNAME && lptr != list) /* Empty string */ ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("ltree syntax error"), @@ -123,7 +421,10 @@ parse_ltree(const char *buf) while (lptr - list < result->numlevel) { curlevel->len = (uint16) lptr->len; - memcpy(curlevel->name, lptr->start, lptr->len); + + if (lptr->len > 0) + copy_unescaped(curlevel->name, lptr->start, lptr->len); + curlevel = LEVEL_NEXT(curlevel); lptr++; } @@ -142,21 +443,40 @@ static char * deparse_ltree(const ltree *in) { char *buf, + *end, *ptr; int i; ltree_level *curlevel; + Size allocated = VARSIZE(in); - ptr = buf = (char *) palloc(VARSIZE(in)); + ptr = buf = (char *) palloc(allocated); + end = buf + allocated; curlevel = LTREE_FIRST(in); + for (i = 0; i < in->numlevel; i++) { + int extra_bytes = extra_bytes_for_escaping(curlevel->name, + curlevel->len); + int level_len = curlevel->len + extra_bytes; + + if (ptr + level_len + 1 >= end) + { + char *old_buf = buf; + + allocated += (level_len + 1) * 2; + buf = repalloc(buf, allocated); + ptr = buf + (ptr - old_buf); + } + if (i != 0) { *ptr = '.'; ptr++; } - memcpy(ptr, curlevel->name, curlevel->len); - ptr += curlevel->len; + + copy_level(ptr, curlevel->name, curlevel->len, extra_bytes); + ptr += level_len; + curlevel = LEVEL_NEXT(curlevel); } @@ -262,7 +582,7 @@ static lquery * parse_lquery(const char *buf) { const char *ptr; - int num = 0, + int levels = 0, totallen = 0, numOR = 0; int state = LQPRS_WAITLEVEL; @@ -274,121 +594,128 @@ parse_lquery(const char *buf) lquery_variant *lrptr = NULL; bool hasnot = false; bool wasbad = false; - int charlen; + int real_levels = 0; int pos = 1; /* character position for error messages */ + int wlen; /* token length in characters */ + int len; /* token length in bytes */ #define UNCHAR ereport(ERROR, \ errcode(ERRCODE_SYNTAX_ERROR), \ errmsg("lquery syntax error at character %d", \ pos)) - ptr = buf; - while (*ptr) - { - charlen = pg_mblen(ptr); + count_parts_ors(buf, &levels, &numOR); - if (t_iseq(ptr, '.')) - num++; - else if (t_iseq(ptr, '|')) - numOR++; - - ptr += charlen; - } - - num++; - if (num > LQUERY_MAX_LEVELS) + if (levels > LQUERY_MAX_LEVELS) ereport(ERROR, (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), errmsg("number of lquery items (%d) exceeds the maximum allowed (%d)", - num, LQUERY_MAX_LEVELS))); - curqlevel = tmpql = (lquery_level *) palloc0(ITEMSIZE * num); - ptr = buf; - while (*ptr) + levels, LQUERY_MAX_LEVELS))); + curqlevel = tmpql = (lquery_level *) palloc0(ITEMSIZE * levels); + + for (ptr = buf; *ptr; ptr += len, pos += wlen) { - charlen = pg_mblen(ptr); + int escaped_count; + ltree_token tok = ltree_get_token(ptr, "lquery", pos, + &len, &wlen, &escaped_count); switch (state) { case LQPRS_WAITLEVEL: - if (ISALNUM(ptr)) + if (tok == LTREE_TOK_SPACE) + break; + + if (tok == LTREE_TOK_NOT) { - GETVAR(curqlevel) = lptr = (nodeitem *) palloc0(sizeof(nodeitem) * (numOR + 1)); - lptr->start = ptr; - state = LQPRS_WAITDELIM; - curqlevel->numvar = 1; + if (curqlevel->flag & LQL_NOT) /* '!!' is disallowed */ + UNCHAR; + + curqlevel->flag |= LQL_NOT; + hasnot = true; + break; } - else if (t_iseq(ptr, '!')) + + real_levels++; + + GETVAR(curqlevel) = lptr = (nodeitem *) palloc0(sizeof(nodeitem) * numOR); + + if (tok == LTREE_TOK_LABEL) { - GETVAR(curqlevel) = lptr = (nodeitem *) palloc0(sizeof(nodeitem) * (numOR + 1)); - lptr->start = ptr + 1; - lptr->wlen = -1; /* compensate for counting ! below */ - state = LQPRS_WAITDELIM; curqlevel->numvar = 1; - curqlevel->flag |= LQL_NOT; - hasnot = true; + finish_nodeitem(lptr, ptr, len, wlen, escaped_count, true, pos); + state = LQPRS_WAITDELIM; } - else if (t_iseq(ptr, '*')) + else if (tok == LTREE_TOK_ASTERISK) + { + if (curqlevel->flag & LQL_NOT) /* '!*' is meaningless */ + UNCHAR; + + lptr->start = ptr; + + curqlevel->low = 0; + curqlevel->high = LTREE_MAX_LEVELS; + state = LQPRS_WAITOPEN; + } else UNCHAR; + break; case LQPRS_WAITVAR: - if (ISALNUM(ptr)) - { - lptr++; - lptr->start = ptr; - state = LQPRS_WAITDELIM; - curqlevel->numvar++; - } - else + if (tok == LTREE_TOK_SPACE) + break; + + if (tok != LTREE_TOK_LABEL) UNCHAR; + + curqlevel->numvar++; + + lptr++; + finish_nodeitem(lptr, ptr, len, wlen, escaped_count, true, pos); + + state = LQPRS_WAITDELIM; break; case LQPRS_WAITDELIM: - if (t_iseq(ptr, '@')) + if (tok == LTREE_TOK_SPACE) + break; + else if (tok == LTREE_TOK_AT) { lptr->flag |= LVAR_INCASE; curqlevel->flag |= LVAR_INCASE; } - else if (t_iseq(ptr, '*')) + else if (tok == LTREE_TOK_ASTERISK) { lptr->flag |= LVAR_ANYEND; curqlevel->flag |= LVAR_ANYEND; } - else if (t_iseq(ptr, '%')) + else if (tok == LTREE_TOK_PERCENT) { lptr->flag |= LVAR_SUBLEXEME; curqlevel->flag |= LVAR_SUBLEXEME; } - else if (t_iseq(ptr, '|')) + else if (tok == LTREE_TOK_OR) { - finish_nodeitem(lptr, ptr, true, pos); state = LQPRS_WAITVAR; } - else if (t_iseq(ptr, '{')) + else if (tok == LTREE_TOK_LBRACE) { - finish_nodeitem(lptr, ptr, true, pos); curqlevel->flag |= LQL_COUNT; state = LQPRS_WAITFNUM; } - else if (t_iseq(ptr, '.')) + else if (tok == LTREE_TOK_DOT) { - finish_nodeitem(lptr, ptr, true, pos); state = LQPRS_WAITLEVEL; curqlevel = NEXTLEV(curqlevel); } - else if (ISALNUM(ptr)) - { - /* disallow more chars after a flag */ - if (lptr->flag) - UNCHAR; - } else UNCHAR; break; case LQPRS_WAITOPEN: - if (t_iseq(ptr, '{')) + if (tok == LTREE_TOK_SPACE) + break; + else if (tok == LTREE_TOK_LBRACE) state = LQPRS_WAITFNUM; - else if (t_iseq(ptr, '.')) + else if (tok == LTREE_TOK_DOT) { /* We only get here for '*', so these are correct defaults */ curqlevel->low = 0; @@ -400,7 +727,7 @@ parse_lquery(const char *buf) UNCHAR; break; case LQPRS_WAITFNUM: - if (t_iseq(ptr, ',')) + if (tok == LTREE_TOK_COMMA) state = LQPRS_WAITSNUM; else if (t_isdigit(ptr)) { @@ -414,6 +741,7 @@ parse_lquery(const char *buf) low, LTREE_MAX_LEVELS, pos))); curqlevel->low = (uint16) low; + len = wlen = 1; state = LQPRS_WAITND; } else @@ -423,7 +751,7 @@ parse_lquery(const char *buf) if (t_isdigit(ptr)) { int high = atoi(ptr); - + if (high < 0 || high > LTREE_MAX_LEVELS) ereport(ERROR, (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), @@ -438,9 +766,10 @@ parse_lquery(const char *buf) curqlevel->low, high, pos))); curqlevel->high = (uint16) high; + len = wlen = 1; state = LQPRS_WAITCLOSE; } - else if (t_iseq(ptr, '}')) + else if (tok == LTREE_TOK_RBRACE) { curqlevel->high = LTREE_MAX_LEVELS; state = LQPRS_WAITEND; @@ -449,24 +778,24 @@ parse_lquery(const char *buf) UNCHAR; break; case LQPRS_WAITCLOSE: - if (t_iseq(ptr, '}')) + if (tok == LTREE_TOK_RBRACE) state = LQPRS_WAITEND; else if (!t_isdigit(ptr)) UNCHAR; break; case LQPRS_WAITND: - if (t_iseq(ptr, '}')) + if (tok == LTREE_TOK_RBRACE) { curqlevel->high = curqlevel->low; state = LQPRS_WAITEND; } - else if (t_iseq(ptr, ',')) + else if (tok == LTREE_TOK_COMMA) state = LQPRS_WAITSNUM; else if (!t_isdigit(ptr)) UNCHAR; break; case LQPRS_WAITEND: - if (t_iseq(ptr, '.')) + if (tok == LTREE_TOK_DOT) { state = LQPRS_WAITLEVEL; curqlevel = NEXTLEV(curqlevel); @@ -477,18 +806,11 @@ parse_lquery(const char *buf) default: elog(ERROR, "internal error in lquery parser"); } - - ptr += charlen; - if (state == LQPRS_WAITDELIM) - lptr->wlen++; - pos++; } - if (state == LQPRS_WAITDELIM) - finish_nodeitem(lptr, ptr, true, pos); - else if (state == LQPRS_WAITOPEN) - curqlevel->high = LTREE_MAX_LEVELS; - else if (state != LQPRS_WAITEND) + if (state != LQPRS_WAITDELIM && + state != LQPRS_WAITOPEN && + state != LQPRS_WAITEND) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("lquery syntax error"), @@ -496,7 +818,7 @@ parse_lquery(const char *buf) curqlevel = tmpql; totallen = LQUERY_HDRSIZE; - while ((char *) curqlevel - (char *) tmpql < num * ITEMSIZE) + while ((char *) curqlevel - (char *) tmpql < levels * ITEMSIZE) { totallen += LQL_HDRSIZE; if (curqlevel->numvar) @@ -513,14 +835,14 @@ parse_lquery(const char *buf) result = (lquery *) palloc0(totallen); SET_VARSIZE(result, totallen); - result->numlevel = num; + result->numlevel = real_levels; result->firstgood = 0; result->flag = 0; if (hasnot) result->flag |= LQUERY_HASNOT; cur = LQUERY_FIRST(result); curqlevel = tmpql; - while ((char *) curqlevel - (char *) tmpql < num * ITEMSIZE) + while ((char *) curqlevel - (char *) tmpql < levels * ITEMSIZE) { memcpy(cur, curqlevel, LQL_HDRSIZE); cur->totallen = LQL_HDRSIZE; @@ -533,8 +855,8 @@ parse_lquery(const char *buf) cur->totallen += MAXALIGN(LVAR_HDRSIZE + lptr->len); lrptr->len = lptr->len; lrptr->flag = lptr->flag; - lrptr->val = ltree_crc32_sz(lptr->start, lptr->len); - memcpy(lrptr->name, lptr->start, lptr->len); + copy_unescaped(lrptr->name, lptr->start, lptr->len); + lrptr->val = ltree_crc32_sz(lrptr->name, lptr->len); lptr++; lrptr = LVAR_NEXT(lrptr); } @@ -568,29 +890,38 @@ parse_lquery(const char *buf) /* * Close out parsing an ltree or lquery nodeitem: * compute the correct length, and complain if it's not OK + * + * "len" - length of label in bytes + * "wlen" - length of label in wide characters + * "escapes" - number of escaped characters in label + * "pos" - position of label in the input string in wide characters */ static void -finish_nodeitem(nodeitem *lptr, const char *ptr, bool is_lquery, int pos) +finish_nodeitem(nodeitem *lptr, const char *ptr, int len, int wlen, int escapes, + bool is_lquery, int pos) { - if (is_lquery) + lptr->start = ptr; + + /* + * Exclude escape symbols from the length, because the labels stored + * unescaped. + */ + lptr->len = len - escapes; + lptr->wlen = wlen - escapes; + + /* + * If it is a quoted label, then we have to move start a byte ahead and + * exclude beginning and final quotes from the label itself. + */ + if (t_iseq(lptr->start, '"')) { - /* - * Back up over any flag characters, and discount them from length and - * position. - */ - while (ptr > lptr->start && strchr("@*%", ptr[-1]) != NULL) - { - ptr--; - lptr->wlen--; - pos--; - } + lptr->start++; + lptr->len -= 2; + lptr->wlen -= 2; } - /* Now compute the byte length, which we weren't tracking before. */ - lptr->len = ptr - lptr->start; - /* Complain if it's empty or too long */ - if (lptr->len == 0) + if (lptr->len <= 0 || lptr->wlen <= 0) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), is_lquery ? @@ -616,7 +947,9 @@ deparse_lquery(const lquery *in) *ptr; int i, j, + var_num = 0, totallen = 1; + int *var_extra_bytes; /* per-var extra bytes for escaping */ lquery_level *curqlevel; lquery_variant *curtlevel; @@ -624,6 +957,8 @@ deparse_lquery(const lquery *in) for (i = 0; i < in->numlevel; i++) { totallen++; + var_num += curqlevel->numvar; + if (curqlevel->numvar) { totallen += 1 + (curqlevel->numvar * 4) + curqlevel->totallen; @@ -631,11 +966,40 @@ deparse_lquery(const lquery *in) totallen += 2 * 11 + 3; } else - totallen += 2 * 11 + 4; + totallen += 2 * 11 + 4; /* length of "*{%d,%d}" */ + + curqlevel = LQL_NEXT(curqlevel); + } + + /* count extra bytes needed for escaping */ + var_extra_bytes = palloc(sizeof(*var_extra_bytes) * var_num); + + var_num = 0; + curqlevel = LQUERY_FIRST(in); + + for (i = 0; i < in->numlevel; i++) + { + if (curqlevel->numvar) + { + curtlevel = LQL_FIRST(curqlevel); + + for (j = 0; j < curqlevel->numvar; j++, var_num++) + { + int extra_bytes = + extra_bytes_for_escaping(curtlevel->name, curtlevel->len); + + var_extra_bytes[var_num] = extra_bytes; + totallen += extra_bytes; + + curtlevel = LVAR_NEXT(curtlevel); + } + } + curqlevel = LQL_NEXT(curqlevel); } ptr = buf = (char *) palloc(totallen); + var_num = 0; curqlevel = LQUERY_FIRST(in); for (i = 0; i < in->numlevel; i++) { @@ -652,15 +1016,20 @@ deparse_lquery(const lquery *in) ptr++; } curtlevel = LQL_FIRST(curqlevel); - for (j = 0; j < curqlevel->numvar; j++) + for (j = 0; j < curqlevel->numvar; j++, var_num++) { + int extra_bytes = var_extra_bytes[var_num]; + if (j != 0) { *ptr = '|'; ptr++; } - memcpy(ptr, curtlevel->name, curtlevel->len); - ptr += curtlevel->len; + + Assert(ptr + curtlevel->len + extra_bytes < buf + totallen); + copy_level(ptr, curtlevel->name, curtlevel->len, extra_bytes); + ptr += curtlevel->len + extra_bytes; + if ((curtlevel->flag & LVAR_SUBLEXEME)) { *ptr = '%'; @@ -718,6 +1087,8 @@ deparse_lquery(const lquery *in) curqlevel = LQL_NEXT(curqlevel); } + pfree(var_extra_bytes); + *ptr = '\0'; return buf; } diff --git a/contrib/ltree/ltxtquery_io.c b/contrib/ltree/ltxtquery_io.c index d967f92..cbc293d 100644 --- a/contrib/ltree/ltxtquery_io.c +++ b/contrib/ltree/ltxtquery_io.c @@ -35,6 +35,7 @@ typedef struct NODE typedef struct { char *buf; + int pos; int32 state; int32 count; /* reverse polish notation in list (for temporary usage) */ @@ -53,87 +54,100 @@ typedef struct * get token from query string */ static int32 -gettoken_query(QPRS_STATE *state, int32 *val, int32 *lenval, char **strval, uint16 *flag) +gettoken_query(QPRS_STATE *state, int32 *val, int32 *lenval, char **strval, + uint16 *flag) { - int charlen; - for (;;) { - charlen = pg_mblen(state->buf); + int len; + int wlen; + int pos = state->pos; + char *buf = state->buf; + int escaped_cnt; + ltree_token tok = ltree_get_token(buf, "ltxtquery", pos, + &len, &wlen, &escaped_cnt); + + state->buf += len; + state->pos += wlen; switch (state->state) { case WAITOPERAND: - if (charlen == 1 && t_iseq(state->buf, '!')) + if (tok == LTREE_TOK_NOT) { - (state->buf)++; *val = (int32) '!'; return OPR; } - else if (charlen == 1 && t_iseq(state->buf, '(')) + else if (tok == LTREE_TOK_LPAREN) { state->count++; - (state->buf)++; return OPEN; } - else if (ISALNUM(state->buf)) + else if (tok == LTREE_TOK_LABEL) { - state->state = INOPERAND; - *strval = state->buf; - *lenval = charlen; + *strval = buf; + *lenval = len - escaped_cnt; *flag = 0; + + if (t_iseq(buf, '"')) /* strip quotes */ + { + *lenval -= 2; + *strval += 1; + } + + state->state = INOPERAND; + } + else if (tok == LTREE_TOK_SPACE) + { + /* do nothing */ } - else if (!t_isspace(state->buf)) + else ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("operand syntax error"))); + errmsg("ltxtquery syntax error at character %d", pos), + errdetail("Unquoted special symbol"))); break; + case INOPERAND: - if (ISALNUM(state->buf)) + if (tok == LTREE_TOK_END || tok == LTREE_TOK_SPACE) { - if (*flag) - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("modifiers syntax error"))); - *lenval += charlen; + state->state = WAITOPERATOR; + return VAL; } - else if (charlen == 1 && t_iseq(state->buf, '%')) + else if (tok == LTREE_TOK_PERCENT) *flag |= LVAR_SUBLEXEME; - else if (charlen == 1 && t_iseq(state->buf, '@')) + else if (tok == LTREE_TOK_AT) *flag |= LVAR_INCASE; - else if (charlen == 1 && t_iseq(state->buf, '*')) + else if (tok == LTREE_TOK_ASTERISK) *flag |= LVAR_ANYEND; else - { - state->state = WAITOPERATOR; - return VAL; - } + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("ltxtquery syntax error at character %d", pos), + errdetail("Unquoted special symbol"))); break; + case WAITOPERATOR: - if (charlen == 1 && (t_iseq(state->buf, '&') || t_iseq(state->buf, '|'))) + if (tok == LTREE_TOK_OR || tok == LTREE_TOK_AND) { state->state = WAITOPERAND; - *val = (int32) *(state->buf); - (state->buf)++; + *val = (int32) *buf; return OPR; } - else if (charlen == 1 && t_iseq(state->buf, ')')) + else if (tok == LTREE_TOK_RPAREN) { - (state->buf)++; state->count--; return (state->count < 0) ? ERR : CLOSE; } - else if (*(state->buf) == '\0') + else if (tok == LTREE_TOK_END) return (state->count) ? ERR : END; - else if (charlen == 1 && !t_iseq(state->buf, ' ')) + else if (tok != LTREE_TOK_SPACE) return ERR; break; + default: return ERR; - break; } - - state->buf += charlen; } } @@ -169,23 +183,30 @@ pushquery(QPRS_STATE *state, int32 type, int32 val, int32 distance, int32 lenval static void pushval_asis(QPRS_STATE *state, int type, char *strval, int lenval, uint16 flag) { + if (lenval <= 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("empty labels are forbidden"))); + if (lenval > 0xffff) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("word is too long"))); - pushquery(state, type, ltree_crc32_sz(strval, lenval), - state->curop - state->op, lenval, flag); - while (state->curop - state->op + lenval + 1 >= state->lenop) { int32 tmp = state->curop - state->op; state->lenop *= 2; - state->op = (char *) repalloc((void *) state->op, state->lenop); + state->op = (char *) repalloc(state->op, state->lenop); state->curop = state->op + tmp; } - memcpy((void *) state->curop, (void *) strval, lenval); + + copy_unescaped(state->curop, strval, lenval); + + pushquery(state, type, ltree_crc32_sz(state->curop, lenval), + state->curop - state->op, lenval, flag); + state->curop += lenval; *(state->curop) = '\0'; state->curop++; @@ -322,6 +343,7 @@ queryin(char *buf) /* init state */ state.buf = buf; + state.pos = 1; state.state = WAITOPERAND; state.count = 0; state.num = 0; @@ -448,14 +470,14 @@ infix(INFIX *in, bool first) if (in->curpol->type == VAL) { char *op = in->op + in->curpol->distance; + char *opend = strchr(op, '\0'); + int delta = opend - op; + int extra_bytes = extra_bytes_for_escaping(op, delta); RESIZEBUF(in, in->curpol->length * 2 + 5); - while (*op) - { - *(in->cur) = *op; - op++; - in->cur++; - } + copy_level(in->cur, op, delta, extra_bytes); + in->cur += delta + extra_bytes; + if (in->curpol->flag & LVAR_SUBLEXEME) { *(in->cur) = '%'; diff --git a/contrib/ltree/sql/ltree.sql b/contrib/ltree/sql/ltree.sql index bf733ed..4229040 100644 --- a/contrib/ltree/sql/ltree.sql +++ b/contrib/ltree/sql/ltree.sql @@ -1,5 +1,7 @@ CREATE EXTENSION ltree; +SET standard_conforming_strings=on; + -- Check whether any of our opclasses fail amvalidate SELECT amname, opcname FROM pg_opclass opc LEFT JOIN pg_am am ON am.oid = opcmethod @@ -73,6 +75,7 @@ SELECT lca('1.2.2.3','1.2.3.4.5.6','2'); SELECT lca('1.2.2.3','1.2.3.4.5.6','1'); +SELECT ''::lquery; SELECT '1'::lquery; SELECT '4|3|2'::lquery; SELECT '1.2'::lquery; @@ -97,6 +100,8 @@ SELECT '1.*.4|3|2.*{1,}'::lquery; SELECT '1.*.4|3|2.*{1}'::lquery; SELECT 'foo.bar{,}.!a*|b{1,}.c{,44}.d{3,4}'::lquery; SELECT 'foo*@@*'::lquery; +SELECT '*'::lquery; +SELECT '*{1}|2'::lquery; SELECT 'qwerty%@*.tu'::lquery; -- empty labels not allowed @@ -382,3 +387,265 @@ SELECT count(*) FROM _ltreetest WHERE t ~ '23.*{1}.1' ; SELECT count(*) FROM _ltreetest WHERE t ~ '23.*.1' ; SELECT count(*) FROM _ltreetest WHERE t ~ '23.*.2' ; SELECT count(*) FROM _ltreetest WHERE t ? '{23.*.1,23.*.2}' ; + +-- Extended syntax, escaping, quoting etc +-- success +SELECT E'\\.'::ltree; +SELECT E'\\ '::ltree; +SELECT E'\\\\'::ltree; +SELECT E'\\a'::ltree; +SELECT E'\\n'::ltree; +SELECT E'x\\\\'::ltree; +SELECT E'x\\ '::ltree; +SELECT E'x\\.'::ltree; +SELECT E'x\\a'::ltree; +SELECT E'x\\n'::ltree; +SELECT 'a b.с d'::ltree; +SELECT '"a b"."с d"'::ltree; +SELECT ' e . f '::ltree; +SELECT ' '::ltree; + +SELECT E'\\ g . h\\ '::ltree; +SELECT E'\\ g'::ltree; +SELECT E' h\\ '::ltree; +SELECT '"g" '::ltree; +SELECT '"g" . h'::ltree; +SELECT '" g "." h "'::ltree; +SELECT '" g " '::ltree; +SELECT '" g " ." h " '::ltree; + +SELECT nlevel(E'Bottom\\.Test'::ltree); +SELECT subpath(E'Bottom\\.'::ltree, 0, 1); + +SELECT subpath(E'a\\.b', 0, 1); +SELECT subpath(E'a\\..b', 1, 1); +SELECT subpath(E'a\\..\\b', 1, 1); +SELECT subpath(E'"a b"."с d"'::ltree, 1, 1); + +SELECT( +'01234567890123456789012345678901234567890123456789' || +'01234567890123456789012345678901234567890123456789' || +'01234567890123456789012345678901234567890123456789' || +'01234567890123456789012345678901234567890123456789' || +'01234567890123456789012345678901234567890123456789' || +'\z\z\z\z\z')::ltree; + +SELECT(' ' || +'01234567890123456789012345678901234567890123456789' || +'01234567890123456789012345678901234567890123456789' || +'01234567890123456789012345678901234567890123456789' || +'01234567890123456789012345678901234567890123456789' || +'01234567890123456789012345678901234567890123456789' || +'\a\b\c\d\e ')::ltree; + +SELECT 'abc\|d'::lquery; +SELECT 'abc\|d'::ltree ~ 'abc\|d'::lquery; +SELECT 'abc\|d'::ltree ~ 'abc*'::lquery; --true +SELECT 'abc\|d'::ltree ~ 'abc\*'::lquery; --false +SELECT E'abc\\|\\.'::ltree ~ 'abc\|*'::lquery; --true + +SELECT E'"\\""'::ltree; +SELECT '\"'::ltree; +SELECT E'\\"'::ltree; +SELECT 'a\"b'::ltree; +SELECT '"ab"'::ltree; +SELECT '"."'::ltree; +SELECT E'".\\""'::ltree; +SELECT( +'"01234567890123456789012345678901234567890123456789' || +'01234567890123456789012345678901234567890123456789' || +'01234567890123456789012345678901234567890123456789' || +'01234567890123456789012345678901234567890123456789' || +'01234567890123456789012345678901234567890123456789' || +'\z\z\z\z\z"')::ltree; + +SELECT E'"\\""'::lquery; +SELECT '\"'::lquery; +SELECT E'\\"'::lquery; +SELECT 'a\"b'::lquery; +SELECT '"ab"'::lquery; +SELECT '"."'::lquery; +SELECT E'".\\""'::lquery; +SELECT( +'"01234567890123456789012345678901234567890123456789' || +'01234567890123456789012345678901234567890123456789' || +'01234567890123456789012345678901234567890123456789' || +'01234567890123456789012345678901234567890123456789' || +'01234567890123456789012345678901234567890123456789' || +'\z\z\z\z\z"')::lquery; + +SELECT ' e . f '::lquery; +SELECT ' e | f '::lquery; + +SELECT E'\\ g . h\\ '::lquery; +SELECT E'\\ g'::lquery; +SELECT E' h\\ '::lquery; +SELECT E'"\\ g"'::lquery; +SELECT E' "h\\ "'::lquery; +SELECT '" g "." h "'::lquery; + +SELECT E'\\ g | h\\ '::lquery; +SELECT '" g "|" h "'::lquery; + +SELECT '"g" '::lquery; +SELECT '"g" . h'::lquery; +SELECT '" g " '::lquery; +SELECT '" g " ." h " '::lquery; +SELECT '" g " | " h " '::lquery; + +SELECT(' ' || +'01234567890123456789012345678901234567890123456789' || +'01234567890123456789012345678901234567890123456789' || +'01234567890123456789012345678901234567890123456789' || +'01234567890123456789012345678901234567890123456789' || +'01234567890123456789012345678901234567890123456789' || +'\a\b\c\d\e ')::lquery; + +SELECT '"a!b"'::lquery; +SELECT '!"!b"'::lquery; +SELECT '!"{b"'::lquery; + +SELECT E'"a\\"b"'::lquery; +SELECT E'a\\"b'::lquery; +SELECT E'a\\*b'::lquery; +SELECT E'a\\.b'::lquery; + +SELECT E'!\\\\b'::lquery; +SELECT E'!\\@b'::lquery; + +SELECT '"1"'::lquery; +SELECT '"2.*"'::lquery; +SELECT '!"1"'::lquery; +SELECT '!"1|"'::lquery; +SELECT '4|3|"2"'::lquery; +SELECT '"1".2'::lquery; +SELECT '"1.4"|"3"|2'::lquery; +SELECT '"1"."4"|"3"|"2"'::lquery; +SELECT '"1"."0"'::lquery; +SELECT '"1".0'::lquery; +SELECT '"1".*'::lquery; +SELECT '4|"3"|2.*'::lquery; +SELECT '4|"3"|"2.*"'::lquery; +SELECT '2."*"'::lquery; +SELECT '"*".1."*"'::lquery; +SELECT '"*.4"|3|2.*'::lquery; +SELECT '"*.4"|3|"2.*"'::lquery; +SELECT '1.*.4|3|2.*{,4}'::lquery; +SELECT '1.*.4|3|2.*{1,}'::lquery; +SELECT '1.*.4|3|2.*{1}'::lquery; +SELECT '"qwerty"%@*.tu'::lquery; + +SELECT '1.*.4|3|"2".*{1,4}'::lquery; +SELECT '1."*".4|3|"2".*{1,4}'::lquery; +SELECT '\%\ \@'::lquery; +SELECT '"\% \@"'::lquery; + +SELECT E'\\aa.b.c.d.e'::ltree ~ 'A@.b.c.d.e'; +SELECT E'a\\a.b.c.\\d.e'::ltree ~ 'A*.b.c.d.e'; +SELECT E'a\\a.b.c.\\d.e'::ltree ~ E'A*@.b.c.d.\\e'; +SELECT E'a\\a.b.c.\\d.e'::ltree ~ E'A*@|\\g.b.c.d.e'; +--ltxtquery +SELECT '!"tree" & aWdf@*'::ltxtquery; +SELECT '"!tree" & aWdf@*'::ltxtquery; +SELECT E'tr\\ee'::ltree @ E'\\t\\r\\e\\e'::ltxtquery; +SELECT E'tr\\ee.awd\\fg'::ltree @ E'tre\\e & a\\Wdf@*'::ltxtquery; +SELECT 'tree & aw_qw%*'::ltxtquery; +SELECT 'tree."awdfg"'::ltree @ E'tree & a\\Wdf@*'::ltxtquery; +SELECT 'tree."awdfg"'::ltree @ E'tree & "a\\Wdf"@*'::ltxtquery; +SELECT 'tree.awdfg_qwerty'::ltree @ 'tree & aw_qw%*'::ltxtquery; +SELECT 'tree.awdfg_qwerty'::ltree @ 'tree & "aw_rw"%*'::ltxtquery; +SELECT 'tree.awdfg_qwerty'::ltree @ E'tree & "aw\\_qw"%*'::ltxtquery; +SELECT 'tree.awdfg_qwerty'::ltree @ E'tree & aw\\_qw%*'::ltxtquery; + +SELECT E'"a\\"b"'::ltxtquery; +SELECT '"a!b"'::ltxtquery; + +SELECT E'a\\"'::ltxtquery; +SELECT E'a\\!'::ltxtquery; +SELECT E'a\\"b'::ltxtquery; +SELECT E'a\\!b'::ltxtquery; +SELECT E'a\\%b'::ltxtquery; +SELECT E'\\"b'::ltxtquery; +SELECT E'\\*b'::ltxtquery; +SELECT E'"\\"b"'::ltxtquery; +SELECT E'"a\\""'::ltxtquery; + +SELECT '"!b" | "%b"'::ltxtquery; +SELECT '"a!" | "a%"'::ltxtquery; + +--failures +SELECT E'\\'::ltree; +SELECT E'n\\'::ltree; +SELECT '"'::ltree; +SELECT '"a'::ltree; +SELECT '""'::ltree; +SELECT 'a"b'::ltree; +SELECT E'\\"ab"'::ltree; +SELECT '"a"."a'::ltree; +SELECT '"a."a"'::ltree; +SELECT '"".a'::ltree; +SELECT 'a.""'::ltree; +SELECT '"".""'::ltree; +SELECT '""'::lquery; +SELECT '"".""'::lquery; +SELECT 'a.""'::lquery; +SELECT ' . '::ltree; +SELECT ' . '::lquery; +SELECT ' | '::lquery; + +SELECT( +'01234567890123456789012345678901234567890123456789' || +'01234567890123456789012345678901234567890123456789' || +'01234567890123456789012345678901234567890123456789' || +'01234567890123456789012345678901234567890123456789' || +'01234567890123456789012345678901234567890123456789' || +'z\z\z\z\z\z')::ltree; +SELECT( +'"01234567890123456789012345678901234567890123456789' || +'01234567890123456789012345678901234567890123456789' || +'01234567890123456789012345678901234567890123456789' || +'01234567890123456789012345678901234567890123456789' || +'01234567890123456789012345678901234567890123456789' || +'\z\z\z\z\z\z"')::ltree; + +SELECT '"'::lquery; +SELECT '"a'::lquery; +SELECT '"a"."a'::lquery; +SELECT '"a."a"'::lquery; + +SELECT E'\\"ab"'::lquery; +SELECT 'a"b'::lquery; +SELECT 'a!b'::lquery; +SELECT 'a{'::lquery; +SELECT '%b'::lquery; +SELECT '!*b'::lquery; + +SELECT '"foo"bar.baz'::lquery; +SELECT '"foo bar"@*.baz'::lquery; + +SELECT( +'"01234567890123456789012345678901234567890123456789' || +'01234567890123456789012345678901234567890123456789' || +'01234567890123456789012345678901234567890123456789' || +'01234567890123456789012345678901234567890123456789' || +'01234567890123456789012345678901234567890123456789' || +'\z\z\z\z\z\z"')::lquery; + +SELECT 'a | ""'::ltxtquery; +SELECT '"" & ""'::ltxtquery; +SELECT 'a.""'::ltxtquery; +SELECT '"'::ltxtquery; + +SELECT '"""'::ltxtquery; +SELECT '"a'::ltxtquery; +SELECT '"a" & "a'::ltxtquery; +SELECT '"a | "a"'::ltxtquery; +SELECT '"!tree" & aWdf@*"'::ltxtquery; + +SELECT 'a"b'::ltxtquery; +SELECT 'a!b'::ltxtquery; +SELECT 'a%b'::ltxtquery; +SELECT '"b'::ltxtquery; +SELECT '%b'::ltxtquery; +SELECT 'a"'::ltxtquery; +SELECT 'a!'::ltxtquery; diff --git a/doc/src/sgml/ltree.sgml b/doc/src/sgml/ltree.sgml index e342d45..e80eb64 100644 --- a/doc/src/sgml/ltree.sgml +++ b/doc/src/sgml/ltree.sgml @@ -23,14 +23,36 @@ Definitions - A label is a sequence of alphanumeric characters - and underscores (for example, in C locale the characters - A-Za-z0-9_ are allowed). - Labels must be less than 256 characters long. + A label is a sequence of characters. Labels must be + less than 256 characters long. Label may contain any character supported + by PostgreSQL except \0. + If label contains characters other than alphanumeric characters and + underscores, they should be escaped. + Escaping can be done with either by a preceding backslash (\\) + symbol or by wrapping the whole label in double quotes ("). + Initial and final unescaped whitespace is stripped. - Examples: 42, Personal_Services + Examples: 42, Personal_Services, + "This is a literal", Literal\\ with\\ spaces. + + + + During converting to internal representation, wrapping double quotes + and escaping backslashes are removed. During converting from internal + representation to text, if the label contain only alphanumeric characters + and underscores, it is printed as is. Otherwise, it is wrapped in quotes and, + if there are internal quotes or backslashes, they are escaped with backslashes. + + + + Examples: 42, "\\42", + \\4\\2, 42 and "42" + will have the same internal representation and, being + converted from internal representation, will become 42. + Literal abc def will turn into "abc + def". @@ -725,11 +747,13 @@ ltreetest=> SELECT ins_label(path,2,'Space') FROM test WHERE path <@ 'Top. Authors - All work was done by Teodor Sigaev (teodor@stack.net) and + Initial version was done by Teodor Sigaev (teodor@sigaev.ru) and Oleg Bartunov (oleg@sai.msu.su). See for additional information. Authors would like to thank Eugeny Rodichev for - helpful discussions. Comments and bug reports are welcome. + helpful discussions. Implementation of escaping syntax was done by Dmitry Belyavskiy + (beldmit@gmail.com) directed by Teodor Sigaev. + Comments and bug reports are welcome. -- 2.7.4 --------------C6F834EA887BEF501916689F--