On 02.04.2020 19:16, Tom Lane wrote:
Nikita Glukhov <n.glu...@postgrespro.ru> writes:
Rebased patch attached.
Thanks for rebasing! The cfbot's not very happy though:
4842ltxtquery_io.c: In function ‘makepol’:
4843ltxtquery_io.c:188:13: error: ‘escaped’ may be used uninitialized in this
function [-Werror=maybe-uninitialized]
4844 if (lenval - escaped <= 0)
4845 ^
4846ltxtquery_io.c:230:6: note: ‘escaped’ was declared here
4847 int escaped;
4848 ^
4849cc1: all warnings being treated as errors
regards, tom lane
Fixed patch attached.
--
Nikita Glukhov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
>From c4929c05423f5b29063c739f25dfebfe99681d01 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <n.glu...@postgrespro.ru>
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 @@
<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 characters long.
+ A <firstterm>label</firstterm> is a sequence of characters. Labels must be
+ less than 256 characters long. Label may contain any character supported
+ by <productname>PostgreSQL</productname> except <literal>\0</literal>.
+ If label contains characters other than alphanumeric characters and
+ underscores, they should be <firstterm>escaped</firstterm>.
+ Escaping can be done with either by a preceding backslash (<literal>\\</literal>)
+ symbol or by wrapping the whole label 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 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.
+ </para>
+
+ <para>
+ Examples: <literal>42</literal>, <literal>"\\42"</literal>,
+ <literal>\\4\\2</literal>, <literal> 42 </literal> and <literal> "42"
+ </literal> will have the same 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>
@@ -725,11 +747,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>teo...@stack.net</email>) and
+ Initial version was done by Teodor Sigaev (<email>teo...@sigaev.ru</email>) and
Oleg Bartunov (<email>o...@sai.msu.su</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>beld...@gmail.com</email>) directed by Teodor Sigaev.
+ Comments and bug reports are welcome.
</para>
</sect2>
--
2.7.4