Dear all,
Please find attached the patch extending the sets of symbols allowed in
ltree labels. The patch introduces 2 variants of escaping symbols, via
backslashing separate symbols and via quoting the labels in whole for
ltree, lquery and ltxtquery datatypes.
--
SY, Dmitry Belyavsky
diff --git a/contrib/ltree/expected/ltree.out b/contrib/ltree/expected/ltree.out
index 8226930905..5f45726229 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
@@ -7679,3 +7680,1587 @@ 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;
+ 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 "." 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 " | " h " '::lquery;
+ lquery
+--------------
+ " g "|" h "
+(1 row)
+
+SELECT(' ' ||
+'01234567890123456789012345678901234567890123456789' ||
+'01234567890123456789012345678901234567890123456789' ||
+'01234567890123456789012345678901234567890123456789' ||
+'01234567890123456789012345678901234567890123456789' ||
+'01234567890123456789012345678901234567890123456789' ||
+'\a\b\c\d\e ')::lquery;
+ lquery
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ 0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789abcde
+(1 row)
+
+SELECT E'"a\\"b"'::lquery;
+ lquery
+--------
+ "a\"b"
+(1 row)
+
+SELECT '"a!b"'::lquery;
+ lquery
+--------
+ "a!b"
+(1 row)
+
+SELECT '"a%b"'::lquery;
+ lquery
+--------
+ "a%b"
+(1 row)
+
+SELECT '"a*b"'::lquery;
+ lquery
+--------
+ "a*b"
+(1 row)
+
+SELECT '"a@b"'::lquery;
+ lquery
+--------
+ "a@b"
+(1 row)
+
+SELECT '"a{b"'::lquery;
+ lquery
+--------
+ "a{b"
+(1 row)
+
+SELECT '"a}b"'::lquery;
+ lquery
+--------
+ "a}b"
+(1 row)
+
+SELECT '"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'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'a\\|b'::lquery;
+ lquery
+--------
+ "a|b"
+(1 row)
+
+SELECT '!"!b"'::lquery;
+ lquery
+--------
+ !"!b"
+(1 row)
+
+SELECT '!"%b"'::lquery;
+ lquery
+--------
+ !"%b"
+(1 row)
+
+SELECT '!"*b"'::lquery;
+ lquery
+--------
+ !"*b"
+(1 row)
+
+SELECT '!"@b"'::lquery;
+ lquery
+--------
+ !"@b"
+(1 row)
+
+SELECT '!"{b"'::lquery;
+ lquery
+--------
+ !"{b"
+(1 row)
+
+SELECT '!"}b"'::lquery;
+ lquery
+--------
+ !"}b"
+(1 row)
+
+SELECT E'!\\!b'::lquery;
+ lquery
+--------
+ !"!b"
+(1 row)
+
+SELECT E'!\\%b'::lquery;
+ lquery
+--------
+ !"%b"
+(1 row)
+
+SELECT E'!\\*b'::lquery;
+ lquery
+--------
+ !"*b"
+(1 row)
+
+SELECT E'!\\@b'::lquery;
+ lquery
+--------
+ !"@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 ~ '[email protected]';
+ ?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 '"a%b"'::ltxtquery;
+ ltxtquery
+-----------
+ "a%b"
+(1 row)
+
+SELECT '"a*b"'::ltxtquery;
+ ltxtquery
+-----------
+ "a*b"
+(1 row)
+
+SELECT '"a@b"'::ltxtquery;
+ ltxtquery
+-----------
+ "a@b"
+(1 row)
+
+SELECT '"a{b"'::ltxtquery;
+ ltxtquery
+-----------
+ "a{b"
+(1 row)
+
+SELECT '"a}b"'::ltxtquery;
+ ltxtquery
+-----------
+ "a}b"
+(1 row)
+
+SELECT '"a|b"'::ltxtquery;
+ ltxtquery
+-----------
+ "a|b"
+(1 row)
+
+SELECT '"a&b"'::ltxtquery;
+ ltxtquery
+-----------
+ "a&b"
+(1 row)
+
+SELECT '"a(b"'::ltxtquery;
+ ltxtquery
+-----------
+ "a(b"
+(1 row)
+
+SELECT '"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'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'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'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 '"!b"'::ltxtquery;
+ ltxtquery
+-----------
+ "!b"
+(1 row)
+
+SELECT '"%b"'::ltxtquery;
+ ltxtquery
+-----------
+ "%b"
+(1 row)
+
+SELECT '"*b"'::ltxtquery;
+ ltxtquery
+-----------
+ "*b"
+(1 row)
+
+SELECT '"@b"'::ltxtquery;
+ ltxtquery
+-----------
+ "@b"
+(1 row)
+
+SELECT '"{b"'::ltxtquery;
+ ltxtquery
+-----------
+ "{b"
+(1 row)
+
+SELECT '"}b"'::ltxtquery;
+ ltxtquery
+-----------
+ "}b"
+(1 row)
+
+SELECT '"|b"'::ltxtquery;
+ ltxtquery
+-----------
+ "|b"
+(1 row)
+
+SELECT '"&b"'::ltxtquery;
+ ltxtquery
+-----------
+ "&b"
+(1 row)
+
+SELECT '"(b"'::ltxtquery;
+ ltxtquery
+-----------
+ "(b"
+(1 row)
+
+SELECT '")b"'::ltxtquery;
+ ltxtquery
+-----------
+ ")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'\\*b'::ltxtquery;
+ ltxtquery
+-----------
+ "*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'\\|b'::ltxtquery;
+ ltxtquery
+-----------
+ "|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 '"a!"'::ltxtquery;
+ ltxtquery
+-----------
+ "a!"
+(1 row)
+
+SELECT '"a%"'::ltxtquery;
+ ltxtquery
+-----------
+ "a%"
+(1 row)
+
+SELECT '"a*"'::ltxtquery;
+ ltxtquery
+-----------
+ "a*"
+(1 row)
+
+SELECT '"a@"'::ltxtquery;
+ ltxtquery
+-----------
+ "a@"
+(1 row)
+
+SELECT '"a{"'::ltxtquery;
+ ltxtquery
+-----------
+ "a{"
+(1 row)
+
+SELECT '"a}"'::ltxtquery;
+ ltxtquery
+-----------
+ "a}"
+(1 row)
+
+SELECT '"a|"'::ltxtquery;
+ ltxtquery
+-----------
+ "a|"
+(1 row)
+
+SELECT '"a&"'::ltxtquery;
+ ltxtquery
+-----------
+ "a&"
+(1 row)
+
+SELECT '"a("'::ltxtquery;
+ ltxtquery
+-----------
+ "a("
+(1 row)
+
+SELECT '"a)"'::ltxtquery;
+ ltxtquery
+-----------
+ "a)"
+(1 row)
+
+SELECT E'a\\"'::ltxtquery;
+ ltxtquery
+-----------
+ "a\""
+(1 row)
+
+SELECT E'a\\!'::ltxtquery;
+ ltxtquery
+-----------
+ "a!"
+(1 row)
+
+SELECT E'a\\%'::ltxtquery;
+ ltxtquery
+-----------
+ "a%"
+(1 row)
+
+SELECT E'a\\*'::ltxtquery;
+ ltxtquery
+-----------
+ "a*"
+(1 row)
+
+SELECT E'a\\@'::ltxtquery;
+ ltxtquery
+-----------
+ "a@"
+(1 row)
+
+SELECT E'a\\{'::ltxtquery;
+ ltxtquery
+-----------
+ "a{"
+(1 row)
+
+SELECT E'a\\}'::ltxtquery;
+ ltxtquery
+-----------
+ "a}"
+(1 row)
+
+SELECT E'a\\|'::ltxtquery;
+ ltxtquery
+-----------
+ "a|"
+(1 row)
+
+SELECT E'a\\&'::ltxtquery;
+ ltxtquery
+-----------
+ "a&"
+(1 row)
+
+SELECT E'a\\('::ltxtquery;
+ ltxtquery
+-----------
+ "a("
+(1 row)
+
+SELECT E'a\\)'::ltxtquery;
+ ltxtquery
+-----------
+ "a)"
+(1 row)
+
+--failures
+SELECT E'\\'::ltree;
+ERROR: syntax error
+LINE 1: SELECT E'\\'::ltree;
+ ^
+DETAIL: Unexpected end of line.
+SELECT E'n\\'::ltree;
+ERROR: syntax error
+LINE 1: SELECT E'n\\'::ltree;
+ ^
+DETAIL: Unexpected end of line.
+SELECT '"'::ltree;
+ERROR: syntax error
+LINE 1: SELECT '"'::ltree;
+ ^
+DETAIL: Unexpected end of line.
+SELECT '"a'::ltree;
+ERROR: syntax error
+LINE 1: SELECT '"a'::ltree;
+ ^
+DETAIL: Unexpected end of line.
+SELECT '""'::ltree;
+ERROR: name of level is empty
+LINE 1: SELECT '""'::ltree;
+ ^
+DETAIL: Name length is 0 in position 2.
+SELECT 'a"b'::ltree;
+ERROR: syntax error at position 1
+LINE 1: SELECT 'a"b'::ltree;
+ ^
+SELECT E'\\"ab"'::ltree;
+ERROR: syntax error at position 4
+LINE 1: SELECT E'\\"ab"'::ltree;
+ ^
+SELECT '"a"."a'::ltree;
+ERROR: syntax error
+LINE 1: SELECT '"a"."a'::ltree;
+ ^
+DETAIL: Unexpected end of line.
+SELECT '"a."a"'::ltree;
+ERROR: syntax error at position 4
+LINE 1: SELECT '"a."a"'::ltree;
+ ^
+SELECT '"".a'::ltree;
+ERROR: name of level is empty
+LINE 1: SELECT '"".a'::ltree;
+ ^
+DETAIL: Name length is 0 in position 2.
+SELECT 'a.""'::ltree;
+ERROR: name of level is empty
+LINE 1: SELECT 'a.""'::ltree;
+ ^
+DETAIL: Name length is 0 in position 4.
+SELECT '"".""'::ltree;
+ERROR: name of level is empty
+LINE 1: SELECT '"".""'::ltree;
+ ^
+DETAIL: Name length is 0 in position 2.
+SELECT '""'::lquery;
+ERROR: name of level is empty
+LINE 1: SELECT '""'::lquery;
+ ^
+DETAIL: Name length is 0 in position 2.
+SELECT '"".""'::lquery;
+ERROR: name of level is empty
+LINE 1: SELECT '"".""'::lquery;
+ ^
+DETAIL: Name length is 0 in position 2.
+SELECT 'a.""'::lquery;
+ERROR: name of level is empty
+LINE 1: SELECT 'a.""'::lquery;
+ ^
+DETAIL: Name length is 0 in position 4.
+SELECT ' . '::ltree;
+ERROR: syntax error at position 1
+LINE 1: SELECT ' . '::ltree;
+ ^
+SELECT ' . '::lquery;
+ERROR: syntax error at position 1
+LINE 1: SELECT ' . '::lquery;
+ ^
+SELECT ' | '::lquery;
+ERROR: syntax error at position 1
+LINE 1: SELECT ' | '::lquery;
+ ^
+SELECT(
+'01234567890123456789012345678901234567890123456789' ||
+'01234567890123456789012345678901234567890123456789' ||
+'01234567890123456789012345678901234567890123456789' ||
+'01234567890123456789012345678901234567890123456789' ||
+'01234567890123456789012345678901234567890123456789' ||
+'z\z\z\z\z\z')::ltree;
+ERROR: name of level is too long
+DETAIL: Name length is 256, must be < 256, in position 261.
+SELECT(
+'"01234567890123456789012345678901234567890123456789' ||
+'01234567890123456789012345678901234567890123456789' ||
+'01234567890123456789012345678901234567890123456789' ||
+'01234567890123456789012345678901234567890123456789' ||
+'01234567890123456789012345678901234567890123456789' ||
+'\z\z\z\z\z\z"')::ltree;
+ERROR: name of level is too long
+DETAIL: Name length is 256, must be < 256, in position 264.
+SELECT '"'::lquery;
+ERROR: syntax error
+LINE 1: SELECT '"'::lquery;
+ ^
+DETAIL: Unexpected end of line.
+SELECT '"a'::lquery;
+ERROR: syntax error
+LINE 1: SELECT '"a'::lquery;
+ ^
+DETAIL: Unexpected end of line.
+SELECT '"a"."a'::lquery;
+ERROR: syntax error
+LINE 1: SELECT '"a"."a'::lquery;
+ ^
+DETAIL: Unexpected end of line.
+SELECT '"a."a"'::lquery;
+ERROR: syntax error at position 4
+LINE 1: SELECT '"a."a"'::lquery;
+ ^
+SELECT E'\\"ab"'::lquery;
+ERROR: syntax error at position 4
+LINE 1: SELECT E'\\"ab"'::lquery;
+ ^
+SELECT 'a"b'::lquery;
+ERROR: syntax error at position 1
+LINE 1: SELECT 'a"b'::lquery;
+ ^
+SELECT 'a!b'::lquery;
+ERROR: syntax error at position 1
+LINE 1: SELECT 'a!b'::lquery;
+ ^
+SELECT 'a%b'::lquery;
+ERROR: syntax error at position 2
+LINE 1: SELECT 'a%b'::lquery;
+ ^
+SELECT 'a*b'::lquery;
+ERROR: syntax error at position 2
+LINE 1: SELECT 'a*b'::lquery;
+ ^
+SELECT 'a@b'::lquery;
+ERROR: syntax error at position 2
+LINE 1: SELECT 'a@b'::lquery;
+ ^
+SELECT 'a{b'::lquery;
+ERROR: syntax error at position 1
+LINE 1: SELECT 'a{b'::lquery;
+ ^
+SELECT 'a}b'::lquery;
+ERROR: syntax error at position 1
+LINE 1: SELECT 'a}b'::lquery;
+ ^
+SELECT 'a!'::lquery;
+ERROR: syntax error at position 1
+LINE 1: SELECT 'a!'::lquery;
+ ^
+SELECT 'a{'::lquery;
+ERROR: syntax error at position 1
+LINE 1: SELECT 'a{'::lquery;
+ ^
+SELECT 'a}'::lquery;
+ERROR: syntax error at position 1
+LINE 1: SELECT 'a}'::lquery;
+ ^
+SELECT '%b'::lquery;
+ERROR: syntax error at position 0
+LINE 1: SELECT '%b'::lquery;
+ ^
+SELECT '*b'::lquery;
+ERROR: syntax error at position 1
+LINE 1: SELECT '*b'::lquery;
+ ^
+SELECT '@b'::lquery;
+ERROR: syntax error at position 0
+LINE 1: SELECT '@b'::lquery;
+ ^
+SELECT '{b'::lquery;
+ERROR: syntax error at position 0
+LINE 1: SELECT '{b'::lquery;
+ ^
+SELECT '}b'::lquery;
+ERROR: syntax error at position 0
+LINE 1: SELECT '}b'::lquery;
+ ^
+SELECT '!%b'::lquery;
+ERROR: syntax error at position 1
+LINE 1: SELECT '!%b'::lquery;
+ ^
+SELECT '!*b'::lquery;
+ERROR: syntax error at position 1
+LINE 1: SELECT '!*b'::lquery;
+ ^
+SELECT '!@b'::lquery;
+ERROR: syntax error at position 1
+LINE 1: SELECT '!@b'::lquery;
+ ^
+SELECT '!{b'::lquery;
+ERROR: syntax error at position 1
+LINE 1: SELECT '!{b'::lquery;
+ ^
+SELECT '!}b'::lquery;
+ERROR: syntax error at position 1
+LINE 1: SELECT '!}b'::lquery;
+ ^
+SELECT '"qwert"y.tu'::lquery;
+ERROR: syntax error at position 7
+LINE 1: SELECT '"qwert"y.tu'::lquery;
+ ^
+SELECT 'q"wert"y"%@*.tu'::lquery;
+ERROR: syntax error at position 1
+LINE 1: SELECT 'q"wert"y"%@*.tu'::lquery;
+ ^
+SELECT(
+'"01234567890123456789012345678901234567890123456789' ||
+'01234567890123456789012345678901234567890123456789' ||
+'01234567890123456789012345678901234567890123456789' ||
+'01234567890123456789012345678901234567890123456789' ||
+'01234567890123456789012345678901234567890123456789' ||
+'\z\z\z\z\z\z"')::lquery;
+ERROR: name of level is too long
+DETAIL: Name length is 256, must be < 256, in position 264.
+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: escaping syntax error
+LINE 1: SELECT 'a.""'::ltxtquery;
+ ^
+SELECT '"'::ltxtquery;
+ERROR: escaping syntax error
+LINE 1: SELECT '"'::ltxtquery;
+ ^
+SELECT '"""'::ltxtquery;
+ERROR: escaping syntax error
+LINE 1: SELECT '"""'::ltxtquery;
+ ^
+SELECT '"a'::ltxtquery;
+ERROR: escaping syntax error
+LINE 1: SELECT '"a'::ltxtquery;
+ ^
+SELECT '"a" & "a'::ltxtquery;
+ERROR: escaping syntax error
+LINE 1: SELECT '"a" & "a'::ltxtquery;
+ ^
+SELECT '"a | "a"'::ltxtquery;
+ERROR: escaping syntax error
+LINE 1: SELECT '"a | "a"'::ltxtquery;
+ ^
+SELECT '"!tree" & aWdf@*"'::ltxtquery;
+ERROR: modifiers syntax error
+LINE 1: SELECT '"!tree" & aWdf@*"'::ltxtquery;
+ ^
+SELECT 'a"b'::ltxtquery;
+ERROR: escaping syntax error
+LINE 1: SELECT 'a"b'::ltxtquery;
+ ^
+SELECT 'a!b'::ltxtquery;
+ERROR: unquoted special symbol
+LINE 1: SELECT 'a!b'::ltxtquery;
+ ^
+SELECT 'a%b'::ltxtquery;
+ERROR: modifiers syntax error
+LINE 1: SELECT 'a%b'::ltxtquery;
+ ^
+SELECT 'a*b'::ltxtquery;
+ERROR: modifiers syntax error
+LINE 1: SELECT 'a*b'::ltxtquery;
+ ^
+SELECT 'a@b'::ltxtquery;
+ERROR: modifiers syntax error
+LINE 1: SELECT 'a@b'::ltxtquery;
+ ^
+SELECT 'a{b'::ltxtquery;
+ERROR: unquoted special symbol
+LINE 1: SELECT 'a{b'::ltxtquery;
+ ^
+SELECT 'a}b'::ltxtquery;
+ERROR: unquoted special symbol
+LINE 1: SELECT 'a}b'::ltxtquery;
+ ^
+SELECT 'a|b'::ltxtquery;
+ERROR: unquoted special symbol
+LINE 1: SELECT 'a|b'::ltxtquery;
+ ^
+SELECT 'a&b'::ltxtquery;
+ERROR: unquoted special symbol
+LINE 1: SELECT 'a&b'::ltxtquery;
+ ^
+SELECT 'a(b'::ltxtquery;
+ERROR: unquoted special symbol
+LINE 1: SELECT 'a(b'::ltxtquery;
+ ^
+SELECT 'a)b'::ltxtquery;
+ERROR: unquoted special symbol
+LINE 1: SELECT 'a)b'::ltxtquery;
+ ^
+SELECT '"b'::ltxtquery;
+ERROR: escaping syntax error
+LINE 1: SELECT '"b'::ltxtquery;
+ ^
+SELECT '%b'::ltxtquery;
+ERROR: unquoted special symbol
+LINE 1: SELECT '%b'::ltxtquery;
+ ^
+SELECT '*b'::ltxtquery;
+ERROR: unquoted special symbol
+LINE 1: SELECT '*b'::ltxtquery;
+ ^
+SELECT '@b'::ltxtquery;
+ERROR: unquoted special symbol
+LINE 1: SELECT '@b'::ltxtquery;
+ ^
+SELECT '{b'::ltxtquery;
+ERROR: unquoted special symbol
+LINE 1: SELECT '{b'::ltxtquery;
+ ^
+SELECT '}b'::ltxtquery;
+ERROR: unquoted special symbol
+LINE 1: SELECT '}b'::ltxtquery;
+ ^
+SELECT '|b'::ltxtquery;
+ERROR: unquoted special symbol
+LINE 1: SELECT '|b'::ltxtquery;
+ ^
+SELECT '&b'::ltxtquery;
+ERROR: unquoted special symbol
+LINE 1: SELECT '&b'::ltxtquery;
+ ^
+SELECT '(b'::ltxtquery;
+ERROR: syntax error
+LINE 1: SELECT '(b'::ltxtquery;
+ ^
+SELECT ')b'::ltxtquery;
+ERROR: unquoted special symbol
+LINE 1: SELECT ')b'::ltxtquery;
+ ^
+SELECT 'a"'::ltxtquery;
+ERROR: escaping syntax error
+LINE 1: SELECT 'a"'::ltxtquery;
+ ^
+SELECT 'a!'::ltxtquery;
+ERROR: unquoted special symbol
+LINE 1: SELECT 'a!'::ltxtquery;
+ ^
+SELECT 'a{'::ltxtquery;
+ERROR: unquoted special symbol
+LINE 1: SELECT 'a{'::ltxtquery;
+ ^
+SELECT 'a}'::ltxtquery;
+ERROR: unquoted special symbol
+LINE 1: SELECT 'a}'::ltxtquery;
+ ^
+SELECT 'a|'::ltxtquery;
+ERROR: unquoted special symbol
+LINE 1: SELECT 'a|'::ltxtquery;
+ ^
+SELECT 'a&'::ltxtquery;
+ERROR: unquoted special symbol
+LINE 1: SELECT 'a&'::ltxtquery;
+ ^
+SELECT 'a('::ltxtquery;
+ERROR: unquoted special symbol
+LINE 1: SELECT 'a('::ltxtquery;
+ ^
+SELECT 'a)'::ltxtquery;
+ERROR: unquoted special symbol
+LINE 1: SELECT 'a)'::ltxtquery;
+ ^
diff --git a/contrib/ltree/ltree.h b/contrib/ltree/ltree.h
index e4b8c84fa6..a525eb2e5d 100644
--- a/contrib/ltree/ltree.h
+++ b/contrib/ltree/ltree.h
@@ -43,6 +43,7 @@ typedef struct
#define LVAR_ANYEND 0x01
#define LVAR_INCASE 0x02
#define LVAR_SUBLEXEME 0x04
+#define LVAR_QUOTEDPART 0x08
typedef struct
{
@@ -80,8 +81,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 */
/*
@@ -164,6 +163,8 @@ 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 bytes_to_escape(const char *start, const int len, const char *to_escape);
+void copy_level(char *dst, const char *src, int len, int extra_bytes);
/* 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 f54f037443..e086c091ab 100644
--- a/contrib/ltree/ltree_io.c
+++ b/contrib/ltree/ltree_io.c
@@ -33,6 +33,211 @@ typedef struct
#define LTPRS_WAITNAME 0
#define LTPRS_WAITDELIM 1
+#define LTPRS_WAITESCAPED 2
+#define LTPRS_WAITDELIMSTRICT 3
+
+static void
+count_parts_ors(const char *ptr, int *plevels, int *pORs)
+{
+ int escape_mode = 0;
+ int charlen;
+
+ while (*ptr)
+ {
+ charlen = pg_mblen(ptr);
+
+ if (escape_mode == 1)
+ escape_mode = 0;
+ else if (charlen == 1)
+ {
+ if (t_iseq(ptr, '\\'))
+ escape_mode = 1;
+ else if (t_iseq(ptr, '.'))
+ (*plevels)++;
+ else if (t_iseq(ptr, '|') && pORs != NULL)
+ (*pORs)++;
+ }
+
+ ptr += charlen;
+ }
+
+ (*plevels)++;
+ if (pORs != NULL)
+ (*pORs)++;
+}
+
+/*
+ * Char-by-char copying from src to dst representation removing escaping \\
+ * Total amount of copied bytes is len
+ */
+static void
+copy_unescaped(char *dst, const char *src, int len)
+{
+ uint16 copied = 0;
+ int charlen;
+ bool escaping = false;
+
+ while (*src && copied < len)
+ {
+ charlen = pg_mblen(src);
+ if ((charlen == 1) && t_iseq(src, '\\') && escaping == 0)
+ {
+ escaping = 1;
+ src++;
+ continue;
+ };
+
+ if (copied + charlen > len)
+ elog(ERROR, "internal error during splitting levels");
+
+ memcpy(dst, src, charlen);
+ src += charlen;
+ dst += charlen;
+ copied += charlen;
+ escaping = 0;
+ }
+
+ if (copied != len)
+ elog(ERROR, "internal error during splitting levels");
+}
+
+/*
+ * Function calculating bytes to escape
+ * to_escape is an array of "special" 1-byte symbols
+ * Behvaiour:
+ * If there is no "special" symbols, return 0
+ * If there are any special symbol, we need initial and final quote, so return 2
+ * If there are any quotes, we need to escape all of them and also initial and final quote, so
+ * return 2 + number of quotes
+ */
+int
+bytes_to_escape(const char *start, const int len, const char *to_escape)
+{
+ uint16 copied = 0;
+ int charlen;
+ int escapes = 0;
+ int quotes = 0;
+ const char *buf = start;
+
+ if (len == 0)
+ return 2;
+
+ while (*start && copied < len)
+ {
+ charlen = pg_mblen(buf);
+ if ((charlen == 1) && strchr(to_escape, *buf))
+ {
+ escapes++;
+ }
+ else if ((charlen == 1) && t_iseq(buf, '"'))
+ {
+ quotes++;
+ }
+
+ if (copied + charlen > len)
+ elog(ERROR, "internal error during merging levels");
+
+ buf += charlen;
+ copied += charlen;
+ }
+
+ return (quotes > 0) ? quotes + 2 :
+ (escapes > 0) ? 2 : 0;
+}
+
+static int
+copy_escaped(char *dst, const char *src, int len)
+{
+ uint16 copied = 0;
+ int charlen;
+ int escapes = 0;
+ char *buf = dst;
+
+ while (*src && copied < len)
+ {
+ charlen = pg_mblen(src);
+ if ((charlen == 1) && t_iseq(src, '"'))
+ {
+ *buf = '\\';
+ buf++;
+ escapes++;
+ };
+
+ if (copied + charlen > len)
+ elog(ERROR, "internal error during merging levels");
+
+ memcpy(buf, src, charlen);
+ src += charlen;
+ buf += charlen;
+ copied += charlen;
+ }
+ return escapes;
+}
+
+void
+copy_level(char *dst, const char *src, int len, int extra_bytes)
+{
+ if (extra_bytes == 0)
+ memcpy(dst, src, len);
+ else if (extra_bytes == 2)
+ {
+ *dst = '"';
+ memcpy(dst + 1, src, len);
+ dst[len + 1] = '"';
+ }
+ else
+ {
+ *dst = '"';
+ copy_escaped(dst + 1, src, len);
+ dst[len + extra_bytes - 1] = '"';
+ }
+}
+
+static void
+real_nodeitem_len(nodeitem *lptr, const char *ptr, int escapes, int tail_space_bytes, int tail_space_symbols)
+{
+ lptr->len = ptr - lptr->start - escapes -
+ ((lptr->flag & LVAR_SUBLEXEME) ? 1 : 0) -
+ ((lptr->flag & LVAR_INCASE) ? 1 : 0) -
+ ((lptr->flag & LVAR_ANYEND) ? 1 : 0) - tail_space_bytes;
+ lptr->wlen -= tail_space_symbols;
+}
+
+/*
+ * If we have a part beginning with quote,
+ * we must be sure it is finished with quote either.
+ * After that we moving start of the part a byte ahead
+ * and excluding beginning and final quotes from the part itself.
+ * */
+static void
+adjust_quoted_nodeitem(nodeitem *lptr)
+{
+ lptr->start++;
+ lptr->len -= 2;
+ lptr->wlen -= 2;
+}
+
+static void
+check_level_length(const nodeitem *lptr, int pos)
+{
+ if (lptr->len < 0)
+ elog(ERROR, "internal error: invalid level length");
+
+ if (lptr->wlen <= 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("name of level is empty"),
+ errdetail("Name length is 0 in position %d.",
+ pos)));
+
+ if (lptr->wlen > 255)
+ ereport(ERROR,
+ (errcode(ERRCODE_NAME_TOO_LONG),
+ errmsg("name of level is too long"),
+ errdetail("Name length is %d, must "
+ "be < 256, in position %d.",
+ lptr->wlen, pos)));
+}
Datum
ltree_in(PG_FUNCTION_ARGS)
@@ -41,89 +246,158 @@ ltree_in(PG_FUNCTION_ARGS)
char *ptr;
nodeitem *list,
*lptr;
- int num = 0,
+ int levels = 0,
totallen = 0;
int state = LTPRS_WAITNAME;
ltree *result;
ltree_level *curlevel;
int charlen;
+
+ /* Position in strings, in symbols. */
int pos = 0;
+ int escaped_count = 0;
+ int tail_space_bytes = 0;
+ int tail_space_symbols = 0;
ptr = buf;
- while (*ptr)
- {
- charlen = pg_mblen(ptr);
- if (charlen == 1 && t_iseq(ptr, '.'))
- num++;
- ptr += charlen;
- }
+ count_parts_ors(ptr, &levels, NULL);
- if (num + 1 > MaxAllocSize / sizeof(nodeitem))
+ if (levels > MaxAllocSize / sizeof(nodeitem))
ereport(ERROR,
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
errmsg("number of levels (%d) exceeds the maximum allowed (%d)",
- num + 1, (int) (MaxAllocSize / sizeof(nodeitem)))));
- list = lptr = (nodeitem *) palloc(sizeof(nodeitem) * (num + 1));
+ levels, (int) (MaxAllocSize / sizeof(nodeitem)))));
+ list = lptr = (nodeitem *) palloc(sizeof(nodeitem) * (levels));
+
+ /*
+ * This block calculates single nodes' settings
+ */
ptr = buf;
while (*ptr)
{
charlen = pg_mblen(ptr);
-
if (state == LTPRS_WAITNAME)
{
- if (ISALNUM(ptr))
+ if (t_isspace(ptr))
{
- lptr->start = ptr;
- lptr->wlen = 0;
- state = LTPRS_WAITDELIM;
+ ptr += charlen;
+ pos++;
+ continue;
}
- else
- UNCHAR;
+ state = LTPRS_WAITDELIM;
+ lptr->start = ptr;
+ lptr->wlen = 0;
+ lptr->flag = 0;
+ escaped_count = 0;
+
+ if (charlen == 1)
+ {
+ if (t_iseq(ptr, '.'))
+ {
+ UNCHAR;
+ }
+ else if (t_iseq(ptr, '\\'))
+ state = LTPRS_WAITESCAPED;
+ else if (t_iseq(ptr, '"'))
+ lptr->flag |= LVAR_QUOTEDPART;
+ }
+ }
+ else if (state == LTPRS_WAITESCAPED)
+ {
+ state = LTPRS_WAITDELIM;
+ escaped_count++;
}
else if (state == LTPRS_WAITDELIM)
{
- if (charlen == 1 && t_iseq(ptr, '.'))
+ if (charlen == 1)
{
- lptr->len = ptr - lptr->start;
- if (lptr->wlen > 255)
- ereport(ERROR,
- (errcode(ERRCODE_NAME_TOO_LONG),
- errmsg("name of level is too long"),
- errdetail("Name length is %d, must "
- "be < 256, in position %d.",
- lptr->wlen, pos)));
-
- totallen += MAXALIGN(lptr->len + LEVEL_HDRSIZE);
- lptr++;
- state = LTPRS_WAITNAME;
+ if (t_iseq(ptr, '.') && !(lptr->flag & LVAR_QUOTEDPART))
+ {
+ real_nodeitem_len(lptr, ptr, escaped_count, tail_space_bytes, tail_space_symbols);
+ check_level_length(lptr, pos);
+
+ totallen += MAXALIGN(lptr->len + LEVEL_HDRSIZE);
+ lptr++;
+ state = LTPRS_WAITNAME;
+ }
+ else if (t_iseq(ptr, '\\'))
+ {
+ state = LTPRS_WAITESCAPED;
+ }
+ else if (t_iseq(ptr, '"'))
+ {
+ if (lptr->flag & LVAR_QUOTEDPART)
+ {
+ lptr->flag &= ~LVAR_QUOTEDPART;
+ state = LTPRS_WAITDELIMSTRICT;
+ }
+ else /* Unescaped quote is forbidden */
+ UNCHAR;
+ }
}
- else if (!ISALNUM(ptr))
+
+ if (t_isspace(ptr))
+ {
+ tail_space_symbols++;
+ tail_space_bytes += charlen;
+ }
+ else
+ {
+ tail_space_symbols = 0;
+ tail_space_bytes = 0;
+ }
+ }
+ else if (state == LTPRS_WAITDELIMSTRICT)
+ {
+ if (t_isspace(ptr))
+ {
+ ptr += charlen;
+ pos++;
+ tail_space_bytes += charlen;
+ tail_space_symbols = 1;
+ continue;
+ }
+
+ if (!(charlen == 1 && t_iseq(ptr, '.')))
UNCHAR;
+
+ real_nodeitem_len(lptr, ptr, escaped_count, tail_space_bytes, tail_space_symbols);
+
+ adjust_quoted_nodeitem(lptr);
+ check_level_length(lptr, pos);
+
+ totallen += MAXALIGN(lptr->len + LEVEL_HDRSIZE);
+ lptr++;
+ state = LTPRS_WAITNAME;
}
else
/* internal error */
elog(ERROR, "internal error in parser");
-
ptr += charlen;
- lptr->wlen++;
+ if (state == LTPRS_WAITDELIM || state == LTPRS_WAITDELIMSTRICT)
+ lptr->wlen++;
pos++;
}
- if (state == LTPRS_WAITDELIM)
+ if (state == LTPRS_WAITDELIM || state == LTPRS_WAITDELIMSTRICT)
{
- lptr->len = ptr - lptr->start;
- if (lptr->wlen > 255)
+ if (lptr->flag & LVAR_QUOTEDPART)
ereport(ERROR,
- (errcode(ERRCODE_NAME_TOO_LONG),
- errmsg("name of level is too long"),
- errdetail("Name length is %d, must "
- "be < 256, in position %d.",
- lptr->wlen, pos)));
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("syntax error"),
+ errdetail("Unexpected end of line.")));
+
+ real_nodeitem_len(lptr, ptr, escaped_count, tail_space_bytes, tail_space_symbols);
+
+ if (state == LTPRS_WAITDELIMSTRICT)
+ adjust_quoted_nodeitem(lptr);
+
+ check_level_length(lptr, pos);
totallen += MAXALIGN(lptr->len + LEVEL_HDRSIZE);
lptr++;
}
- else if (!(state == LTPRS_WAITNAME && lptr == list))
+ else if (!(state == LTPRS_WAITNAME && lptr == list)) /* Empty string */
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("syntax error"),
@@ -137,7 +411,10 @@ ltree_in(PG_FUNCTION_ARGS)
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++;
}
@@ -154,8 +431,10 @@ ltree_out(PG_FUNCTION_ARGS)
*ptr;
int i;
ltree_level *curlevel;
+ Size allocated = VARSIZE(in);
+ Size filled = 0;
- ptr = buf = (char *) palloc(VARSIZE(in));
+ ptr = buf = (char *) palloc(allocated);
curlevel = LTREE_FIRST(in);
for (i = 0; i < in->numlevel; i++)
{
@@ -163,9 +442,22 @@ ltree_out(PG_FUNCTION_ARGS)
{
*ptr = '.';
ptr++;
+ filled++;
+ }
+ if (curlevel->len >= 0)
+ {
+ int extra_bytes = bytes_to_escape(curlevel->name, curlevel->len, "\\ .");
+
+ if (filled + extra_bytes + curlevel->len >= allocated)
+ {
+ buf = repalloc(buf, allocated + (extra_bytes + curlevel->len) * 2);
+ allocated += (extra_bytes + curlevel->len) * 2;
+ ptr = buf + filled;
+ }
+
+ copy_level(ptr, curlevel->name, curlevel->len, extra_bytes);
+ ptr += curlevel->len + extra_bytes;
}
- memcpy(ptr, curlevel->name, curlevel->len);
- ptr += curlevel->len;
curlevel = LEVEL_NEXT(curlevel);
}
@@ -184,6 +476,8 @@ ltree_out(PG_FUNCTION_ARGS)
#define LQPRS_WAITCLOSE 6
#define LQPRS_WAITEND 7
#define LQPRS_WAITVAR 8
+#define LQPRS_WAITESCAPED 9
+#define LQPRS_WAITDELIMSTRICT 10
#define GETVAR(x) ( *((nodeitem**)LQL_FIRST(x)) )
@@ -195,7 +489,7 @@ lquery_in(PG_FUNCTION_ARGS)
{
char *buf = (char *) PG_GETARG_POINTER(0);
char *ptr;
- int num = 0,
+ int levels = 0,
totallen = 0,
numOR = 0;
int state = LQPRS_WAITLEVEL;
@@ -209,30 +503,20 @@ lquery_in(PG_FUNCTION_ARGS)
bool wasbad = false;
int charlen;
int pos = 0;
+ int escaped_count = 0;
+ int real_levels = 0;
+ int tail_space_bytes = 0;
+ int tail_space_symbols = 0;
ptr = buf;
- while (*ptr)
- {
- charlen = pg_mblen(ptr);
-
- if (charlen == 1)
- {
- if (t_iseq(ptr, '.'))
- num++;
- else if (t_iseq(ptr, '|'))
- numOR++;
- }
-
- ptr += charlen;
- }
+ count_parts_ors(ptr, &levels, &numOR);
- num++;
- if (num > MaxAllocSize / ITEMSIZE)
+ if (levels > MaxAllocSize / ITEMSIZE)
ereport(ERROR,
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
errmsg("number of levels (%d) exceeds the maximum allowed (%d)",
- num, (int) (MaxAllocSize / ITEMSIZE))));
- curqlevel = tmpql = (lquery_level *) palloc0(ITEMSIZE * num);
+ levels, (int) (MaxAllocSize / ITEMSIZE))));
+ curqlevel = tmpql = (lquery_level *) palloc0(ITEMSIZE * levels);
ptr = buf;
while (*ptr)
{
@@ -240,102 +524,207 @@ lquery_in(PG_FUNCTION_ARGS)
if (state == LQPRS_WAITLEVEL)
{
- if (ISALNUM(ptr))
+ if (t_isspace(ptr))
{
- GETVAR(curqlevel) = lptr = (nodeitem *) palloc0(sizeof(nodeitem) * (numOR + 1));
- lptr->start = ptr;
- state = LQPRS_WAITDELIM;
- curqlevel->numvar = 1;
+ ptr += charlen;
+ pos++;
+ continue;
+ }
+
+ escaped_count = 0;
+ real_levels++;
+ if (charlen == 1)
+ {
+ if (t_iseq(ptr, '!'))
+ {
+ GETVAR(curqlevel) = lptr = (nodeitem *) palloc0(sizeof(nodeitem) * numOR);
+ lptr->start = ptr + 1;
+ state = LQPRS_WAITDELIM;
+ curqlevel->numvar = 1;
+ curqlevel->flag |= LQL_NOT;
+ hasnot = true;
+ }
+ else if (t_iseq(ptr, '*'))
+ state = LQPRS_WAITOPEN;
+ else if (t_iseq(ptr, '\\'))
+ {
+ GETVAR(curqlevel) = lptr = (nodeitem *) palloc0(sizeof(nodeitem) * numOR);
+ lptr->start = ptr;
+ curqlevel->numvar = 1;
+ state = LQPRS_WAITESCAPED;
+ }
+ else if (strchr(".|@%{}", *ptr))
+ {
+ UNCHAR;
+ }
+ else
+ {
+ GETVAR(curqlevel) = lptr = (nodeitem *) palloc0(sizeof(nodeitem) * numOR);
+ lptr->start = ptr;
+ state = LQPRS_WAITDELIM;
+ curqlevel->numvar = 1;
+ if (t_iseq(ptr, '"'))
+ {
+ lptr->flag |= LVAR_QUOTEDPART;
+ }
+ }
}
- else if (charlen == 1 && t_iseq(ptr, '!'))
+ else
{
- GETVAR(curqlevel) = lptr = (nodeitem *) palloc0(sizeof(nodeitem) * (numOR + 1));
- lptr->start = ptr + 1;
+ GETVAR(curqlevel) = lptr = (nodeitem *) palloc0(sizeof(nodeitem) * numOR);
+ lptr->start = ptr;
state = LQPRS_WAITDELIM;
curqlevel->numvar = 1;
- curqlevel->flag |= LQL_NOT;
- hasnot = true;
}
- else if (charlen == 1 && t_iseq(ptr, '*'))
- state = LQPRS_WAITOPEN;
- else
- UNCHAR;
}
else if (state == LQPRS_WAITVAR)
{
- if (ISALNUM(ptr))
+ if (t_isspace(ptr))
{
- lptr++;
- lptr->start = ptr;
- state = LQPRS_WAITDELIM;
- curqlevel->numvar++;
+ ptr += charlen;
+ pos++;
+ continue;
}
- else
+
+ escaped_count = 0;
+ lptr++;
+ lptr->start = ptr;
+ curqlevel->numvar++;
+ if (t_iseq(ptr, '.') || t_iseq(ptr, '|'))
UNCHAR;
+
+ state = (t_iseq(ptr, '\\')) ? LQPRS_WAITESCAPED : LQPRS_WAITDELIM;
+ if (t_iseq(ptr, '"'))
+ lptr->flag |= LVAR_QUOTEDPART;
}
- else if (state == LQPRS_WAITDELIM)
+ else if (state == LQPRS_WAITDELIM || state == LQPRS_WAITDELIMSTRICT)
{
- if (charlen == 1 && t_iseq(ptr, '@'))
+ if (charlen == 1 && t_iseq(ptr, '"'))
{
+ /* We are here if variant begins with ! */
if (lptr->start == ptr)
+ lptr->flag |= LVAR_QUOTEDPART;
+ else if (state == LQPRS_WAITDELIMSTRICT)
+ {
UNCHAR;
- lptr->flag |= LVAR_INCASE;
- curqlevel->flag |= LVAR_INCASE;
- }
- else if (charlen == 1 && t_iseq(ptr, '*'))
- {
- if (lptr->start == ptr)
+ }
+ else if (lptr->flag & LVAR_QUOTEDPART)
+ {
+ lptr->flag &= ~LVAR_QUOTEDPART;
+ state = LQPRS_WAITDELIMSTRICT;
+ }
+ else
UNCHAR;
- lptr->flag |= LVAR_ANYEND;
- curqlevel->flag |= LVAR_ANYEND;
}
- else if (charlen == 1 && t_iseq(ptr, '%'))
+ else if ((lptr->flag & LVAR_QUOTEDPART) == 0)
{
- if (lptr->start == ptr)
- UNCHAR;
- lptr->flag |= LVAR_SUBLEXEME;
- curqlevel->flag |= LVAR_SUBLEXEME;
+ if (charlen == 1 && t_iseq(ptr, '@'))
+ {
+ if (lptr->start == ptr)
+ UNCHAR;
+ lptr->flag |= LVAR_INCASE;
+ curqlevel->flag |= LVAR_INCASE;
+ }
+ else if (charlen == 1 && t_iseq(ptr, '*'))
+ {
+ if (lptr->start == ptr)
+ UNCHAR;
+ lptr->flag |= LVAR_ANYEND;
+ curqlevel->flag |= LVAR_ANYEND;
+ }
+ else if (charlen == 1 && t_iseq(ptr, '%'))
+ {
+ if (lptr->start == ptr)
+ UNCHAR;
+ lptr->flag |= LVAR_SUBLEXEME;
+ curqlevel->flag |= LVAR_SUBLEXEME;
+ }
+ else if (charlen == 1 && t_iseq(ptr, '|'))
+ {
+ real_nodeitem_len(lptr, ptr, escaped_count, tail_space_bytes, tail_space_symbols);
+
+ if (state == LQPRS_WAITDELIMSTRICT)
+ adjust_quoted_nodeitem(lptr);
+
+ check_level_length(lptr, pos);
+ state = LQPRS_WAITVAR;
+ }
+ else if (charlen == 1 && t_iseq(ptr, '.'))
+ {
+ real_nodeitem_len(lptr, ptr, escaped_count, tail_space_bytes, tail_space_symbols);
+
+ if (state == LQPRS_WAITDELIMSTRICT)
+ adjust_quoted_nodeitem(lptr);
+
+ check_level_length(lptr, pos);
+
+ state = LQPRS_WAITLEVEL;
+ curqlevel = NEXTLEV(curqlevel);
+ }
+ else if (charlen == 1 && t_iseq(ptr, '\\'))
+ {
+ if (state == LQPRS_WAITDELIMSTRICT)
+ UNCHAR;
+ state = LQPRS_WAITESCAPED;
+ }
+ else
+ {
+ if (charlen == 1 && strchr("!{}", *ptr))
+ UNCHAR;
+ if (state == LQPRS_WAITDELIMSTRICT)
+ {
+ if (t_isspace(ptr))
+ {
+ ptr += charlen;
+ pos++;
+ tail_space_bytes += charlen;
+ tail_space_symbols = 1;
+ continue;
+ }
+
+ UNCHAR;
+ }
+ if (lptr->flag & ~LVAR_QUOTEDPART)
+ UNCHAR;
+ }
}
- else if (charlen == 1 && t_iseq(ptr, '|'))
+ else if (charlen == 1 && t_iseq(ptr, '\\'))
{
- lptr->len = ptr - lptr->start -
- ((lptr->flag & LVAR_SUBLEXEME) ? 1 : 0) -
- ((lptr->flag & LVAR_INCASE) ? 1 : 0) -
- ((lptr->flag & LVAR_ANYEND) ? 1 : 0);
- if (lptr->wlen > 255)
- ereport(ERROR,
- (errcode(ERRCODE_NAME_TOO_LONG),
- errmsg("name of level is too long"),
- errdetail("Name length is %d, must "
- "be < 256, in position %d.",
- lptr->wlen, pos)));
-
- state = LQPRS_WAITVAR;
+ if (state == LQPRS_WAITDELIMSTRICT)
+ UNCHAR;
+ if (lptr->flag & ~LVAR_QUOTEDPART)
+ UNCHAR;
+ state = LQPRS_WAITESCAPED;
}
- else if (charlen == 1 && t_iseq(ptr, '.'))
+ else
{
- lptr->len = ptr - lptr->start -
- ((lptr->flag & LVAR_SUBLEXEME) ? 1 : 0) -
- ((lptr->flag & LVAR_INCASE) ? 1 : 0) -
- ((lptr->flag & LVAR_ANYEND) ? 1 : 0);
- if (lptr->wlen > 255)
- ereport(ERROR,
- (errcode(ERRCODE_NAME_TOO_LONG),
- errmsg("name of level is too long"),
- errdetail("Name length is %d, must "
- "be < 256, in position %d.",
- lptr->wlen, pos)));
+ if (state == LQPRS_WAITDELIMSTRICT)
+ {
+ if (t_isspace(ptr))
+ {
+ ptr += charlen;
+ pos++;
+ tail_space_bytes += charlen;
+ tail_space_symbols = 1;
+ continue;
+ }
- state = LQPRS_WAITLEVEL;
- curqlevel = NEXTLEV(curqlevel);
+ UNCHAR;
+ }
+ if (lptr->flag & ~LVAR_QUOTEDPART)
+ UNCHAR;
}
- else if (ISALNUM(ptr))
+
+ if (t_isspace(ptr))
{
- if (lptr->flag)
- UNCHAR;
+ tail_space_symbols++;
+ tail_space_bytes += charlen;
}
else
- UNCHAR;
+ {
+ tail_space_symbols = 0;
+ tail_space_bytes = 0;
+ }
}
else if (state == LQPRS_WAITOPEN)
{
@@ -399,7 +788,7 @@ lquery_in(PG_FUNCTION_ARGS)
}
else if (state == LQPRS_WAITEND)
{
- if (charlen == 1 && t_iseq(ptr, '.'))
+ if (charlen == 1 && (t_iseq(ptr, '.') || t_iseq(ptr, '|')))
{
state = LQPRS_WAITLEVEL;
curqlevel = NEXTLEV(curqlevel);
@@ -407,17 +796,29 @@ lquery_in(PG_FUNCTION_ARGS)
else
UNCHAR;
}
+ else if (state == LQPRS_WAITESCAPED)
+ {
+ state = LQPRS_WAITDELIM;
+ escaped_count++;
+ }
else
/* internal error */
elog(ERROR, "internal error in parser");
ptr += charlen;
- if (state == LQPRS_WAITDELIM)
+ if (state == LQPRS_WAITDELIM || state == LQPRS_WAITDELIMSTRICT)
lptr->wlen++;
pos++;
}
- if (state == LQPRS_WAITDELIM)
+ if (lptr->flag & LVAR_QUOTEDPART)
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("syntax error"),
+ errdetail("Unexpected end of line.")));
+ }
+ else if (state == LQPRS_WAITDELIM || state == LQPRS_WAITDELIMSTRICT)
{
if (lptr->start == ptr)
ereport(ERROR,
@@ -425,23 +826,12 @@ lquery_in(PG_FUNCTION_ARGS)
errmsg("syntax error"),
errdetail("Unexpected end of line.")));
- lptr->len = ptr - lptr->start -
- ((lptr->flag & LVAR_SUBLEXEME) ? 1 : 0) -
- ((lptr->flag & LVAR_INCASE) ? 1 : 0) -
- ((lptr->flag & LVAR_ANYEND) ? 1 : 0);
- if (lptr->len == 0)
- ereport(ERROR,
- (errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("syntax error"),
- errdetail("Unexpected end of line.")));
+ real_nodeitem_len(lptr, ptr, escaped_count, tail_space_bytes, tail_space_symbols);
- if (lptr->wlen > 255)
- ereport(ERROR,
- (errcode(ERRCODE_NAME_TOO_LONG),
- errmsg("name of level is too long"),
- errdetail("Name length is %d, must "
- "be < 256, in position %d.",
- lptr->wlen, pos)));
+ if (state == LQPRS_WAITDELIMSTRICT)
+ adjust_quoted_nodeitem(lptr);
+
+ check_level_length(lptr, pos);
}
else if (state == LQPRS_WAITOPEN)
curqlevel->high = 0xffff;
@@ -450,10 +840,16 @@ lquery_in(PG_FUNCTION_ARGS)
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("syntax error"),
errdetail("Unexpected end of line.")));
+ else if (state == LQPRS_WAITESCAPED)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("syntax error"),
+ errdetail("Unexpected end of line.")));
+
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)
@@ -477,14 +873,14 @@ lquery_in(PG_FUNCTION_ARGS)
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;
@@ -497,8 +893,8 @@ lquery_in(PG_FUNCTION_ARGS)
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);
}
@@ -526,7 +922,8 @@ lquery_out(PG_FUNCTION_ARGS)
*ptr;
int i,
j,
- totallen = 1;
+ totallen = 1,
+ filled = 0;
lquery_level *curqlevel;
lquery_variant *curtlevel;
@@ -549,6 +946,7 @@ lquery_out(PG_FUNCTION_ARGS)
{
*ptr = '.';
ptr++;
+ filled++;
}
if (curqlevel->numvar)
{
@@ -556,31 +954,46 @@ lquery_out(PG_FUNCTION_ARGS)
{
*ptr = '!';
ptr++;
+ filled++;
}
curtlevel = LQL_FIRST(curqlevel);
for (j = 0; j < curqlevel->numvar; j++)
{
+ int extra_bytes = bytes_to_escape(curtlevel->name, curtlevel->len, ". \\|!*@%{}");
+
if (j != 0)
{
*ptr = '|';
ptr++;
+ filled++;
}
- memcpy(ptr, curtlevel->name, curtlevel->len);
- ptr += curtlevel->len;
+ if (filled + extra_bytes + curtlevel->len >= totallen)
+ {
+ buf = repalloc(buf, totallen + (extra_bytes + curtlevel->len) * 2);
+ totallen += (extra_bytes + curtlevel->len) * 2;
+ ptr = buf + filled;
+ }
+
+ copy_level(ptr, curtlevel->name, curtlevel->len, extra_bytes);
+ ptr += curtlevel->len + extra_bytes;
+
if ((curtlevel->flag & LVAR_SUBLEXEME))
{
*ptr = '%';
ptr++;
+ filled++;
}
if ((curtlevel->flag & LVAR_INCASE))
{
*ptr = '@';
ptr++;
+ filled++;
}
if ((curtlevel->flag & LVAR_ANYEND))
{
*ptr = '*';
ptr++;
+ filled++;
}
curtlevel = LVAR_NEXT(curtlevel);
}
@@ -608,6 +1021,7 @@ lquery_out(PG_FUNCTION_ARGS)
else
sprintf(ptr, "*{%d,%d}", curqlevel->low, curqlevel->high);
ptr = strchr(ptr, '\0');
+ filled = ptr - buf;
}
curqlevel = LQL_NEXT(curqlevel);
diff --git a/contrib/ltree/ltxtquery_io.c b/contrib/ltree/ltxtquery_io.c
index 56bf39d145..45261415a0 100644
--- a/contrib/ltree/ltxtquery_io.c
+++ b/contrib/ltree/ltxtquery_io.c
@@ -19,6 +19,8 @@ PG_FUNCTION_INFO_V1(ltxtq_out);
#define WAITOPERAND 1
#define INOPERAND 2
#define WAITOPERATOR 3
+#define WAITESCAPED 4
+#define ENDOPERAND 5
/*
* node of query tree, also used
@@ -78,38 +80,151 @@ gettoken_query(QPRS_STATE *state, int32 *val, int32 *lenval, char **strval, uint
(state->buf)++;
return OPEN;
}
- else if (ISALNUM(state->buf))
+ else if (charlen == 1 && t_iseq(state->buf, '\\'))
{
+ state->state = WAITESCAPED;
+ *strval = state->buf;
+ *lenval = 1;
+ *flag = 0;
+ }
+ else if (t_isspace(state->buf))
+ {
+ /* do nothing */
+ }
+ else
+ {
+ if (charlen == 1 && strchr("{}()|&%*@", *(state->buf)))
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("unquoted special symbol")));
+
state->state = INOPERAND;
*strval = state->buf;
*lenval = charlen;
*flag = 0;
+ if (charlen == 1 && t_iseq(state->buf, '"'))
+ *flag |= LVAR_QUOTEDPART;
}
- else if (!t_isspace(state->buf))
- ereport(ERROR,
- (errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("operand syntax error")));
break;
case INOPERAND:
- if (ISALNUM(state->buf))
+ case ENDOPERAND:
+ if (charlen == 1 && t_iseq(state->buf, '"'))
{
- if (*flag)
+ if (state->state == ENDOPERAND)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("escaping syntax error")));
+ else if (*flag & ~LVAR_QUOTEDPART)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("modifiers syntax error")));
+ else if (*flag & LVAR_QUOTEDPART)
+ {
+ *flag &= ~LVAR_QUOTEDPART;
+ state->state = ENDOPERAND;
+ }
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("escaping syntax error")));
+ }
+ else if ((*flag & LVAR_QUOTEDPART) == 0)
+ {
+ if ((*(state->buf) == '\0') || t_isspace(state->buf))
+ {
+ /* Adjust */
+ if (state->state == ENDOPERAND)
+ {
+ (*strval)++;
+ (*lenval)--;
+ }
+ state->state = WAITOPERATOR;
+ return VAL;
+ }
+
+ if (charlen == 1 && strchr("!{}()|&", *(state->buf)))
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("unquoted special symbol")));
+
+ if (charlen != 1 || (charlen == 1 && !strchr("@%*\\", *(state->buf))))
+ {
+ if (*flag & ~LVAR_QUOTEDPART)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("modifiers syntax error")));
+
+ *lenval += charlen;
+ }
+ else if (charlen == 1 && t_iseq(state->buf, '%'))
+ *flag |= LVAR_SUBLEXEME;
+ else if (charlen == 1 && t_iseq(state->buf, '@'))
+ *flag |= LVAR_INCASE;
+ else if (charlen == 1 && t_iseq(state->buf, '*'))
+ *flag |= LVAR_ANYEND;
+ else if (charlen == 1 && t_iseq(state->buf, '\\'))
+ {
+ if (*flag & ~LVAR_QUOTEDPART)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("escaping syntax error")));
+
+ state->state = WAITESCAPED;
+ *lenval += charlen;
+ }
+ else
+ {
+ /* Adjust */
+ if (state->state == ENDOPERAND)
+ {
+ (*strval)++;
+ (*lenval)--;
+ }
+ state->state = WAITOPERATOR;
+ return VAL;
+ }
+ }
+ else if (charlen == 1 && t_iseq(state->buf, '\\'))
+ {
+ if (state->state == ENDOPERAND)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("escaping syntax error")));
+ if (*flag & ~LVAR_QUOTEDPART)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("escaping syntax error")));
+
+ state->state = WAITESCAPED;
*lenval += charlen;
}
- else if (charlen == 1 && t_iseq(state->buf, '%'))
- *flag |= LVAR_SUBLEXEME;
- else if (charlen == 1 && t_iseq(state->buf, '@'))
- *flag |= LVAR_INCASE;
- else if (charlen == 1 && t_iseq(state->buf, '*'))
- *flag |= LVAR_ANYEND;
else
{
- state->state = WAITOPERATOR;
- return VAL;
+ if (*(state->buf) == '\0' && (*flag & LVAR_QUOTEDPART))
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("escaping syntax error")));
+
+ if (state->state == ENDOPERAND)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("syntax error")));
+ if (*flag & ~LVAR_QUOTEDPART)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("syntax error")));
+ *lenval += charlen;
+ }
+ break;
+ case WAITESCAPED:
+ if (*(state->buf) == '\0')
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("escaping syntax error")));
}
+ *lenval += charlen;
+ state->state = INOPERAND;
break;
case WAITOPERATOR:
if (charlen == 1 && (t_iseq(state->buf, '&') || t_iseq(state->buf, '|')))
@@ -140,6 +255,47 @@ gettoken_query(QPRS_STATE *state, int32 *val, int32 *lenval, char **strval, uint
}
/*
+ * This function is similar to copy_unescaped.
+ * It proceeds total_len bytes from src
+ * Copying all to dst skipping escapes
+ * Returns amount of skipped symbols
+ * */
+static int
+copy_skip_escapes(char *dst, const char *src, int total_len)
+{
+ uint16 copied = 0;
+ int charlen;
+ bool escaping = false;
+ int skipped = 0;
+
+ while (*src && (copied + skipped < total_len))
+ {
+ charlen = pg_mblen(src);
+ if ((charlen == 1) && t_iseq(src, '\\') && escaping == 0)
+ {
+ escaping = 1;
+ src++;
+ skipped++;
+ continue;
+ };
+
+ if (copied + skipped + charlen > total_len)
+ elog(ERROR, "internal error during copying");
+
+ memcpy(dst, src, charlen);
+ src += charlen;
+ dst += charlen;
+ copied += charlen;
+ escaping = 0;
+ }
+
+ if (copied + skipped != total_len)
+ elog(ERROR, "internal error during copying");
+
+ return skipped;
+}
+
+/*
* push new one in polish notation reverse view
*/
static void
@@ -171,14 +327,18 @@ 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)
{
+ int skipped = 0;
+
+ 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;
@@ -187,11 +347,19 @@ pushval_asis(QPRS_STATE *state, int type, char *strval, int lenval, uint16 flag)
state->op = (char *) repalloc((void *) state->op, state->lenop);
state->curop = state->op + tmp;
}
- memcpy((void *) state->curop, (void *) strval, lenval);
- state->curop += lenval;
+ skipped = copy_skip_escapes((void *) state->curop, (void *) strval, lenval);
+ if (lenval == skipped) /* Empty quoted literal */
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("empty labels are forbidden")));
+
+ pushquery(state, type, ltree_crc32_sz(state->curop, lenval - skipped),
+ state->curop - state->op, lenval - skipped, flag);
+
+ state->curop += lenval - skipped;
*(state->curop) = '\0';
state->curop++;
- state->sumlen += lenval + 1;
+ state->sumlen += lenval - skipped + 1;
return;
}
@@ -422,14 +590,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 = bytes_to_escape(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 846b04e48e..9268742293 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
@@ -291,3 +293,379 @@ 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 ' e . f '::ltree;
+SELECT ' '::ltree;
+
+SELECT E'\\ g . h\\ '::ltree;
+SELECT E'\\ g'::ltree;
+SELECT E' 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 " | " h " '::lquery;
+
+SELECT(' ' ||
+'01234567890123456789012345678901234567890123456789' ||
+'01234567890123456789012345678901234567890123456789' ||
+'01234567890123456789012345678901234567890123456789' ||
+'01234567890123456789012345678901234567890123456789' ||
+'01234567890123456789012345678901234567890123456789' ||
+'\a\b\c\d\e ')::lquery;
+
+SELECT E'"a\\"b"'::lquery;
+SELECT '"a!b"'::lquery;
+SELECT '"a%b"'::lquery;
+SELECT '"a*b"'::lquery;
+SELECT '"a@b"'::lquery;
+SELECT '"a{b"'::lquery;
+SELECT '"a}b"'::lquery;
+SELECT '"a|b"'::lquery;
+
+SELECT E'a\\"b'::lquery;
+SELECT E'a\\!b'::lquery;
+SELECT E'a\\%b'::lquery;
+SELECT E'a\\*b'::lquery;
+SELECT E'a\\@b'::lquery;
+SELECT E'a\\{b'::lquery;
+SELECT E'a\\}b'::lquery;
+SELECT E'a\\|b'::lquery;
+
+SELECT '!"!b"'::lquery;
+SELECT '!"%b"'::lquery;
+SELECT '!"*b"'::lquery;
+SELECT '!"@b"'::lquery;
+SELECT '!"{b"'::lquery;
+SELECT '!"}b"'::lquery;
+
+SELECT E'!\\!b'::lquery;
+SELECT E'!\\%b'::lquery;
+SELECT E'!\\*b'::lquery;
+SELECT E'!\\@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 ~ '[email protected]';
+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 '"a%b"'::ltxtquery;
+SELECT '"a*b"'::ltxtquery;
+SELECT '"a@b"'::ltxtquery;
+SELECT '"a{b"'::ltxtquery;
+SELECT '"a}b"'::ltxtquery;
+SELECT '"a|b"'::ltxtquery;
+SELECT '"a&b"'::ltxtquery;
+SELECT '"a(b"'::ltxtquery;
+SELECT '"a)b"'::ltxtquery;
+
+SELECT E'a\\"b'::ltxtquery;
+SELECT E'a\\!b'::ltxtquery;
+SELECT E'a\\%b'::ltxtquery;
+SELECT E'a\\*b'::ltxtquery;
+SELECT E'a\\@b'::ltxtquery;
+SELECT E'a\\{b'::ltxtquery;
+SELECT E'a\\}b'::ltxtquery;
+SELECT E'a\\|b'::ltxtquery;
+SELECT E'a\\&b'::ltxtquery;
+SELECT E'a\\(b'::ltxtquery;
+SELECT E'a\\)b'::ltxtquery;
+
+SELECT E'"\\"b"'::ltxtquery;
+SELECT '"!b"'::ltxtquery;
+SELECT '"%b"'::ltxtquery;
+SELECT '"*b"'::ltxtquery;
+SELECT '"@b"'::ltxtquery;
+SELECT '"{b"'::ltxtquery;
+SELECT '"}b"'::ltxtquery;
+SELECT '"|b"'::ltxtquery;
+SELECT '"&b"'::ltxtquery;
+SELECT '"(b"'::ltxtquery;
+SELECT '")b"'::ltxtquery;
+
+SELECT E'\\"b'::ltxtquery;
+SELECT E'\\!b'::ltxtquery;
+SELECT E'\\%b'::ltxtquery;
+SELECT E'\\*b'::ltxtquery;
+SELECT E'\\@b'::ltxtquery;
+SELECT E'\\{b'::ltxtquery;
+SELECT E'\\}b'::ltxtquery;
+SELECT E'\\|b'::ltxtquery;
+SELECT E'\\&b'::ltxtquery;
+SELECT E'\\(b'::ltxtquery;
+SELECT E'\\)b'::ltxtquery;
+
+SELECT E'"a\\""'::ltxtquery;
+SELECT '"a!"'::ltxtquery;
+SELECT '"a%"'::ltxtquery;
+SELECT '"a*"'::ltxtquery;
+SELECT '"a@"'::ltxtquery;
+SELECT '"a{"'::ltxtquery;
+SELECT '"a}"'::ltxtquery;
+SELECT '"a|"'::ltxtquery;
+SELECT '"a&"'::ltxtquery;
+SELECT '"a("'::ltxtquery;
+SELECT '"a)"'::ltxtquery;
+
+SELECT E'a\\"'::ltxtquery;
+SELECT E'a\\!'::ltxtquery;
+SELECT E'a\\%'::ltxtquery;
+SELECT E'a\\*'::ltxtquery;
+SELECT E'a\\@'::ltxtquery;
+SELECT E'a\\{'::ltxtquery;
+SELECT E'a\\}'::ltxtquery;
+SELECT E'a\\|'::ltxtquery;
+SELECT E'a\\&'::ltxtquery;
+SELECT E'a\\('::ltxtquery;
+SELECT E'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%b'::lquery;
+SELECT 'a*b'::lquery;
+SELECT 'a@b'::lquery;
+SELECT 'a{b'::lquery;
+SELECT 'a}b'::lquery;
+
+SELECT 'a!'::lquery;
+SELECT 'a{'::lquery;
+SELECT 'a}'::lquery;
+
+SELECT '%b'::lquery;
+SELECT '*b'::lquery;
+SELECT '@b'::lquery;
+SELECT '{b'::lquery;
+SELECT '}b'::lquery;
+
+SELECT '!%b'::lquery;
+SELECT '!*b'::lquery;
+SELECT '!@b'::lquery;
+SELECT '!{b'::lquery;
+SELECT '!}b'::lquery;
+
+SELECT '"qwert"y.tu'::lquery;
+SELECT 'q"wert"y"%@*.tu'::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 'a*b'::ltxtquery;
+SELECT 'a@b'::ltxtquery;
+SELECT 'a{b'::ltxtquery;
+SELECT 'a}b'::ltxtquery;
+SELECT 'a|b'::ltxtquery;
+SELECT 'a&b'::ltxtquery;
+SELECT 'a(b'::ltxtquery;
+SELECT 'a)b'::ltxtquery;
+
+SELECT '"b'::ltxtquery;
+SELECT '%b'::ltxtquery;
+SELECT '*b'::ltxtquery;
+SELECT '@b'::ltxtquery;
+SELECT '{b'::ltxtquery;
+SELECT '}b'::ltxtquery;
+SELECT '|b'::ltxtquery;
+SELECT '&b'::ltxtquery;
+SELECT '(b'::ltxtquery;
+SELECT ')b'::ltxtquery;
+
+SELECT 'a"'::ltxtquery;
+SELECT 'a!'::ltxtquery;
+SELECT 'a{'::ltxtquery;
+SELECT 'a}'::ltxtquery;
+SELECT 'a|'::ltxtquery;
+SELECT 'a&'::ltxtquery;
+SELECT 'a('::ltxtquery;
+SELECT 'a)'::ltxtquery;
+
diff --git a/doc/src/sgml/ltree.sgml b/doc/src/sgml/ltree.sgml
index 3ddd335b8c..08f120730f 100644
--- a/doc/src/sgml/ltree.sgml
+++ b/doc/src/sgml/ltree.sgml
@@ -17,14 +17,37 @@
<title>Definitions</title>
<para>
- A <firstterm>label</firstterm> is a sequence of alphanumeric characters
- and underscores (for example, in C locale the characters
- <literal>A-Za-z0-9_</literal> are allowed). Labels must be less than 256 bytes
- long.
+ A <firstterm>label</firstterm> is a sequence of characters. Labels must be
+ less than 256 symbols long. Label may contain any character supported by Postgres
+ except <literal>\0</literal>. If label contains spaces, dots, lquery modifiers,
+ they may be <firstterm>escaped</firstterm>. Escaping can be done either by preceeding
+ backslash (<literal>\\</literal>) symbol, or by wrapping the label in whole in double
+ quotes (<literal>"</literal>). Initial and final unescaped whitespace is stripped.
</para>
<para>
- Examples: <literal>42</literal>, <literal>Personal_Services</literal>
+ Examples: <literal>42</literal>, <literal>Personal_Services</literal>,
+ <literal>"This is a literal"</literal>, <literal>Literal\\ with\\ spaces</literal>.
+ </para>
+
+ <para>
+ During converting text into internal representations, wrapping double quotes
+ and escaping backslashes are removed. During converting internal
+ representations into text, if the label does not contain any special
+ symbols, it is printed as is. Otherwise, it is wrapped in quotes and, if
+ there are internal quotes, they are escaped with backslash. The list of special
+ symbols for ltree includes space (<literal> </literal>), backslash and double quote,
+ lquery and ltxtquery also require escaping <literal>|</literal>, <literal>&</literal>,
+ <literal>!</literal>, <literal>@</literal>, and <literal>*</literal>.
+ </para>
+
+ <para>
+ Examples: <literal>42</literal>, <literal>"\\42"</literal>,
+ <literal>\\4\\2</literal>, <literal> 42 </literal> and <literal> "42"
+ </literal> will have the similar internal representation and, being
+ converted from internal representation, will become <literal>42</literal>.
+ Literal <literal>abc def</literal> will turn into <literal>"abc
+ def"</literal>.
</para>
<para>
@@ -681,11 +704,13 @@ ltreetest=> SELECT ins_label(path,2,'Space') FROM test WHERE path <@ 'Top.
<title>Authors</title>
<para>
- All work was done by Teodor Sigaev (<email>[email protected]</email>) and
+ Initial version was done by Teodor Sigaev (<email>[email protected]</email>) and
Oleg Bartunov (<email>[email protected]</email>). See
<ulink url="http://www.sai.msu.su/~megera/postgres/gist/"></ulink> 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
+ (<email>[email protected]</email>) directed by Teodor Sigaev.
+ Comments and bug reports are welcome.
</para>
</sect2>