Ltree syntax improvement

Started by Dmitry Belyavskyalmost 7 years ago33 messages
#1Dmitry Belyavsky
beldmit@gmail.com
1 attachment(s)

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

Attachments:

ltree_20190129.difftext/x-patch; charset=UTF-8; name=ltree_20190129.diffDownload
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 ~ '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 '"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 ~ '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 '"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=&gt; SELECT ins_label(path,2,'Space') FROM test WHERE path &lt;@ 'Top.
   <title>Authors</title>
 
   <para>
-   All work was done by Teodor Sigaev (<email>teodor@stack.net</email>) and
+   Initial version was done by Teodor Sigaev (<email>teodor@sigaev.ru</email>) and
    Oleg Bartunov (<email>oleg@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>beldmit@gmail.com</email>) directed by Teodor Sigaev. 
+   Comments and bug reports are welcome.
   </para>
  </sect2>
 
#2Nikolay Shaplov
dhyan@nataraj.su
In reply to: Dmitry Belyavsky (#1)
Re: Ltree syntax improvement

В письме от вторник, 29 января 2019 г. 20:43:07 MSK пользователь Dmitry
Belyavsky написал:

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.

Hi!

Let's me join the review process...

I've just give a superficial look to the patch, read it through, and try here
and there, so first I'll give feedback about exterior features of the patch.

So, the first question: why do we need all this? The answer seems to be
obvious, but it would be good say it explicitly. What problems would it solve?
Do these problems really exists?

The second question is about coding style. As far as I can see you've passed
your code through pg_indent, but it does not solve all problems.

As far as I know, postgres commiters are quite strict about code width, the
code should not be wider that 80 col, except places were string constants are
introduced (There you can legally exceed 80 col). So I would advice to use vim
with tabstop=4 and colorcolumn=80 and make sure that new code text does not
cross red vertical line.

Comments. There is no fixed coding style for comments in postgres. Usually this
means that it is better to follow coding in the code around. But ltree does
not have much comments, so I would suggest to use the best style I've seen in
the code
/*
* [function name]
* < tab ><tab> [Short description, what does it do]
*
* [long explanation how it works, several subparagraph if needed
*/
(Seen this in src/include/utils/rel.h)
or something like that.
At least it would be good to have explicit separation between descriptions and
explanation of how it works.

And about comment
/*
* 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
*/

It has some formatting. But it should not. As far as I understand this
comment should be treated as single paragraph by reformatter, and refomatted.
I do not understand why pg_indent have not done it. Documentation (https://
www.postgresql.org/docs/11/source-format.html) claims that if you want to
avoid reformatting, you should add "-------------" at the beginning and at the
end of the comment. So it would be good either remove this formatting, or add
"----" if you are sure you want to keep it.

Third part is about error messages.
First I've seen errors like elog(ERROR, "internal error during splitting
levels");. I've never seen error like that in postgres. May be if this error
is in the part of the code, that should never be reached in real live, may be
it would be good to change it to Assert? Or if this code can be normally
reached, add some more explanation of what had happened...

About other error messages.

There are messages like
errmsg("syntax error"),
errdetail("Unexpected end of line.")));

or

errmsg("escaping syntax error")));

In postgres there is error message style guide
https://www.postgresql.org/docs/11/error-style-guide.html

Here I would expect messages like that

Primary: Error parsing ltree path
Detail: Curly quote is opened and not closed

Or something like that, I am not expert in English, but this way it would be
better for sure. Key points are: Primary message should point to general area
where error occurred (there can be a large SQL with a lot of things in it, so
it is good to point that problem is with ltree staff). And is is better to
word from the point of view of a user. What for you (and me) is unexpected end
of line, for him it is unclosed curly quote.

Also I am not sure, if it is easy, but if it is possible, it would be good to
move "^" symbol that points to the place of the error, to the place inside ' '
where unclosed curly quote is located. As a user I would appreciate that.

So that's what I've seen while giving it superficial look...

#3Nikolay Shaplov
dhyan@nataraj.su
In reply to: Dmitry Belyavsky (#1)
Re: Ltree syntax improvement

В письме от вторник, 29 января 2019 г. 20:43:07 MSK пользователь Dmitry
Belyavsky написал:

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.

Sorry I still did not do full revision, I've stumbled up on other things in
the code, which, as I think should be fixed before final checking through. (I
guess that code do what is expected, since it passes tests and so on, it needs
recheck, but I'd like to do this full recheck only once)

So my doubts about this code is that a lot parts of code is just a copy-paste
of the same code that was written right above. And it can be rewritten in a
way that the same lines (or parts of lines) is repeated only once...
This should make code more readable, and make code more similar to the code of
postgres core, I've seen.

Here I will speak about lquery_in function from ltree_io.c.

1. May be it is better here to switch from else if (state ==
LQPRS_[something]) to switch (stat) { case LQPRS_[something]: }
because because here the main case is to determine what is the current state
of your state machine, do something with it, and then go to the next step.

Now after you've done what should be done in this step, you should make sure
that no other code is executed writing more ifs... If you use switch/case you
will be able to say break wherever you like, it will save you some ifs.

Theoretical example

(cl is for character length fc is for fist char)

if (cl==1 && fc =='1') {do_case1();}
else if (cl==1 && fc =='2') {do_case2();}
else if (cl==1 && fc =='3') {do_case3();}
else {do_otherwise();}

If it is wrapped in switch/case it will be like

if (cl==1)
{
if (fc =='1') {do_case1(); break;}
if (fc =='2') {do_case2(); break;}
if (fc =='3') {do_case3(); break;}
}
/*if nothing is found */
do_otherwise();

This for example will allow you to get rid of multiply charlen == 1 checks in

else if ((lptr->flag & LVAR_QUOTEDPART) == 0)
{
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;
}
}

There are a lot of them, I would suggest to reduce there number to one check
in the right place.

Similar thing is about "GETVAR(curqlevel) = lptr = (nodeitem *)
palloc0(sizeof(nodeitem) * numOR);" in this part of code.

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
{
GETVAR(curqlevel) = lptr = (nodeitem *)
palloc0(sizeof(nodeitem) * numOR);
lptr->start = ptr;
state = LQPRS_WAITDELIM;
curqlevel->numvar = 1;
}

It is repeated almost is every branch of this if-subtree... Can it be
rewritten the way it is repeated only once?

And so on.

So my request is to reduce multiply repetition of the same code...

PS. One other thing that I've been thinking over but did not came to final
conclusion...

It is often quite often case

if (t_iseq(ptr, 'a'){same_code(); code_for_a();}
else if (t_iseq(ptr, 'b'){same_code(); code_for_b();}
else if (t_iseq(ptr, 'c'){same_code(); code_for_c);}
else something_else();

I've been thinking if it is possible to move this to

const unsigned char c=TOUCHAR(ptr);
switch(c)
{
case 'a':
case 'b':
case 'c':
same_code();
if (c=='a') {code_for_a();}
if (c=='b') {code_for_b();}
if (c=='c') {code_for_c();}
break;
default:
something_else();
}

I did not still get, how valid is replacement of t_iseq() with TOUCHAR() +
"==". It is quite equivalent now, but I do not know how the core code is going
to evolve, so can't get final understanding. I even tried to discuss it with
Teodor (you've seen it) but still did come to any conclusion.

So for now status of this idea is "my considerations about ways to deduplicate
the code".

So this is all essential things I can say about this patch as it is now. May
be somebody can add something more...

#4Dmitry Belyavsky
beldmit@gmail.com
In reply to: Nikolay Shaplov (#3)
Re: Ltree syntax improvement

Dear Nikolay,

On Wed, Feb 20, 2019 at 12:28 PM Nikolay Shaplov <dhyan@nataraj.su> wrote:

В письме от вторник, 29 января 2019 г. 20:43:07 MSK пользователь Dmitry
Belyavsky написал:

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.

Sorry I still did not do full revision, I've stumbled up on other things
in
the code, which, as I think should be fixed before final checking through.
(I
guess that code do what is expected, since it passes tests and so on, it
needs
recheck, but I'd like to do this full recheck only once)

So my doubts about this code is that a lot parts of code is just a
copy-paste
of the same code that was written right above. And it can be rewritten in
a
way that the same lines (or parts of lines) is repeated only once...
This should make code more readable, and make code more similar to the
code of
postgres core, I've seen.

Here I will speak about lquery_in function from ltree_io.c.

1. May be it is better here to switch from else if (state ==
LQPRS_[something]) to switch (stat) { case LQPRS_[something]: }
because because here the main case is to determine what is the current
state
of your state machine, do something with it, and then go to the next step.

Now after you've done what should be done in this step, you should make
sure
that no other code is executed writing more ifs... If you use switch/case
you
will be able to say break wherever you like, it will save you some ifs.

I thought about it writing the code. But it significantly increased the
size of the patch
so I decided to follow the original coding style here.

Theoretical example

(cl is for character length fc is for fist char)

if (cl==1 && fc =='1') {do_case1();}
else if (cl==1 && fc =='2') {do_case2();}
else if (cl==1 && fc =='3') {do_case3();}
else {do_otherwise();}

If it is wrapped in switch/case it will be like

if (cl==1)
{
if (fc =='1') {do_case1(); break;}
if (fc =='2') {do_case2(); break;}
if (fc =='3') {do_case3(); break;}
}
/*if nothing is found */
do_otherwise();

This for example will allow you to get rid of multiply charlen == 1 checks
in

else if ((lptr->flag & LVAR_QUOTEDPART) == 0)

{

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;

}

}

Yes, this piece of code could be converted to the form you suggest, but
only partially, because the final else includes a strchr() call that,
being rewritten to the 'case' syntax will look ugly enough.

There are a lot of them, I would suggest to reduce there number to one
check
in the right place.

Similar thing is about "GETVAR(curqlevel) = lptr = (nodeitem *)
palloc0(sizeof(nodeitem) * numOR);" in this part of code.

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

{

GETVAR(curqlevel) = lptr = (nodeitem *)
palloc0(sizeof(nodeitem) * numOR);

lptr->start = ptr;

state = LQPRS_WAITDELIM;

curqlevel->numvar = 1;

}

It is repeated almost is every branch of this if-subtree... Can it be
rewritten the way it is repeated only once?

Well, I see the only one so long sequence of 'if'.

And so on.

So my request is to reduce multiply repetition of the same code...

PS. One other thing that I've been thinking over but did not came to final
conclusion...

It is often quite often case

if (t_iseq(ptr, 'a'){same_code(); code_for_a();}
else if (t_iseq(ptr, 'b'){same_code(); code_for_b();}
else if (t_iseq(ptr, 'c'){same_code(); code_for_c);}
else something_else();

I've been thinking if it is possible to move this to

const unsigned char c=TOUCHAR(ptr);
switch(c)
{
case 'a':
case 'b':
case 'c':
same_code();
if (c=='a') {code_for_a();}
if (c=='b') {code_for_b();}
if (c=='c') {code_for_c();}
break;
default:
something_else();
}

I did not still get, how valid is replacement of t_iseq() with TOUCHAR() +
"==". It is quite equivalent now, but I do not know how the core code is
going
to evolve, so can't get final understanding. I even tried to discuss it
with
Teodor (you've seen it) but still did come to any conclusion.

I agree with Teodor here. We do not know about various charsets so
the current syntax seems to be more safe.

So for now status of this idea is "my considerations about ways to
deduplicate
the code".

So this is all essential things I can say about this patch as it is now.
May
be somebody can add something more...

--
SY, Dmitry Belyavsky

#5David Steele
david@pgmasters.net
In reply to: Dmitry Belyavsky (#4)
Re: Re: Ltree syntax improvement

Hi Dmitry,

This patch appears too complex to be submitted to the last CF, as Andres
has also noted [1]/messages/by-id/20190216054526.zss2cufdxfeudr4i@alap3.anarazel.de.

I have set the target version to PG13.

Regards,
--
-David
david@pgmasters.net

[1]: /messages/by-id/20190216054526.zss2cufdxfeudr4i@alap3.anarazel.de
/messages/by-id/20190216054526.zss2cufdxfeudr4i@alap3.anarazel.de

#6Chris Travers
chris.travers@adjust.com
In reply to: Nikolay Shaplov (#2)
Re: Ltree syntax improvement

On Tue, Feb 5, 2019 at 2:27 PM Nikolay Shaplov <dhyan@nataraj.su> wrote:

В письме от вторник, 29 января 2019 г. 20:43:07 MSK пользователь Dmitry
Belyavsky написал:

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.

Hi!

Let's me join the review process...

I've just give a superficial look to the patch, read it through, and try
here
and there, so first I'll give feedback about exterior features of the
patch.

So, the first question: why do we need all this? The answer seems to be
obvious, but it would be good say it explicitly. What problems would it
solve?
Do these problems really exists?

Yes, it does. We maintain an extension (https://github.com/adjust/wltree)
which has a fixed separator (::) and allows any utf-8 character in the tree.

In our case we currently use our extended tree to store user-defined
hierarchies of labels, which might contain whitespace, Chinese, Japanese,
or Korean characters, etc.

I would love to be able to deprecate our work on this extension and
eventually use stock ltree.

The second question is about coding style. As far as I can see you've
passed
your code through pg_indent, but it does not solve all problems.

As far as I know, postgres commiters are quite strict about code width,
the
code should not be wider that 80 col, except places were string constants
are
introduced (There you can legally exceed 80 col). So I would advice to use
vim
with tabstop=4 and colorcolumn=80 and make sure that new code text does
not
cross red vertical line.

Comments. There is no fixed coding style for comments in postgres. Usually
this
means that it is better to follow coding in the code around. But ltree
does
not have much comments, so I would suggest to use the best style I've seen
in
the code
/*
* [function name]
* < tab ><tab> [Short description, what does it do]
*
* [long explanation how it works, several subparagraph if needed
*/
(Seen this in src/include/utils/rel.h)
or something like that.
At least it would be good to have explicit separation between descriptions
and
explanation of how it works.

And about comment
/*
* 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
*/

It has some formatting. But it should not. As far as I understand this
comment should be treated as single paragraph by reformatter, and
refomatted.
I do not understand why pg_indent have not done it. Documentation (https://
www.postgresql.org/docs/11/source-format.html) claims that if you want to
avoid reformatting, you should add "-------------" at the beginning and at
the
end of the comment. So it would be good either remove this formatting, or
add
"----" if you are sure you want to keep it.

Third part is about error messages.
First I've seen errors like elog(ERROR, "internal error during splitting
levels");. I've never seen error like that in postgres. May be if this
error
is in the part of the code, that should never be reached in real live, may
be
it would be good to change it to Assert? Or if this code can be normally
reached, add some more explanation of what had happened...

About other error messages.

There are messages like
errmsg("syntax error"),
errdetail("Unexpected end of line.")));

or

errmsg("escaping syntax error")));

In postgres there is error message style guide
https://www.postgresql.org/docs/11/error-style-guide.html

Here I would expect messages like that

Primary: Error parsing ltree path
Detail: Curly quote is opened and not closed

Or something like that, I am not expert in English, but this way it would
be
better for sure. Key points are: Primary message should point to general
area
where error occurred (there can be a large SQL with a lot of things in it,
so
it is good to point that problem is with ltree staff). And is is better to
word from the point of view of a user. What for you (and me) is unexpected
end
of line, for him it is unclosed curly quote.

Also I am not sure, if it is easy, but if it is possible, it would be good
to
move "^" symbol that points to the place of the error, to the place inside
' '
where unclosed curly quote is located. As a user I would appreciate that.

So that's what I've seen while giving it superficial look...

--
Best Regards,
Chris Travers
Head of Database

Tel: +49 162 9037 210 | Skype: einhverfr | www.adjust.com
Saarbrücker Straße 37a, 10405 Berlin

#7Filip Rembiałkowski
plk.zuber@gmail.com
In reply to: Chris Travers (#6)
Re: Ltree syntax improvement

Good day.

Sorry to pop in, but if you are active users of ltree, please let me
know if you rely on the exclamation operator (negative matching)?
I have just filed a patch, fixing very old bug - and it changes the
logic substantially.
https://commitfest.postgresql.org/23/2054/ - I'd be grateful for your
comments (please use the other thread).

Show quoted text

On Thu, Mar 7, 2019 at 1:10 PM Chris Travers <chris.travers@adjust.com> wrote:

On Tue, Feb 5, 2019 at 2:27 PM Nikolay Shaplov <dhyan@nataraj.su> wrote:

В письме от вторник, 29 января 2019 г. 20:43:07 MSK пользователь Dmitry
Belyavsky написал:

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.

Hi!

Let's me join the review process...

I've just give a superficial look to the patch, read it through, and try here
and there, so first I'll give feedback about exterior features of the patch.

So, the first question: why do we need all this? The answer seems to be
obvious, but it would be good say it explicitly. What problems would it solve?
Do these problems really exists?

Yes, it does. We maintain an extension (https://github.com/adjust/wltree) which has a fixed separator (::) and allows any utf-8 character in the tree.

In our case we currently use our extended tree to store user-defined hierarchies of labels, which might contain whitespace, Chinese, Japanese, or Korean characters, etc.

I would love to be able to deprecate our work on this extension and eventually use stock ltree.

The second question is about coding style. As far as I can see you've passed
your code through pg_indent, but it does not solve all problems.

As far as I know, postgres commiters are quite strict about code width, the
code should not be wider that 80 col, except places were string constants are
introduced (There you can legally exceed 80 col). So I would advice to use vim
with tabstop=4 and colorcolumn=80 and make sure that new code text does not
cross red vertical line.

Comments. There is no fixed coding style for comments in postgres. Usually this
means that it is better to follow coding in the code around. But ltree does
not have much comments, so I would suggest to use the best style I've seen in
the code
/*
* [function name]
* < tab ><tab> [Short description, what does it do]
*
* [long explanation how it works, several subparagraph if needed
*/
(Seen this in src/include/utils/rel.h)
or something like that.
At least it would be good to have explicit separation between descriptions and
explanation of how it works.

And about comment
/*
* 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
*/

It has some formatting. But it should not. As far as I understand this
comment should be treated as single paragraph by reformatter, and refomatted.
I do not understand why pg_indent have not done it. Documentation (https://
www.postgresql.org/docs/11/source-format.html) claims that if you want to
avoid reformatting, you should add "-------------" at the beginning and at the
end of the comment. So it would be good either remove this formatting, or add
"----" if you are sure you want to keep it.

Third part is about error messages.
First I've seen errors like elog(ERROR, "internal error during splitting
levels");. I've never seen error like that in postgres. May be if this error
is in the part of the code, that should never be reached in real live, may be
it would be good to change it to Assert? Or if this code can be normally
reached, add some more explanation of what had happened...

About other error messages.

There are messages like
errmsg("syntax error"),
errdetail("Unexpected end of line.")));

or

errmsg("escaping syntax error")));

In postgres there is error message style guide
https://www.postgresql.org/docs/11/error-style-guide.html

Here I would expect messages like that

Primary: Error parsing ltree path
Detail: Curly quote is opened and not closed

Or something like that, I am not expert in English, but this way it would be
better for sure. Key points are: Primary message should point to general area
where error occurred (there can be a large SQL with a lot of things in it, so
it is good to point that problem is with ltree staff). And is is better to
word from the point of view of a user. What for you (and me) is unexpected end
of line, for him it is unclosed curly quote.

Also I am not sure, if it is easy, but if it is possible, it would be good to
move "^" symbol that points to the place of the error, to the place inside ' '
where unclosed curly quote is located. As a user I would appreciate that.

So that's what I've seen while giving it superficial look...

--
Best Regards,
Chris Travers
Head of Database

Tel: +49 162 9037 210 | Skype: einhverfr | www.adjust.com
Saarbrücker Straße 37a, 10405 Berlin

#8Nikolay Shaplov
dhyan@nataraj.su
In reply to: Chris Travers (#6)
Re: Ltree syntax improvement

В письме от четверг, 7 марта 2019 г. 13:09:49 MSK пользователь Chris Travers
написал:

We maintain an extension (https://github.com/adjust/wltree)
which has a fixed separator (::) and allows any utf-8 character in the tree.

In our case we currently use our extended tree to store user-defined
hierarchies of labels, which might contain whitespace, Chinese, Japanese,
or Korean characters, etc.

I would love to be able to deprecate our work on this extension and
eventually use stock ltree.

I am afraid, that if would not be possible to have ltree with custom
separator. Or we need to pass a very long way to reach there.

To have custom separator we will need some place to store it.

We can use GUC variable, and set separator for ltree globally. It might solve
some of the problems. But will get some more. If for example we dump database,
and then restore it on instance where that GUC option is not set, everything
will be brocken.

It is not opclass option (that are discussed here on the list), because
opclass options is no about parsing, but about comparing things that was
already parsed.

It is not option of an attribute (if we can have custom attribute option),
because we can have ltree as a variable, without creating an attribute...

It should be an option of ltree type itself. I have no idea how we can
implement this. And who will allow us to commit such strange thing ;-)

#9Nikolay Shaplov
dhyan@nataraj.su
In reply to: Dmitry Belyavsky (#4)
1 attachment(s)
Re: Ltree syntax improvement

В письме от воскресенье, 24 февраля 2019 г. 14:31:55 MSK пользователь Dmitry
Belyavsky написал:

Hi! Am back here again.

I've been thinking about this patch a while... Come to some conclusions and
wrote some examples...

First I came to idea that the best way to simplify the code is change the
state machine from if to switch/case. Because in your code a lot of repetition
is done just because you can't say "Thats all, let's go to next symbol" in any
place in the code. Now it should follow all the branches of if-else tree that
is inside the state-machine "node" to get to the next symbol.

To show how simpler the things would be I changed the state machine processing
in lquery_in form if to switch/case, and changed the code for LQPRS_WAITLEVEL
state processing, removing duplicate code, using "break" wherever it is
possible

(The indention in this example is unproper to make diff more clear)

so from that much of code
=================
if (state == LQPRS_WAITLEVEL)
{
if (t_isspace(ptr))
{
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
{
GETVAR(curqlevel) = lptr = (nodeitem *) palloc0(sizeof(nodeitem) *
numOR);
lptr->start = ptr;
state = LQPRS_WAITDELIM;
curqlevel->numvar = 1;
}
}
=====================
I came to this
=====================
case LQPRS_WAITLEVEL:
if (t_isspace(ptr))
break; /* Just go to next symbol */
escaped_count = 0;
real_levels++;

if (charlen == 1)
{
if (strchr(".|@%{}", *ptr))
UNCHAR;
if (t_iseq(ptr, '*'))
{
state = LQPRS_WAITOPEN;
break;
}
}
GETVAR(curqlevel) = lptr = (nodeitem *) palloc0(sizeof(nodeitem) *
numOR);
lptr->start = ptr;
curqlevel->numvar = 1;
state = LQPRS_WAITDELIM;
if (charlen == 1)
{
if (t_iseq(ptr, '\\'))
{
state = LQPRS_WAITESCAPED;
break;
}
if (t_iseq(ptr, '!'))
{
lptr->start += 1 /*FIXME explain why */;
curqlevel->flag |= LQL_NOT;
hasnot = true;
}
else if (t_iseq(ptr, '"'))
{
lptr->flag |= LVAR_QUOTEDPART;
}
}
break;
=======
And this code is much more readable.... You can just read it aloud:
-Spaces we just skip
- on .|@%{} throws and exception
- for asterisk change state and nothing else
then for others allocate a buffer
- for slash we just set a flag
- for exclamation mark we set a flag and skip it
- for double quote set a flag.

All the logic are clear. And in your version of the code it is difficult to get
the idea from the code that simple.

So my suggestion is following:

1. Submit and commit the patch that changes state machines from ifs to switch/
cases, and nothing else. This will reindent the code, so in order to keep
further changes visible, we should change nothing else.

2. Change your code to switch/cases, use break to deduplicate code wherever is
possible, and commit it.

I can join you while working with this (but then I am not sure I would be able
to remain a reviewer...)

I've also attached an example I've quoted above, formatted as a diff, so it
would be easy to check how does it work. It should be applied after your
patch.
(I gave it a strange name ltree.not-a-patch hoping that commitfest software
will not treat it a new version of a patch)

Attachments:

ltree.not-a-patchtext/x-patch; charset=UTF-8; name=ltree.not-a-patchDownload
diff --git a/.gitignore b/.gitignore
index 794e35b..37331c2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -31,6 +31,7 @@ win32ver.rc
 *.exe
 lib*dll.def
 lib*.pc
+tags
 
 # Local excludes in root directory
 /GNUmakefile
diff --git a/contrib/ltree/ltree_io.c b/contrib/ltree/ltree_io.c
index e086c09..0ffb4b8 100644
--- a/contrib/ltree/ltree_io.c
+++ b/contrib/ltree/ltree_io.c
@@ -521,64 +521,50 @@ lquery_in(PG_FUNCTION_ARGS)
 	while (*ptr)
 	{
 		charlen = pg_mblen(ptr);
-
-		if (state == LQPRS_WAITLEVEL)
+		switch (state)
 		{
+		 case LQPRS_WAITLEVEL:
 			if (t_isspace(ptr))
-			{
-				ptr += charlen;
-				pos++;
-				continue;
-			}
+				break; /* Just go to next symbol */
 
 			escaped_count = 0;
 			real_levels++;
+
 			if (charlen == 1)
 			{
-				if (t_iseq(ptr, '!'))
+				if (strchr(".|@%{}", *ptr))
+					UNCHAR;
+
+				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, '\\'))
+					break;
+				}
+			}
+			GETVAR(curqlevel) = lptr = (nodeitem *) palloc0(sizeof(nodeitem) * numOR);
+			lptr->start = ptr;
+			curqlevel->numvar = 1;
+			state = LQPRS_WAITDELIM;
+			if (charlen == 1)
+			{
+				if (t_iseq(ptr, '\\'))
 				{
-					GETVAR(curqlevel) = lptr = (nodeitem *) palloc0(sizeof(nodeitem) * numOR);
-					lptr->start = ptr;
-					curqlevel->numvar = 1;
 					state = LQPRS_WAITESCAPED;
+					break;
 				}
-				else if (strchr(".|@%{}", *ptr))
+				if (t_iseq(ptr, '!'))
 				{
-					UNCHAR;
+					lptr->start += 1 /*FIXME explain why */;
+					curqlevel->flag |= LQL_NOT;
+					hasnot = true;
 				}
-				else
+				else if (t_iseq(ptr, '"'))
 				{
-					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;
-					}
+					lptr->flag |= LVAR_QUOTEDPART;
 				}
 			}
-			else
-			{
-				GETVAR(curqlevel) = lptr = (nodeitem *) palloc0(sizeof(nodeitem) * numOR);
-				lptr->start = ptr;
-				state = LQPRS_WAITDELIM;
-				curqlevel->numvar = 1;
-			}
-		}
-		else if (state == LQPRS_WAITVAR)
-		{
+			break;
+		case LQPRS_WAITVAR:
 			if (t_isspace(ptr))
 			{
 				ptr += charlen;
@@ -596,9 +582,9 @@ lquery_in(PG_FUNCTION_ARGS)
 			state = (t_iseq(ptr, '\\')) ? LQPRS_WAITESCAPED : LQPRS_WAITDELIM;
 			if (t_iseq(ptr, '"'))
 				lptr->flag |= LVAR_QUOTEDPART;
-		}
-		else if (state == LQPRS_WAITDELIM || state == LQPRS_WAITDELIMSTRICT)
-		{
+			break;
+		case LQPRS_WAITDELIM:
+		case LQPRS_WAITDELIMSTRICT:
 			if (charlen == 1 && t_iseq(ptr, '"'))
 			{
 				/* We are here if variant begins with ! */
@@ -725,9 +711,8 @@ lquery_in(PG_FUNCTION_ARGS)
 				tail_space_symbols = 0;
 				tail_space_bytes = 0;
 			}
-		}
-		else if (state == LQPRS_WAITOPEN)
-		{
+			break;
+		case LQPRS_WAITOPEN:
 			if (charlen == 1 && t_iseq(ptr, '{'))
 				state = LQPRS_WAITFNUM;
 			else if (charlen == 1 && t_iseq(ptr, '.'))
@@ -739,9 +724,8 @@ lquery_in(PG_FUNCTION_ARGS)
 			}
 			else
 				UNCHAR;
-		}
-		else if (state == LQPRS_WAITFNUM)
-		{
+			break;
+		case LQPRS_WAITFNUM:
 			if (charlen == 1 && t_iseq(ptr, ','))
 				state = LQPRS_WAITSNUM;
 			else if (t_isdigit(ptr))
@@ -751,9 +735,8 @@ lquery_in(PG_FUNCTION_ARGS)
 			}
 			else
 				UNCHAR;
-		}
-		else if (state == LQPRS_WAITSNUM)
-		{
+			break;
+		case LQPRS_WAITSNUM:
 			if (t_isdigit(ptr))
 			{
 				curqlevel->high = atoi(ptr);
@@ -766,16 +749,14 @@ lquery_in(PG_FUNCTION_ARGS)
 			}
 			else
 				UNCHAR;
-		}
-		else if (state == LQPRS_WAITCLOSE)
-		{
+			break;
+		case LQPRS_WAITCLOSE:
 			if (charlen == 1 && t_iseq(ptr, '}'))
 				state = LQPRS_WAITEND;
 			else if (!t_isdigit(ptr))
 				UNCHAR;
-		}
-		else if (state == LQPRS_WAITND)
-		{
+			break;
+		case LQPRS_WAITND:
 			if (charlen == 1 && t_iseq(ptr, '}'))
 			{
 				curqlevel->high = curqlevel->low;
@@ -785,9 +766,8 @@ lquery_in(PG_FUNCTION_ARGS)
 				state = LQPRS_WAITSNUM;
 			else if (!t_isdigit(ptr))
 				UNCHAR;
-		}
-		else if (state == LQPRS_WAITEND)
-		{
+			break;
+		case LQPRS_WAITEND:
 			if (charlen == 1 && (t_iseq(ptr, '.') || t_iseq(ptr, '|')))
 			{
 				state = LQPRS_WAITLEVEL;
@@ -795,16 +775,15 @@ lquery_in(PG_FUNCTION_ARGS)
 			}
 			else
 				UNCHAR;
-		}
-		else if (state == LQPRS_WAITESCAPED)
-		{
+			break;
+		case LQPRS_WAITESCAPED:
 			state = LQPRS_WAITDELIM;
 			escaped_count++;
-		}
-		else
+			break;
+		default:
 			/* internal error */
 			elog(ERROR, "internal error in parser");
-
+		}
 		ptr += charlen;
 		if (state == LQPRS_WAITDELIM || state == LQPRS_WAITDELIMSTRICT)
 			lptr->wlen++;
#10Dmitry Belyavsky
beldmit@gmail.com
In reply to: Nikolay Shaplov (#9)
Re: Ltree syntax improvement

Dear Nikolay,

Many thanks for your efforts!

On Sat, Apr 6, 2019 at 2:29 PM Nikolay Shaplov <dhyan@nataraj.su> wrote:

В письме от воскресенье, 24 февраля 2019 г. 14:31:55 MSK пользователь
Dmitry
Belyavsky написал:

Hi! Am back here again.

I've been thinking about this patch a while... Come to some conclusions
and
wrote some examples...

First I came to idea that the best way to simplify the code is change the
state machine from if to switch/case. Because in your code a lot of
repetition
is done just because you can't say "Thats all, let's go to next symbol" in
any
place in the code. Now it should follow all the branches of if-else tree
that
is inside the state-machine "node" to get to the next symbol.

To show how simpler the things would be I changed the state machine
processing
in lquery_in form if to switch/case, and changed the code for
LQPRS_WAITLEVEL
state processing, removing duplicate code, using "break" wherever it is
possible

(The indention in this example is unproper to make diff more clear)

so from that much of code
=================
if (state == LQPRS_WAITLEVEL)
{
if (t_isspace(ptr))
{
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
{
GETVAR(curqlevel) = lptr = (nodeitem *)
palloc0(sizeof(nodeitem) *
numOR);
lptr->start = ptr;
state = LQPRS_WAITDELIM;
curqlevel->numvar = 1;
}
}
=====================
I came to this
=====================
case LQPRS_WAITLEVEL:
if (t_isspace(ptr))
break; /* Just go to next symbol */
escaped_count = 0;
real_levels++;

if (charlen == 1)
{
if (strchr(".|@%{}", *ptr))
UNCHAR;
if (t_iseq(ptr, '*'))
{
state = LQPRS_WAITOPEN;
break;
}
}
GETVAR(curqlevel) = lptr = (nodeitem *)
palloc0(sizeof(nodeitem) *
numOR);
lptr->start = ptr;
curqlevel->numvar = 1;
state = LQPRS_WAITDELIM;
if (charlen == 1)
{
if (t_iseq(ptr, '\\'))
{
state = LQPRS_WAITESCAPED;
break;
}
if (t_iseq(ptr, '!'))
{
lptr->start += 1 /*FIXME explain
why */;
curqlevel->flag |= LQL_NOT;
hasnot = true;
}
else if (t_iseq(ptr, '"'))
{
lptr->flag |= LVAR_QUOTEDPART;
}
}
break;
=======
And this code is much more readable.... You can just read it aloud:
-Spaces we just skip
- on .|@%{} throws and exception
- for asterisk change state and nothing else
then for others allocate a buffer
- for slash we just set a flag
- for exclamation mark we set a flag and skip it
- for double quote set a flag.

All the logic are clear. And in your version of the code it is difficult
to get
the idea from the code that simple.

So my suggestion is following:

1. Submit and commit the patch that changes state machines from ifs to
switch/
cases, and nothing else. This will reindent the code, so in order to keep
further changes visible, we should change nothing else.

2. Change your code to switch/cases, use break to deduplicate code
wherever is
possible, and commit it.

I can join you while working with this (but then I am not sure I would be
able
to remain a reviewer...)

I've also attached an example I've quoted above, formatted as a diff, so
it
would be easy to check how does it work. It should be applied after your
patch.
(I gave it a strange name ltree.not-a-patch hoping that commitfest
software
will not treat it a new version of a patch)

I've applied your patch.
From my point of view, there is no major difference between case and chain
if here.
Neither case nor ifs allow extracting the common code to separate function
- just because there seem to be no identical pieces of code.

So yes, your patch makes sense, but I do not see any advantages of applying
it.

--
SY, Dmitry Belyavsky

#11Thomas Munro
thomas.munro@gmail.com
In reply to: Dmitry Belyavsky (#10)
Re: Ltree syntax improvement

On Wed, Apr 17, 2019 at 5:29 AM Dmitry Belyavsky <beldmit@gmail.com> wrote:

I've applied your patch.
From my point of view, there is no major difference between case and chain if here.
Neither case nor ifs allow extracting the common code to separate function - just because there seem to be no identical pieces of code.

Hi Dmitry,

The documentation doesn't build[1]https://travis-ci.org/postgresql-cfbot/postgresql/builds/555571856, due to invalid XML. Since I'm
here, here is some proof-reading of the English in the documentation:

   <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

"fewer than 256 characters in length", and
"<productname>PostgreSQL</productname>"

+ except <literal>\0</literal>. If label contains spaces, dots,
lquery modifiers,

"spaces, dots or 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.

"Escaping can be done with either a preceding backslash [...] or by
wrapping the whole label in double quotes [...]."

</para>

+ During converting text into internal representations, wrapping
double quotes

"During conversion to internal representation, "

+    and escaping backslashes are removed. During converting internal
+    representations into text, if the label does not contain any special

"During conversion from internal representation to text, "

+    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

"escaped with backslashes."

+  <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

"will all have the same internal representation and,"

+    converted from internal representation, will become <literal>42</literal>.
+    Literal <literal>abc def</literal> will turn into <literal>"abc
+    def"</literal>.
   </para>

[1]: https://travis-ci.org/postgresql-cfbot/postgresql/builds/555571856

--
Thomas Munro
https://enterprisedb.com

#12Dmitry Belyavsky
beldmit@gmail.com
In reply to: Thomas Munro (#11)
1 attachment(s)
Re: Ltree syntax improvement

Dear Thomas,

Thank you for your proofreading!

Please find the updated patch attached. It also contains the missing
escaping.

On Mon, Jul 8, 2019 at 10:39 AM Thomas Munro <thomas.munro@gmail.com> wrote:

On Wed, Apr 17, 2019 at 5:29 AM Dmitry Belyavsky <beldmit@gmail.com>
wrote:

I've applied your patch.
From my point of view, there is no major difference between case and

chain if here.

Neither case nor ifs allow extracting the common code to separate

function - just because there seem to be no identical pieces of code.

Hi Dmitry,

The documentation doesn't build[1], due to invalid XML. Since I'm
here, here is some proof-reading of the English in the documentation:

<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

"fewer than 256 characters in length", and
"<productname>PostgreSQL</productname>"

+ except <literal>\0</literal>. If label contains spaces, dots,
lquery modifiers,

"spaces, dots or 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.

"Escaping can be done with either a preceding backslash [...] or by
wrapping the whole label in double quotes [...]."

</para>

+ During converting text into internal representations, wrapping
double quotes

"During conversion to internal representation, "

+    and escaping backslashes are removed. During converting internal
+    representations into text, if the label does not contain any special

"During conversion from internal representation to text, "

+    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

"escaped with backslashes."

+  <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

"will all have the same internal representation and,"

+    converted from internal representation, will become
<literal>42</literal>.
+    Literal <literal>abc def</literal> will turn into <literal>"abc
+    def"</literal>.
</para>

[1] https://travis-ci.org/postgresql-cfbot/postgresql/builds/555571856

--
Thomas Munro
https://enterprisedb.com

--
SY, Dmitry Belyavsky

Attachments:

ltree_20190708.difftext/x-patch; charset=UTF-8; name=ltree_20190708.diffDownload
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 ~ '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 '"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, '|')))
@@ -139,6 +254,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
  */
@@ -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 ~ '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 '"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..a115562361 100644
--- a/doc/src/sgml/ltree.sgml
+++ b/doc/src/sgml/ltree.sgml
@@ -17,14 +17,38 @@
   <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 
+   fewer than 256 characters in length. Label may contain any character supported 
+   by <productname>PostgreSQL</productname> except <literal>\0</literal>. If label 
+   contains spaces, dots, or lquery modifiers, they may 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 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 backslashes. The list of special 
+    symbols for ltree includes space (<literal> </literal>), backslash and double quote,
+    lquery and ltxtquery also require escaping <literal>|</literal>, <literal>&amp;</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 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>
@@ -681,11 +705,13 @@ ltreetest=&gt; SELECT ins_label(path,2,'Space') FROM test WHERE path &lt;@ 'Top.
   <title>Authors</title>
 
   <para>
-   All work was done by Teodor Sigaev (<email>teodor@stack.net</email>) and
+   Initial version was done by Teodor Sigaev (<email>teodor@sigaev.ru</email>) and
    Oleg Bartunov (<email>oleg@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>beldmit@gmail.com</email>) directed by Teodor Sigaev. 
+   Comments and bug reports are welcome.
   </para>
  </sect2>
 
#13Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Dmitry Belyavsky (#12)
Re: Ltree syntax improvement

On 2019-Jul-08, Dmitry Belyavsky wrote:

Dear Thomas,

Thank you for your proofreading!

Please find the updated patch attached. It also contains the missing
escaping.

I think all these functions you're adding should have a short sentence
explaining what it does.

I'm not really convinced that we need this much testing. It seems a bit
excessive. Many of these very focused test SQL lines could be removed
with no loss of coverage, and many of the others could be grouped into
one.

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#14Dmitry Belyavsky
beldmit@gmail.com
In reply to: Alvaro Herrera (#13)
Re: Ltree syntax improvement

Dear Alvaro,

On Mon, Jul 8, 2019 at 11:16 PM Alvaro Herrera <alvherre@2ndquadrant.com>
wrote:

On 2019-Jul-08, Dmitry Belyavsky wrote:

Dear Thomas,

Thank you for your proofreading!

Please find the updated patch attached. It also contains the missing
escaping.

I think all these functions you're adding should have a short sentence
explaining what it does.

I'm not really convinced that we need this much testing. It seems a bit
excessive. Many of these very focused test SQL lines could be removed
with no loss of coverage, and many of the others could be grouped into
one.

I did not introduce any functions. I've just changed the parser.
I'm not sure that it makes sense to remove any tests as most of them were
written to catch really happened bugs during the implementation.

--
SY, Dmitry Belyavsky

#15Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Dmitry Belyavsky (#14)
Re: Ltree syntax improvement

On 2019-Jul-08, Dmitry Belyavsky wrote:

I did not introduce any functions. I've just changed the parser.

I mean the C-level functions -- count_parts_ors() and so on.

I'm not sure that it makes sense to remove any tests as most of them were
written to catch really happened bugs during the implementation.

Well, I don't mean to decrease the coverage, only to condense a lot of
little tests in a small powerful test.

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#16Dmitry Belyavsky
beldmit@gmail.com
In reply to: Alvaro Herrera (#15)
1 attachment(s)
Re: Ltree syntax improvement

On Mon, Jul 8, 2019 at 11:33 PM Alvaro Herrera <alvherre@2ndquadrant.com>
wrote:

On 2019-Jul-08, Dmitry Belyavsky wrote:

I did not introduce any functions. I've just changed the parser.

I mean the C-level functions -- count_parts_ors() and so on.

Added a comment to count_parts_ors()

The other functions introduced by me were already described.

--
SY, Dmitry Belyavsky

Attachments:

ltree_20190709.difftext/x-patch; charset=UTF-8; name=ltree_20190709.diffDownload
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 ~ '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 '"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..1a2c337fb0 100644
--- a/contrib/ltree/ltree_io.c
+++ b/contrib/ltree/ltree_io.c
@@ -33,6 +33,218 @@ typedef struct
 
 #define LTPRS_WAITNAME	0
 #define LTPRS_WAITDELIM 1
+#define LTPRS_WAITESCAPED 2
+#define LTPRS_WAITDELIMSTRICT 3
+
+/*
+ * 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)
+{
+	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 +253,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;
+			}
+			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
-				UNCHAR;
+		}
+		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;
+				}
+			}
+
+			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;
 			}
-			else if (!ISALNUM(ptr))
+
+			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 +418,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 +438,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 +449,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 +483,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 +496,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,136 +510,216 @@ 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)
 	{
 		charlen = pg_mblen(ptr);
-
-		if (state == LQPRS_WAITLEVEL)
+		switch (state)
 		{
-			if (ISALNUM(ptr))
+		 case LQPRS_WAITLEVEL:
+			if (t_isspace(ptr))
+				break; /* Just go to next symbol */
+
+			escaped_count = 0;
+			real_levels++;
+
+			if (charlen == 1)
 			{
-				GETVAR(curqlevel) = lptr = (nodeitem *) palloc0(sizeof(nodeitem) * (numOR + 1));
-				lptr->start = ptr;
-				state = LQPRS_WAITDELIM;
-				curqlevel->numvar = 1;
+				if (strchr(".|@%{}", *ptr))
+					UNCHAR;
+
+				if (t_iseq(ptr, '*'))
+				{
+					state = LQPRS_WAITOPEN;
+					break;
+				}
 			}
-			else if (charlen == 1 && t_iseq(ptr, '!'))
+			GETVAR(curqlevel) = lptr = (nodeitem *) palloc0(sizeof(nodeitem) * numOR);
+			lptr->start = ptr;
+			curqlevel->numvar = 1;
+			state = LQPRS_WAITDELIM;
+			if (charlen == 1)
 			{
-				GETVAR(curqlevel) = lptr = (nodeitem *) palloc0(sizeof(nodeitem) * (numOR + 1));
-				lptr->start = ptr + 1;
-				state = LQPRS_WAITDELIM;
-				curqlevel->numvar = 1;
-				curqlevel->flag |= LQL_NOT;
-				hasnot = true;
+				if (t_iseq(ptr, '\\'))
+				{
+					state = LQPRS_WAITESCAPED;
+					break;
+				}
+				if (t_iseq(ptr, '!'))
+				{
+					lptr->start += 1 /*FIXME explain why */;
+					curqlevel->flag |= LQL_NOT;
+					hasnot = true;
+				}
+				else if (t_iseq(ptr, '"'))
+				{
+					lptr->flag |= LVAR_QUOTEDPART;
+				}
 			}
-			else if (charlen == 1 && t_iseq(ptr, '*'))
-				state = LQPRS_WAITOPEN;
-			else
-				UNCHAR;
-		}
-		else if (state == LQPRS_WAITVAR)
-		{
-			if (ISALNUM(ptr))
+			break;
+		case LQPRS_WAITVAR:
+			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;
-		}
-		else if (state == LQPRS_WAITDELIM)
-		{
-			if (charlen == 1 && t_iseq(ptr, '@'))
+
+			state = (t_iseq(ptr, '\\')) ? LQPRS_WAITESCAPED : LQPRS_WAITDELIM;
+			if (t_iseq(ptr, '"'))
+				lptr->flag |= LVAR_QUOTEDPART;
+			break;
+		case LQPRS_WAITDELIM:
+		case LQPRS_WAITDELIMSTRICT:
+			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;
-		}
-		else if (state == LQPRS_WAITOPEN)
-		{
+			{
+				tail_space_symbols = 0;
+				tail_space_bytes = 0;
+			}
+			break;
+		case LQPRS_WAITOPEN:
 			if (charlen == 1 && t_iseq(ptr, '{'))
 				state = LQPRS_WAITFNUM;
 			else if (charlen == 1 && t_iseq(ptr, '.'))
@@ -350,9 +731,8 @@ lquery_in(PG_FUNCTION_ARGS)
 			}
 			else
 				UNCHAR;
-		}
-		else if (state == LQPRS_WAITFNUM)
-		{
+			break;
+		case LQPRS_WAITFNUM:
 			if (charlen == 1 && t_iseq(ptr, ','))
 				state = LQPRS_WAITSNUM;
 			else if (t_isdigit(ptr))
@@ -362,9 +742,8 @@ lquery_in(PG_FUNCTION_ARGS)
 			}
 			else
 				UNCHAR;
-		}
-		else if (state == LQPRS_WAITSNUM)
-		{
+			break;
+		case LQPRS_WAITSNUM:
 			if (t_isdigit(ptr))
 			{
 				curqlevel->high = atoi(ptr);
@@ -377,16 +756,14 @@ lquery_in(PG_FUNCTION_ARGS)
 			}
 			else
 				UNCHAR;
-		}
-		else if (state == LQPRS_WAITCLOSE)
-		{
+			break;
+		case LQPRS_WAITCLOSE:
 			if (charlen == 1 && t_iseq(ptr, '}'))
 				state = LQPRS_WAITEND;
 			else if (!t_isdigit(ptr))
 				UNCHAR;
-		}
-		else if (state == LQPRS_WAITND)
-		{
+			break;
+		case LQPRS_WAITND:
 			if (charlen == 1 && t_iseq(ptr, '}'))
 			{
 				curqlevel->high = curqlevel->low;
@@ -396,28 +773,38 @@ lquery_in(PG_FUNCTION_ARGS)
 				state = LQPRS_WAITSNUM;
 			else if (!t_isdigit(ptr))
 				UNCHAR;
-		}
-		else if (state == LQPRS_WAITEND)
-		{
-			if (charlen == 1 && t_iseq(ptr, '.'))
+			break;
+		case LQPRS_WAITEND:
+			if (charlen == 1 && (t_iseq(ptr, '.') || t_iseq(ptr, '|')))
 			{
 				state = LQPRS_WAITLEVEL;
 				curqlevel = NEXTLEV(curqlevel);
 			}
 			else
 				UNCHAR;
-		}
-		else
+			break;
+		case LQPRS_WAITESCAPED:
+			state = LQPRS_WAITDELIM;
+			escaped_count++;
+			break;
+		default:
 			/* 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 +812,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 +826,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 +859,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 +879,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 +908,8 @@ lquery_out(PG_FUNCTION_ARGS)
 			   *ptr;
 	int			i,
 				j,
-				totallen = 1;
+				totallen = 1,
+				filled = 0;
 	lquery_level *curqlevel;
 	lquery_variant *curtlevel;
 
@@ -549,6 +932,7 @@ lquery_out(PG_FUNCTION_ARGS)
 		{
 			*ptr = '.';
 			ptr++;
+			filled++;
 		}
 		if (curqlevel->numvar)
 		{
@@ -556,31 +940,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 +1007,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, '|')))
@@ -139,6 +254,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
  */
@@ -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 ~ '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 '"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..a115562361 100644
--- a/doc/src/sgml/ltree.sgml
+++ b/doc/src/sgml/ltree.sgml
@@ -17,14 +17,38 @@
   <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 
+   fewer than 256 characters in length. Label may contain any character supported 
+   by <productname>PostgreSQL</productname> except <literal>\0</literal>. If label 
+   contains spaces, dots, or lquery modifiers, they may 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 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 backslashes. The list of special 
+    symbols for ltree includes space (<literal> </literal>), backslash and double quote,
+    lquery and ltxtquery also require escaping <literal>|</literal>, <literal>&amp;</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 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>
@@ -681,11 +705,13 @@ ltreetest=&gt; SELECT ins_label(path,2,'Space') FROM test WHERE path &lt;@ 'Top.
   <title>Authors</title>
 
   <para>
-   All work was done by Teodor Sigaev (<email>teodor@stack.net</email>) and
+   Initial version was done by Teodor Sigaev (<email>teodor@sigaev.ru</email>) and
    Oleg Bartunov (<email>oleg@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>beldmit@gmail.com</email>) directed by Teodor Sigaev. 
+   Comments and bug reports are welcome.
   </para>
  </sect2>
 
#17Thomas Munro
thomas.munro@gmail.com
In reply to: Dmitry Belyavsky (#16)
Re: Ltree syntax improvement

On Wed, Jul 10, 2019 at 7:40 AM Dmitry Belyavsky <beldmit@gmail.com> wrote:

[ltree_20190709.diff]

Hi Dmitry,

You need to update contrib/ltree_plpython/expected/ltree_plpython.out,
otherwise check-world fails when built with Python support. The good
news is that it looks like it fails because you fixed something!
(Though I didn't check the details).

 CREATE FUNCTION test2() RETURNS ltree
 LANGUAGE plpythonu
 TRANSFORM FOR TYPE ltree
 AS $$
 return ['foo', 'bar', 'baz']
 $$;
 -- plpython to ltree is not yet implemented, so this will fail,
 -- because it will try to parse the Python list as an ltree input
 -- string.
 SELECT test2();
-ERROR:  syntax error at position 0
-CONTEXT:  while creating return value
-PL/Python function "test2"
+          test2
+-------------------------
+ "['foo', 'bar', 'baz']"
+(1 row)
+

--
Thomas Munro
https://enterprisedb.com

#18Dmitry Belyavsky
beldmit@gmail.com
In reply to: Thomas Munro (#17)
1 attachment(s)
Re: Ltree syntax improvement

Dear Thomas,

On Thu, Jul 11, 2019 at 11:20 AM Thomas Munro <thomas.munro@gmail.com>
wrote:

On Wed, Jul 10, 2019 at 7:40 AM Dmitry Belyavsky <beldmit@gmail.com>
wrote:

[ltree_20190709.diff]

Hi Dmitry,

You need to update contrib/ltree_plpython/expected/ltree_plpython.out,
otherwise check-world fails when built with Python support. The good
news is that it looks like it fails because you fixed something!
(Though I didn't check the details).

CREATE FUNCTION test2() RETURNS ltree
LANGUAGE plpythonu
TRANSFORM FOR TYPE ltree
AS $$
return ['foo', 'bar', 'baz']
$$;
-- plpython to ltree is not yet implemented, so this will fail,
-- because it will try to parse the Python list as an ltree input
-- string.
SELECT test2();
-ERROR:  syntax error at position 0
-CONTEXT:  while creating return value
-PL/Python function "test2"
+          test2
+-------------------------
+ "['foo', 'bar', 'baz']"
+(1 row)
+

See attached. I'm not familiar enough with python so I just removed the
failing tests.
If the main patch is accepted, the ltree_python extension should be
redesigned, I think...

--
SY, Dmitry Belyavsky

Attachments:

ltree_20190711.difftext/x-patch; charset=UTF-8; name=ltree_20190711.diffDownload
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 ~ '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 '"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..1a2c337fb0 100644
--- a/contrib/ltree/ltree_io.c
+++ b/contrib/ltree/ltree_io.c
@@ -33,6 +33,218 @@ typedef struct
 
 #define LTPRS_WAITNAME	0
 #define LTPRS_WAITDELIM 1
+#define LTPRS_WAITESCAPED 2
+#define LTPRS_WAITDELIMSTRICT 3
+
+/*
+ * 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)
+{
+	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 +253,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;
+			}
+			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
-				UNCHAR;
+		}
+		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;
+				}
+			}
+
+			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;
 			}
-			else if (!ISALNUM(ptr))
+
+			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 +418,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 +438,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 +449,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 +483,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 +496,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,136 +510,216 @@ 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)
 	{
 		charlen = pg_mblen(ptr);
-
-		if (state == LQPRS_WAITLEVEL)
+		switch (state)
 		{
-			if (ISALNUM(ptr))
+		 case LQPRS_WAITLEVEL:
+			if (t_isspace(ptr))
+				break; /* Just go to next symbol */
+
+			escaped_count = 0;
+			real_levels++;
+
+			if (charlen == 1)
 			{
-				GETVAR(curqlevel) = lptr = (nodeitem *) palloc0(sizeof(nodeitem) * (numOR + 1));
-				lptr->start = ptr;
-				state = LQPRS_WAITDELIM;
-				curqlevel->numvar = 1;
+				if (strchr(".|@%{}", *ptr))
+					UNCHAR;
+
+				if (t_iseq(ptr, '*'))
+				{
+					state = LQPRS_WAITOPEN;
+					break;
+				}
 			}
-			else if (charlen == 1 && t_iseq(ptr, '!'))
+			GETVAR(curqlevel) = lptr = (nodeitem *) palloc0(sizeof(nodeitem) * numOR);
+			lptr->start = ptr;
+			curqlevel->numvar = 1;
+			state = LQPRS_WAITDELIM;
+			if (charlen == 1)
 			{
-				GETVAR(curqlevel) = lptr = (nodeitem *) palloc0(sizeof(nodeitem) * (numOR + 1));
-				lptr->start = ptr + 1;
-				state = LQPRS_WAITDELIM;
-				curqlevel->numvar = 1;
-				curqlevel->flag |= LQL_NOT;
-				hasnot = true;
+				if (t_iseq(ptr, '\\'))
+				{
+					state = LQPRS_WAITESCAPED;
+					break;
+				}
+				if (t_iseq(ptr, '!'))
+				{
+					lptr->start += 1 /*FIXME explain why */;
+					curqlevel->flag |= LQL_NOT;
+					hasnot = true;
+				}
+				else if (t_iseq(ptr, '"'))
+				{
+					lptr->flag |= LVAR_QUOTEDPART;
+				}
 			}
-			else if (charlen == 1 && t_iseq(ptr, '*'))
-				state = LQPRS_WAITOPEN;
-			else
-				UNCHAR;
-		}
-		else if (state == LQPRS_WAITVAR)
-		{
-			if (ISALNUM(ptr))
+			break;
+		case LQPRS_WAITVAR:
+			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;
-		}
-		else if (state == LQPRS_WAITDELIM)
-		{
-			if (charlen == 1 && t_iseq(ptr, '@'))
+
+			state = (t_iseq(ptr, '\\')) ? LQPRS_WAITESCAPED : LQPRS_WAITDELIM;
+			if (t_iseq(ptr, '"'))
+				lptr->flag |= LVAR_QUOTEDPART;
+			break;
+		case LQPRS_WAITDELIM:
+		case LQPRS_WAITDELIMSTRICT:
+			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;
-		}
-		else if (state == LQPRS_WAITOPEN)
-		{
+			{
+				tail_space_symbols = 0;
+				tail_space_bytes = 0;
+			}
+			break;
+		case LQPRS_WAITOPEN:
 			if (charlen == 1 && t_iseq(ptr, '{'))
 				state = LQPRS_WAITFNUM;
 			else if (charlen == 1 && t_iseq(ptr, '.'))
@@ -350,9 +731,8 @@ lquery_in(PG_FUNCTION_ARGS)
 			}
 			else
 				UNCHAR;
-		}
-		else if (state == LQPRS_WAITFNUM)
-		{
+			break;
+		case LQPRS_WAITFNUM:
 			if (charlen == 1 && t_iseq(ptr, ','))
 				state = LQPRS_WAITSNUM;
 			else if (t_isdigit(ptr))
@@ -362,9 +742,8 @@ lquery_in(PG_FUNCTION_ARGS)
 			}
 			else
 				UNCHAR;
-		}
-		else if (state == LQPRS_WAITSNUM)
-		{
+			break;
+		case LQPRS_WAITSNUM:
 			if (t_isdigit(ptr))
 			{
 				curqlevel->high = atoi(ptr);
@@ -377,16 +756,14 @@ lquery_in(PG_FUNCTION_ARGS)
 			}
 			else
 				UNCHAR;
-		}
-		else if (state == LQPRS_WAITCLOSE)
-		{
+			break;
+		case LQPRS_WAITCLOSE:
 			if (charlen == 1 && t_iseq(ptr, '}'))
 				state = LQPRS_WAITEND;
 			else if (!t_isdigit(ptr))
 				UNCHAR;
-		}
-		else if (state == LQPRS_WAITND)
-		{
+			break;
+		case LQPRS_WAITND:
 			if (charlen == 1 && t_iseq(ptr, '}'))
 			{
 				curqlevel->high = curqlevel->low;
@@ -396,28 +773,38 @@ lquery_in(PG_FUNCTION_ARGS)
 				state = LQPRS_WAITSNUM;
 			else if (!t_isdigit(ptr))
 				UNCHAR;
-		}
-		else if (state == LQPRS_WAITEND)
-		{
-			if (charlen == 1 && t_iseq(ptr, '.'))
+			break;
+		case LQPRS_WAITEND:
+			if (charlen == 1 && (t_iseq(ptr, '.') || t_iseq(ptr, '|')))
 			{
 				state = LQPRS_WAITLEVEL;
 				curqlevel = NEXTLEV(curqlevel);
 			}
 			else
 				UNCHAR;
-		}
-		else
+			break;
+		case LQPRS_WAITESCAPED:
+			state = LQPRS_WAITDELIM;
+			escaped_count++;
+			break;
+		default:
 			/* 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 +812,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 +826,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 +859,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 +879,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 +908,8 @@ lquery_out(PG_FUNCTION_ARGS)
 			   *ptr;
 	int			i,
 				j,
-				totallen = 1;
+				totallen = 1,
+				filled = 0;
 	lquery_level *curqlevel;
 	lquery_variant *curtlevel;
 
@@ -549,6 +932,7 @@ lquery_out(PG_FUNCTION_ARGS)
 		{
 			*ptr = '.';
 			ptr++;
+			filled++;
 		}
 		if (curqlevel->numvar)
 		{
@@ -556,31 +940,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 +1007,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, '|')))
@@ -139,6 +254,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
  */
@@ -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 ~ '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 '"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/contrib/ltree_plpython/expected/ltree_plpython.out b/contrib/ltree_plpython/expected/ltree_plpython.out
index 4779755fc8..1fb182e71a 100644
--- a/contrib/ltree_plpython/expected/ltree_plpython.out
+++ b/contrib/ltree_plpython/expected/ltree_plpython.out
@@ -28,16 +28,3 @@ INFO:  ['aa', 'bb', 'cc']
       3
 (1 row)
 
-CREATE FUNCTION test2() RETURNS ltree
-LANGUAGE plpythonu
-TRANSFORM FOR TYPE ltree
-AS $$
-return ['foo', 'bar', 'baz']
-$$;
--- plpython to ltree is not yet implemented, so this will fail,
--- because it will try to parse the Python list as an ltree input
--- string.
-SELECT test2();
-ERROR:  syntax error at position 0
-CONTEXT:  while creating return value
-PL/Python function "test2"
diff --git a/contrib/ltree_plpython/sql/ltree_plpython.sql b/contrib/ltree_plpython/sql/ltree_plpython.sql
index 210f5428a5..e411867262 100644
--- a/contrib/ltree_plpython/sql/ltree_plpython.sql
+++ b/contrib/ltree_plpython/sql/ltree_plpython.sql
@@ -21,16 +21,3 @@ return len(val)
 $$;
 
 SELECT test1n('aa.bb.cc'::ltree);
-
-
-CREATE FUNCTION test2() RETURNS ltree
-LANGUAGE plpythonu
-TRANSFORM FOR TYPE ltree
-AS $$
-return ['foo', 'bar', 'baz']
-$$;
-
--- plpython to ltree is not yet implemented, so this will fail,
--- because it will try to parse the Python list as an ltree input
--- string.
-SELECT test2();
diff --git a/doc/src/sgml/ltree.sgml b/doc/src/sgml/ltree.sgml
index 3ddd335b8c..a115562361 100644
--- a/doc/src/sgml/ltree.sgml
+++ b/doc/src/sgml/ltree.sgml
@@ -17,14 +17,38 @@
   <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 
+   fewer than 256 characters in length. Label may contain any character supported 
+   by <productname>PostgreSQL</productname> except <literal>\0</literal>. If label 
+   contains spaces, dots, or lquery modifiers, they may 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 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 backslashes. The list of special 
+    symbols for ltree includes space (<literal> </literal>), backslash and double quote,
+    lquery and ltxtquery also require escaping <literal>|</literal>, <literal>&amp;</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 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>
@@ -681,11 +705,13 @@ ltreetest=&gt; SELECT ins_label(path,2,'Space') FROM test WHERE path &lt;@ 'Top.
   <title>Authors</title>
 
   <para>
-   All work was done by Teodor Sigaev (<email>teodor@stack.net</email>) and
+   Initial version was done by Teodor Sigaev (<email>teodor@sigaev.ru</email>) and
    Oleg Bartunov (<email>oleg@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>beldmit@gmail.com</email>) directed by Teodor Sigaev. 
+   Comments and bug reports are welcome.
   </para>
  </sect2>
 
#19Nikita Glukhov
n.gluhov@postgrespro.ru
In reply to: Dmitry Belyavsky (#18)
1 attachment(s)
Re: Ltree syntax improvement

Hi!

I have looked at the patch and found some problems.

1. I fixed some bugs (fixed patch with additional test cases is attached):

-- NULL 'lptr' pointer dereference at lquery_in()
=# SELECT '*'::lquery;
-- crash

-- '|' after '*{n}' is wrongly handled (LQPRS_WAITEND state)
=# SELECT '*{1}|2'::lquery;
WARNING: problem in alloc set MessageContext: req size > alloc size for chunk 0x1c2d508 in block 0x1c2bc58
WARNING: problem in alloc set MessageContext: req size > alloc size for chunk 0x1c2d508 in block 0x1c2bc58
lquery
-------------
*{1}.*{,48}
(1 row)

-- wrong handling of trailing whitespace
=# SELECT '"a" '::ltree;
ERROR: name of level is empty
LINE 1: SELECT '"a" '::ltree;
^
DETAIL: Name length is 0 in position 4.

=# SELECT '"a" '::lquery;
ERROR: name of level is empty
LINE 1: SELECT '"a" '::lquery;
^
DETAIL: Name length is 0 in position 4.

-- backslashes are not escaped in ltree_out()/lquery_out(),
-- which is not consistent with ltree_in()/lquery_in()
=# SELECT '"\\"'::ltree;
ltree
-------
"\"
(1 row)

=# SELECT '"\\"'::lquery;
lquery
--------
"\"
(1 row)

=# SELECT '"\\"'::ltree::text::ltree;
ERROR: syntax error
DETAIL: Unexpected end of line.

=# SELECT '"\\"'::lquery::text::lquery;
ERROR: syntax error
DETAIL: Unexpected end of line.

2. There are inconsistencies in whitespace handling before and after *, @, %, {}
(I have not fixed this because I'm not sure how it is supposed to work):

-- whitespace before '*' is not ignored
=# SELECT '"a" *'::lquery;
lquery
--------
"a\""*
(1 row)

=# SELECT 'a *'::lquery;
lquery
--------
"a "*
(1 row)

-- whitespace after '*' and '{}' is disallowed
=# 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 '*{1} .a'::lquery;
ERROR: syntax error at position 4
LINE 1: SELECT '*{1} .a'::lquery;
^

-- but the whitespace after levels without '*@%{}' is allowed
=# SELECT 'a |b'::lquery;
lquery
--------
a|b
(1 row)

3. Empty level names between '!' and '|' are allowed. This behavior can be
seen on master, so it seems that we cannot fix it now:

-- master
=# SELECT '!|a'::lquery;
lquery
--------
!|a
(1 row)

-- patched
=# SELECT '!|a'::lquery;
lquery
--------
!""|a
(1 row)

-- empty level names in other places are disallowed
=# SELECT '!a|'::lquery;
ERROR: syntax error
LINE 1: SELECT '!a|'::lquery;
^
DETAIL: Unexpected end of line.

=# SELECT '|a'::lquery;
ERROR: syntax error at position 0
LINE 1: SELECT '|a'::lquery;
^

4. It looks strange to me that leading and trailing unquoted whitespace is
ignored, but the internal whitespace is treated like a quoted:

=# SELECT ' a b . c d '::lquery;
lquery
-----------------
"a b"."c d"
(1 row)

I would prefer unquoted unescaped whitespace to be a delimiter always.

5. It seems wrong to me that ltree and lquery have different special character
sets now. This leads to the fact that arbitrary ltree text cannot be used
directly as lquery text, as it seemed to be before the syntax improvements:

=# SELECT 'a|b'::ltree::text::lquery;
lquery
--------
a|b
(1 row)

=# SELECT '"a|b"'::ltree::text::lquery;
lquery
--------
a|b
(1 row)

=# SELECT '"a|b"'::lquery;
lquery
--------
"a|b"
(1 row)

There might not be a problem if we had ltree::lquery cast.

Also I think that text[]::ltree/ltree::text[] casts for ltree
construction/deconstruction from text level names can be very useful.

6. ltree and escpecially lquery parsing code still look too complicated for me,
and I believe that the bugs described above are a direct consequence of this.
So the code needs to be refactored, maybe even without using of state machines.

On 11.07.2019 20:49, Dmitry Belyavsky wrote:

On Thu, Jul 11, 2019 at 11:20 AM Thomas Munro <thomas.munro@gmail.com
<mailto:thomas.munro@gmail.com>> wrote:

On Wed, Jul 10, 2019 at 7:40 AM Dmitry Belyavsky
<beldmit@gmail.com <mailto:beldmit@gmail.com>> wrote:

[ltree_20190709.diff]

You need to update contrib/ltree_plpython/expected/ltree_plpython.out,
otherwise check-world fails when built with Python support. The good
news is that it looks like it fails because you fixed something!
(Though I didn't check the details).

 CREATE FUNCTION test2() RETURNS ltree
 LANGUAGE plpythonu
 TRANSFORM FOR TYPE ltree
 AS $$
 return ['foo', 'bar', 'baz']
 $$;
 -- plpython to ltree is not yet implemented, so this will fail,
 -- because it will try to parse the Python list as an ltree input
 -- string.
 SELECT test2();
-ERROR:  syntax error at position 0
-CONTEXT:  while creating return value
-PL/Python function "test2"
+          test2
+-------------------------
+ "['foo', 'bar', 'baz']"
+(1 row)
+

See attached. I'm not familiar enough with python so I just removed
the failing tests.
If the main patch is accepted, the ltree_python extension should be
redesigned, I think...

7. ltree_plpython test does not fail now because Python list is converted to a
text and then to a ltree, and the textual representation of a Python list has
become a valid ltree text:

SELECT $$['foo', 'bar', 'baz']$$::ltree;
ltree
-------------------------
"['foo', 'bar', 'baz']"
(1 row)

So Python lists can be now successfully converted to ltrees without a transform,
but the result is not that is expected ('foo.bar.baz'::ltree).

--
Nikita Glukhov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

Attachments:

0001-Ltree-syntax-improvements-20190717.patchtext/x-patch; name=0001-Ltree-syntax-improvements-20190717.patchDownload
From dc311fd98ef69b6285dc25d301aa686b48850d63 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <n.gluhov@postgrespro.ru>
Date: Tue, 16 Jul 2019 17:59:32 +0300
Subject: [PATCH] Ltree syntax improvements

---
 contrib/ltree/expected/ltree.out                   | 1619 ++++++++++++++++++++
 contrib/ltree/ltree.h                              |    5 +-
 contrib/ltree/ltree_io.c                           |  749 ++++++---
 contrib/ltree/ltxtquery_io.c                       |  222 ++-
 contrib/ltree/sql/ltree.sql                        |  384 +++++
 contrib/ltree_plpython/expected/ltree_plpython.out |   13 -
 contrib/ltree_plpython/sql/ltree_plpython.sql      |   13 -
 doc/src/sgml/ltree.sgml                            |   40 +-
 8 files changed, 2797 insertions(+), 248 deletions(-)

diff --git a/contrib/ltree/expected/ltree.out b/contrib/ltree/expected/ltree.out
index 8226930..bb2b6c1 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
@@ -445,6 +446,16 @@ SELECT '1.*.4|3|2.*{1}'::lquery;
  1.*.4|3|2.*{1}
 (1 row)
 
+SELECT '*'::lquery;
+ lquery 
+--------
+ *
+(1 row)
+
+SELECT '*{1}|2'::lquery;
+ERROR:  syntax error at position 4
+LINE 1: SELECT '*{1}|2'::lquery;
+               ^
 SELECT 'qwerty%@*.tu'::lquery;
     lquery    
 --------------
@@ -7679,3 +7690,1611 @@ 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" '::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 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 ~ '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 '"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 366e580..e778f7d 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 f54f037..2efcd04 100644
--- a/contrib/ltree/ltree_io.c
+++ b/contrib/ltree/ltree_io.c
@@ -33,6 +33,217 @@ typedef struct
 
 #define LTPRS_WAITNAME	0
 #define LTPRS_WAITDELIM 1
+#define LTPRS_WAITESCAPED 2
+#define LTPRS_WAITDELIMSTRICT 3
+
+/*
+ * 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)
+{
+	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 or backslashes, we need to escape all of them and also
+ * initial and final quote, so return 2 + number of quotes/backslashes
+ */
+int
+bytes_to_escape(const char *start, const int len, const char *to_escape)
+{
+	uint16		copied = 0;
+	int			charlen;
+	int			escapes = 0;
+	bool		quotes = false;
+	const char *buf = start;
+
+	if (len == 0)
+		return 2;
+
+	while (*start && copied < len)
+	{
+		charlen = pg_mblen(buf);
+
+		if (charlen == 1)
+		{
+			if (t_iseq(buf, '"') || t_iseq(buf, '\\'))
+				escapes++;
+			else if (strchr(to_escape, *buf))
+				quotes = true;
+		}
+
+		if (copied + charlen > len)
+			elog(ERROR, "internal error during merging levels");
+
+		buf += charlen;
+		copied += charlen;
+	}
+
+	return (escapes > 0) ? escapes + 2 : quotes ? 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, '"') || 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 +252,161 @@ 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)
+			{
+				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;
+				}
+			}
+
+			if (t_isspace(ptr))
+			{
+				tail_space_symbols++;
+				tail_space_bytes += charlen;
+			}
+			else
 			{
-				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)));
+				tail_space_symbols = 0;
+				tail_space_bytes = 0;
+			}
+		}
+		else if (state == LTPRS_WAITDELIMSTRICT)
+		{
+			if (t_isspace(ptr))
+			{
+				tail_space_symbols++;
+				tail_space_bytes += charlen;
+			}
+			else
+			{
+				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;
+
+				tail_space_symbols = 0;
+				tail_space_bytes = 0;
 			}
-			else if (!ISALNUM(ptr))
-				UNCHAR;
 		}
 		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 +420,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 +440,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 +451,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 +485,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 +498,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,136 +512,197 @@ 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)
 	{
 		charlen = pg_mblen(ptr);
-
-		if (state == LQPRS_WAITLEVEL)
+		switch (state)
 		{
-			if (ISALNUM(ptr))
+		 case LQPRS_WAITLEVEL:
+			if (t_isspace(ptr))
+				break; /* Just go to next symbol */
+
+			escaped_count = 0;
+			real_levels++;
+
+			if (charlen == 1)
 			{
-				GETVAR(curqlevel) = lptr = (nodeitem *) palloc0(sizeof(nodeitem) * (numOR + 1));
-				lptr->start = ptr;
-				state = LQPRS_WAITDELIM;
-				curqlevel->numvar = 1;
+				if (strchr(".|@%{}", *ptr))
+					UNCHAR;
+
+				if (t_iseq(ptr, '*'))
+				{
+					state = LQPRS_WAITOPEN;
+					break;
+				}
 			}
-			else if (charlen == 1 && t_iseq(ptr, '!'))
+			GETVAR(curqlevel) = lptr = (nodeitem *) palloc0(sizeof(nodeitem) * numOR);
+			lptr->start = ptr;
+			curqlevel->numvar = 1;
+			state = LQPRS_WAITDELIM;
+			if (charlen == 1)
 			{
-				GETVAR(curqlevel) = lptr = (nodeitem *) palloc0(sizeof(nodeitem) * (numOR + 1));
-				lptr->start = ptr + 1;
-				state = LQPRS_WAITDELIM;
-				curqlevel->numvar = 1;
-				curqlevel->flag |= LQL_NOT;
-				hasnot = true;
+				if (t_iseq(ptr, '\\'))
+				{
+					state = LQPRS_WAITESCAPED;
+					break;
+				}
+				if (t_iseq(ptr, '!'))
+				{
+					lptr->start += 1 /*FIXME explain why */;
+					curqlevel->flag |= LQL_NOT;
+					hasnot = true;
+				}
+				else if (t_iseq(ptr, '"'))
+				{
+					lptr->flag |= LVAR_QUOTEDPART;
+				}
 			}
-			else if (charlen == 1 && t_iseq(ptr, '*'))
-				state = LQPRS_WAITOPEN;
-			else
-				UNCHAR;
-		}
-		else if (state == LQPRS_WAITVAR)
-		{
-			if (ISALNUM(ptr))
+			break;
+		case LQPRS_WAITVAR:
+			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;
-		}
-		else if (state == LQPRS_WAITDELIM)
-		{
-			if (charlen == 1 && t_iseq(ptr, '@'))
+
+			state = (t_iseq(ptr, '\\')) ? LQPRS_WAITESCAPED : LQPRS_WAITDELIM;
+			if (t_iseq(ptr, '"'))
+				lptr->flag |= LVAR_QUOTEDPART;
+			break;
+		case LQPRS_WAITDELIM:
+		case LQPRS_WAITDELIMSTRICT:
+			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;
+				}
+				else if (lptr->flag & LVAR_QUOTEDPART)
+				{
+					lptr->flag &= ~LVAR_QUOTEDPART;
+					state = LQPRS_WAITDELIMSTRICT;
+				}
+				else
 					UNCHAR;
-				lptr->flag |= LVAR_INCASE;
-				curqlevel->flag |= LVAR_INCASE;
 			}
-			else if (charlen == 1 && t_iseq(ptr, '*'))
+			else if ((lptr->flag & LVAR_QUOTEDPART) == 0)
 			{
-				if (lptr->start == ptr)
-					UNCHAR;
-				lptr->flag |= LVAR_ANYEND;
-				curqlevel->flag |= LVAR_ANYEND;
+				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))
+							UNCHAR;
+					}
+					else if (lptr->flag & ~LVAR_QUOTEDPART)
+						UNCHAR;
+				}
 			}
-			else if (charlen == 1 && t_iseq(ptr, '%'))
+			else if (charlen == 1 && t_iseq(ptr, '\\'))
 			{
-				if (lptr->start == ptr)
+				if (state == LQPRS_WAITDELIMSTRICT)
 					UNCHAR;
-				lptr->flag |= LVAR_SUBLEXEME;
-				curqlevel->flag |= LVAR_SUBLEXEME;
+				if (lptr->flag & ~LVAR_QUOTEDPART)
+					UNCHAR;
+				state = LQPRS_WAITESCAPED;
 			}
-			else if (charlen == 1 && t_iseq(ptr, '|'))
+			else if (state == LQPRS_WAITDELIMSTRICT)
 			{
-				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 (!t_isspace(ptr))
+					UNCHAR;
 			}
-			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)));
+			else if (lptr->flag & ~LVAR_QUOTEDPART)
+				UNCHAR;
 
-				state = LQPRS_WAITLEVEL;
-				curqlevel = NEXTLEV(curqlevel);
-			}
-			else if (ISALNUM(ptr))
+			if (t_isspace(ptr))
 			{
-				if (lptr->flag)
-					UNCHAR;
+				tail_space_symbols++;
+				tail_space_bytes += charlen;
 			}
 			else
-				UNCHAR;
-		}
-		else if (state == LQPRS_WAITOPEN)
-		{
+			{
+				tail_space_symbols = 0;
+				tail_space_bytes = 0;
+			}
+			break;
+		case LQPRS_WAITOPEN:
 			if (charlen == 1 && t_iseq(ptr, '{'))
 				state = LQPRS_WAITFNUM;
 			else if (charlen == 1 && t_iseq(ptr, '.'))
@@ -350,9 +714,8 @@ lquery_in(PG_FUNCTION_ARGS)
 			}
 			else
 				UNCHAR;
-		}
-		else if (state == LQPRS_WAITFNUM)
-		{
+			break;
+		case LQPRS_WAITFNUM:
 			if (charlen == 1 && t_iseq(ptr, ','))
 				state = LQPRS_WAITSNUM;
 			else if (t_isdigit(ptr))
@@ -362,9 +725,8 @@ lquery_in(PG_FUNCTION_ARGS)
 			}
 			else
 				UNCHAR;
-		}
-		else if (state == LQPRS_WAITSNUM)
-		{
+			break;
+		case LQPRS_WAITSNUM:
 			if (t_isdigit(ptr))
 			{
 				curqlevel->high = atoi(ptr);
@@ -377,16 +739,14 @@ lquery_in(PG_FUNCTION_ARGS)
 			}
 			else
 				UNCHAR;
-		}
-		else if (state == LQPRS_WAITCLOSE)
-		{
+			break;
+		case LQPRS_WAITCLOSE:
 			if (charlen == 1 && t_iseq(ptr, '}'))
 				state = LQPRS_WAITEND;
 			else if (!t_isdigit(ptr))
 				UNCHAR;
-		}
-		else if (state == LQPRS_WAITND)
-		{
+			break;
+		case LQPRS_WAITND:
 			if (charlen == 1 && t_iseq(ptr, '}'))
 			{
 				curqlevel->high = curqlevel->low;
@@ -396,9 +756,8 @@ lquery_in(PG_FUNCTION_ARGS)
 				state = LQPRS_WAITSNUM;
 			else if (!t_isdigit(ptr))
 				UNCHAR;
-		}
-		else if (state == LQPRS_WAITEND)
-		{
+			break;
+		case LQPRS_WAITEND:
 			if (charlen == 1 && t_iseq(ptr, '.'))
 			{
 				state = LQPRS_WAITLEVEL;
@@ -406,18 +765,29 @@ lquery_in(PG_FUNCTION_ARGS)
 			}
 			else
 				UNCHAR;
-		}
-		else
+			break;
+		case LQPRS_WAITESCAPED:
+			state = LQPRS_WAITDELIM;
+			escaped_count++;
+			break;
+		default:
 			/* 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 && 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 +795,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;
@@ -453,7 +812,7 @@ lquery_in(PG_FUNCTION_ARGS)
 
 	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 +836,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 +856,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 +885,8 @@ lquery_out(PG_FUNCTION_ARGS)
 			   *ptr;
 	int			i,
 				j,
-				totallen = 1;
+				totallen = 1,
+				filled = 0;
 	lquery_level *curqlevel;
 	lquery_variant *curtlevel;
 
@@ -549,6 +909,7 @@ lquery_out(PG_FUNCTION_ARGS)
 		{
 			*ptr = '.';
 			ptr++;
+			filled++;
 		}
 		if (curqlevel->numvar)
 		{
@@ -556,31 +917,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 +984,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 b576676..77f300f 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 846b04e..7dab2fb 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
@@ -87,6 +89,8 @@ SELECT '1.*.4|3|2.*{1,4}'::lquery;
 SELECT '1.*.4|3|2.*{,4}'::lquery;
 SELECT '1.*.4|3|2.*{1,}'::lquery;
 SELECT '1.*.4|3|2.*{1}'::lquery;
+SELECT '*'::lquery;
+SELECT '*{1}|2'::lquery;
 SELECT 'qwerty%@*.tu'::lquery;
 
 SELECT nlevel('1.2.3.4');
@@ -291,3 +295,383 @@ 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" '::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 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 ~ '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 '"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/contrib/ltree_plpython/expected/ltree_plpython.out b/contrib/ltree_plpython/expected/ltree_plpython.out
index 4779755..1fb182e 100644
--- a/contrib/ltree_plpython/expected/ltree_plpython.out
+++ b/contrib/ltree_plpython/expected/ltree_plpython.out
@@ -28,16 +28,3 @@ INFO:  ['aa', 'bb', 'cc']
       3
 (1 row)
 
-CREATE FUNCTION test2() RETURNS ltree
-LANGUAGE plpythonu
-TRANSFORM FOR TYPE ltree
-AS $$
-return ['foo', 'bar', 'baz']
-$$;
--- plpython to ltree is not yet implemented, so this will fail,
--- because it will try to parse the Python list as an ltree input
--- string.
-SELECT test2();
-ERROR:  syntax error at position 0
-CONTEXT:  while creating return value
-PL/Python function "test2"
diff --git a/contrib/ltree_plpython/sql/ltree_plpython.sql b/contrib/ltree_plpython/sql/ltree_plpython.sql
index 210f542..e411867 100644
--- a/contrib/ltree_plpython/sql/ltree_plpython.sql
+++ b/contrib/ltree_plpython/sql/ltree_plpython.sql
@@ -21,16 +21,3 @@ return len(val)
 $$;
 
 SELECT test1n('aa.bb.cc'::ltree);
-
-
-CREATE FUNCTION test2() RETURNS ltree
-LANGUAGE plpythonu
-TRANSFORM FOR TYPE ltree
-AS $$
-return ['foo', 'bar', 'baz']
-$$;
-
--- plpython to ltree is not yet implemented, so this will fail,
--- because it will try to parse the Python list as an ltree input
--- string.
-SELECT test2();
diff --git a/doc/src/sgml/ltree.sgml b/doc/src/sgml/ltree.sgml
index 3ddd335..a115562 100644
--- a/doc/src/sgml/ltree.sgml
+++ b/doc/src/sgml/ltree.sgml
@@ -17,14 +17,38 @@
   <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 
+   fewer than 256 characters in length. Label may contain any character supported 
+   by <productname>PostgreSQL</productname> except <literal>\0</literal>. If label 
+   contains spaces, dots, or lquery modifiers, they may 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 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 backslashes. The list of special 
+    symbols for ltree includes space (<literal> </literal>), backslash and double quote,
+    lquery and ltxtquery also require escaping <literal>|</literal>, <literal>&amp;</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 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>
@@ -681,11 +705,13 @@ ltreetest=&gt; SELECT ins_label(path,2,'Space') FROM test WHERE path &lt;@ 'Top.
   <title>Authors</title>
 
   <para>
-   All work was done by Teodor Sigaev (<email>teodor@stack.net</email>) and
+   Initial version was done by Teodor Sigaev (<email>teodor@sigaev.ru</email>) and
    Oleg Bartunov (<email>oleg@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>beldmit@gmail.com</email>) directed by Teodor Sigaev. 
+   Comments and bug reports are welcome.
   </para>
  </sect2>
 
-- 
2.7.4

#20Thomas Munro
thomas.munro@gmail.com
In reply to: Nikita Glukhov (#19)
Re: Ltree syntax improvement

On Thu, Jul 18, 2019 at 1:28 AM Nikita Glukhov <n.gluhov@postgrespro.ru> wrote:

1. I fixed some bugs (fixed patch with additional test cases is attached):

Hi Nikita,

Thanks. I have set this to "Needs review", in the September 'fest.

--
Thomas Munro
https://enterprisedb.com

#21Tomas Vondra
tomas.vondra@2ndquadrant.com
In reply to: Thomas Munro (#20)
Re: Ltree syntax improvement

Hi,

This patch got mostly ignored since 2019-07 commitfest :-( The latest
patch (sent by Nikita) does not apply because of a minor conflict in
contrib/ltree/ltxtquery_io.c.

I see the patch removes a small bit of ltree_plpython tests which would
otherwise fail (with the "I don't know plpython" justification). Why not
to instead update the tests to accept the new output? Or is it really
the case that the case that we no longer need those tests?

The patch also reworks some parts from "if" to "switch" statements. I
agree switch statements are more readable, but maybe we should do this
in two steps - first adopting the "switch" without changing the logic,
and then making changes. But maybe that's an overkill.

regards

--
Tomas Vondra http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#22Dmitry Belyavsky
beldmit@gmail.com
In reply to: Tomas Vondra (#21)
Re: Ltree syntax improvement

Dear Tomas,

If the C part will be reviewed and considered mergeable, I'll update the
plpython tests.

On Mon, Jan 6, 2020 at 4:49 PM Tomas Vondra <tomas.vondra@2ndquadrant.com>
wrote:

Hi,

This patch got mostly ignored since 2019-07 commitfest :-( The latest
patch (sent by Nikita) does not apply because of a minor conflict in
contrib/ltree/ltxtquery_io.c.

I see the patch removes a small bit of ltree_plpython tests which would
otherwise fail (with the "I don't know plpython" justification). Why not
to instead update the tests to accept the new output? Or is it really
the case that the case that we no longer need those tests?

The patch also reworks some parts from "if" to "switch" statements. I
agree switch statements are more readable, but maybe we should do this
in two steps - first adopting the "switch" without changing the logic,
and then making changes. But maybe that's an overkill.

regards

--
Tomas Vondra http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

--
SY, Dmitry Belyavsky

#23Tom Lane
tgl@sss.pgh.pa.us
In reply to: Dmitry Belyavsky (#22)
Re: Ltree syntax improvement

Dmitry Belyavsky <beldmit@gmail.com> writes:

If the C part will be reviewed and considered mergeable, I'll update the
plpython tests.

I haven't looked at any of the code involved in this, but I did examine
the "failing" plpython test, and I'm quite distressed about that change
of behavior. Simply removing the test case is certainly not okay,
and I do not think that just changing it to accept this new behavior
is okay either. As Nikita said upthread:

7. ltree_plpython test does not fail now because Python list is converted to a
text and then to a ltree, and the textual representation of a Python list has
become a valid ltree text:

SELECT $$['foo', 'bar', 'baz']$$::ltree;
ltree
-------------------------
"['foo', 'bar', 'baz']"
(1 row)

So Python lists can be now successfully converted to ltrees without a transform,
but the result is not that is expected ('foo.bar.baz'::ltree).

If this case doesn't throw an error, then we're going to have a
compatibility problem whenever somebody finally gets around to
implementing the python-to-ltree transform properly, because it
would break any code that might be relying on this (wrong) behavior.

In general, I think it's a mistake to allow unquoted punctuation to be
taken as part of an ltree label, which is what this patch is evidently
doing. By doing that, you'll make it impossible for anyone to ever
again extend the ltree syntax, because if they want to assign special
meaning to braces or whatever, they can't do so without breaking
existing applications. For example, if the existing code allowed
double-quote or backslash as a label character, we'd already have
rejected this patch as being too big a compatibility break. So it's
not very forward-thinking to close off future improvements like this.

Thus, what I think you should do is require non-alphanumeric label
characters to be quoted, either via double-quotes or backslashes
(although it's questionable whether we really need two independent
quoting mechanisms here). That would preserve extensibility, and
it'd also preserve our freedom to fix ltree_plpython later, since
the case of interest would still be an error for now. And it would
mean that you don't have subtly different rules for what's data in
ltree versus what's data in lquery or ltxtquery.

BTW, the general rule in existing backend code that's doing string
parsing is to allow non-ASCII (high-bit-set) characters to be taken as
data without inquiring too closely as to what they are. This avoids a
bunch of locale and encoding issues without much loss of flexibility.
(If we do ever extend the ltree syntax again, we'd certainly choose
ASCII punctuation characters for whatever special symbols we need,
else the feature might not be available in all encodings.) So for
instance in your examples involving "Ñ", it's fine to take that as a
label character without concern for locale/encoding.

I'm not sure what I think about the whitespace business. It looks
like what you propose is to strip unquoted leading and trailing
whitespace but allow embedded whitespace. There's precedent for that,
certainly, but I wonder whether it isn't too confusing. In any case
you didn't document that.

regards, tom lane

#24Nikita Glukhov
n.gluhov@postgrespro.ru
In reply to: Tom Lane (#23)
2 attachment(s)
Re: Ltree syntax improvement

Attached new version of the patch.

I did major refactoring of ltree label parsing, extracting common parsing
code for ltree, lquery, and ltxtquery. This greatly simplified state
machines.

On the advice of Tomas Vondra, I also extracted a preliminary patch with
'if' to 'switch' conversion.

On 21.01.2020 22:13, Tom Lane wrote:

Dmitry Belyavsky <beldmit@gmail.com> writes:

If the C part will be reviewed and considered mergeable, I'll update the
plpython tests.

I haven't looked at any of the code involved in this, but I did examine
the "failing" plpython test, and I'm quite distressed about that change
of behavior. Simply removing the test case is certainly not okay,
and I do not think that just changing it to accept this new behavior
is okay either. As Nikita said upthread:

7. ltree_plpython test does not fail now because Python list is converted to a
text and then to a ltree, and the textual representation of a Python list has
become a valid ltree text:

SELECT $$['foo', 'bar', 'baz']$$::ltree;
ltree
-------------------------
"['foo', 'bar', 'baz']"
(1 row)

So Python lists can be now successfully converted to ltrees without a transform,
but the result is not that is expected ('foo.bar.baz'::ltree).

If this case doesn't throw an error, then we're going to have a
compatibility problem whenever somebody finally gets around to
implementing the python-to-ltree transform properly, because it
would break any code that might be relying on this (wrong) behavior.

In general, I think it's a mistake to allow unquoted punctuation to be
taken as part of an ltree label, which is what this patch is evidently
doing. By doing that, you'll make it impossible for anyone to ever
again extend the ltree syntax, because if they want to assign special
meaning to braces or whatever, they can't do so without breaking
existing applications. For example, if the existing code allowed
double-quote or backslash as a label character, we'd already have
rejected this patch as being too big a compatibility break. So it's
not very forward-thinking to close off future improvements like this.

Thus, what I think you should do is require non-alphanumeric label
characters to be quoted, either via double-quotes or backslashes
(although it's questionable whether we really need two independent
quoting mechanisms here). That would preserve extensibility, and
it'd also preserve our freedom to fix ltree_plpython later, since
the case of interest would still be an error for now. And it would
mean that you don't have subtly different rules for what's data in
ltree versus what's data in lquery or ltxtquery.

Now non-alphanumeric label characters should be escaped in ltree,
lquery and ltxtquery. Plpython tests does not require changes now.

BTW, the general rule in existing backend code that's doing string
parsing is to allow non-ASCII (high-bit-set) characters to be taken as
data without inquiring too closely as to what they are. This avoids a
bunch of locale and encoding issues without much loss of flexibility.
(If we do ever extend the ltree syntax again, we'd certainly choose
ASCII punctuation characters for whatever special symbols we need,
else the feature might not be available in all encodings.) So for
instance in your examples involving "Ñ", it's fine to take that as a
label character without concern for locale/encoding.
I'm not sure what I think about the whitespace business. It looks
like what you propose is to strip unquoted leading and trailing
whitespace but allow embedded whitespace. There's precedent for that,
certainly, but I wonder whether it isn't too confusing. In any case
you didn't document that.

Embedded whitespace should also be escaped now. I'm also not sure
about stripping unquoted leading and trailing whitespace.

--
Nikita Glukhov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

Attachments:

0001-Replace-if-with-switch-in-ltree-code-20200307.patchtext/x-patch; name=0001-Replace-if-with-switch-in-ltree-code-20200307.patchDownload
From c697c173e121c3996325f7f61536852f8b380d9e Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <n.gluhov@postgrespro.ru>
Date: Thu, 5 Mar 2020 17:34:59 +0300
Subject: [PATCH 1/2] Replace 'if' with 'switch' in ltree code

---
 contrib/ltree/ltree_io.c | 402 ++++++++++++++++++++++++-----------------------
 1 file changed, 204 insertions(+), 198 deletions(-)

diff --git a/contrib/ltree/ltree_io.c b/contrib/ltree/ltree_io.c
index 900a46a..e97f035 100644
--- a/contrib/ltree/ltree_io.c
+++ b/contrib/ltree/ltree_io.c
@@ -69,40 +69,43 @@ ltree_in(PG_FUNCTION_ARGS)
 	{
 		charlen = pg_mblen(ptr);
 
-		if (state == LTPRS_WAITNAME)
+		switch (state)
 		{
-			if (ISALNUM(ptr))
-			{
-				lptr->start = ptr;
-				lptr->wlen = 0;
-				state = LTPRS_WAITDELIM;
-			}
-			else
-				UNCHAR;
-		}
-		else if (state == LTPRS_WAITDELIM)
-		{
-			if (charlen == 1 && t_iseq(ptr, '.'))
-			{
-				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;
-			}
-			else if (!ISALNUM(ptr))
-				UNCHAR;
+			case LTPRS_WAITNAME:
+				if (ISALNUM(ptr))
+				{
+					lptr->start = ptr;
+					lptr->wlen = 0;
+					state = LTPRS_WAITDELIM;
+				}
+				else
+					UNCHAR;
+				break;
+
+			case LTPRS_WAITDELIM:
+				if (charlen == 1 && t_iseq(ptr, '.'))
+				{
+					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;
+				}
+				else if (!ISALNUM(ptr))
+					UNCHAR;
+				break;
+
+			default:
+				/* internal error */
+				elog(ERROR, "internal error in parser");
 		}
-		else
-			/* internal error */
-			elog(ERROR, "internal error in parser");
 
 		ptr += charlen;
 		lptr->wlen++;
@@ -238,178 +241,181 @@ lquery_in(PG_FUNCTION_ARGS)
 	{
 		charlen = pg_mblen(ptr);
 
-		if (state == LQPRS_WAITLEVEL)
-		{
-			if (ISALNUM(ptr))
-			{
-				GETVAR(curqlevel) = lptr = (nodeitem *) palloc0(sizeof(nodeitem) * (numOR + 1));
-				lptr->start = ptr;
-				state = LQPRS_WAITDELIM;
-				curqlevel->numvar = 1;
-			}
-			else if (charlen == 1 && t_iseq(ptr, '!'))
-			{
-				GETVAR(curqlevel) = lptr = (nodeitem *) palloc0(sizeof(nodeitem) * (numOR + 1));
-				lptr->start = ptr + 1;
-				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))
-			{
-				lptr++;
-				lptr->start = ptr;
-				state = LQPRS_WAITDELIM;
-				curqlevel->numvar++;
-			}
-			else
-				UNCHAR;
-		}
-		else if (state == LQPRS_WAITDELIM)
+		switch (state)
 		{
-			if (charlen == 1 && t_iseq(ptr, '@'))
-			{
-				if (lptr->start == ptr)
+			case LQPRS_WAITLEVEL:
+				if (ISALNUM(ptr))
+				{
+					GETVAR(curqlevel) = lptr = (nodeitem *) palloc0(sizeof(nodeitem) * (numOR + 1));
+					lptr->start = ptr;
+					state = LQPRS_WAITDELIM;
+					curqlevel->numvar = 1;
+				}
+				else if (charlen == 1 && t_iseq(ptr, '!'))
+				{
+					GETVAR(curqlevel) = lptr = (nodeitem *) palloc0(sizeof(nodeitem) * (numOR + 1));
+					lptr->start = ptr + 1;
+					state = LQPRS_WAITDELIM;
+					curqlevel->numvar = 1;
+					curqlevel->flag |= LQL_NOT;
+					hasnot = true;
+				}
+				else if (charlen == 1 && t_iseq(ptr, '*'))
+					state = LQPRS_WAITOPEN;
+				else
 					UNCHAR;
-				lptr->flag |= LVAR_INCASE;
-				curqlevel->flag |= LVAR_INCASE;
-			}
-			else if (charlen == 1 && t_iseq(ptr, '*'))
-			{
-				if (lptr->start == ptr)
+				break;
+
+			case LQPRS_WAITVAR:
+				if (ISALNUM(ptr))
+				{
+					lptr++;
+					lptr->start = ptr;
+					state = LQPRS_WAITDELIM;
+					curqlevel->numvar++;
+				}
+				else
 					UNCHAR;
-				lptr->flag |= LVAR_ANYEND;
-				curqlevel->flag |= LVAR_ANYEND;
-			}
-			else if (charlen == 1 && t_iseq(ptr, '%'))
-			{
-				if (lptr->start == ptr)
+				break;
+
+			case LQPRS_WAITDELIM:
+				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, '|'))
+				{
+					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;
+				}
+				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_WAITLEVEL;
+					curqlevel = NEXTLEV(curqlevel);
+				}
+				else if (ISALNUM(ptr))
+				{
+					if (lptr->flag)
+						UNCHAR;
+				}
+				else
 					UNCHAR;
-				lptr->flag |= LVAR_SUBLEXEME;
-				curqlevel->flag |= LVAR_SUBLEXEME;
-			}
-			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;
-			}
-			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_WAITLEVEL;
-				curqlevel = NEXTLEV(curqlevel);
-			}
-			else if (ISALNUM(ptr))
-			{
-				if (lptr->flag)
+				break;
+
+			case LQPRS_WAITOPEN:
+				if (charlen == 1 && t_iseq(ptr, '{'))
+					state = LQPRS_WAITFNUM;
+				else if (charlen == 1 && t_iseq(ptr, '.'))
+				{
+					curqlevel->low = 0;
+					curqlevel->high = 0xffff;
+					curqlevel = NEXTLEV(curqlevel);
+					state = LQPRS_WAITLEVEL;
+				}
+				else
 					UNCHAR;
-			}
-			else
-				UNCHAR;
-		}
-		else if (state == LQPRS_WAITOPEN)
-		{
-			if (charlen == 1 && t_iseq(ptr, '{'))
-				state = LQPRS_WAITFNUM;
-			else if (charlen == 1 && t_iseq(ptr, '.'))
-			{
-				curqlevel->low = 0;
-				curqlevel->high = 0xffff;
-				curqlevel = NEXTLEV(curqlevel);
-				state = LQPRS_WAITLEVEL;
-			}
-			else
-				UNCHAR;
-		}
-		else if (state == LQPRS_WAITFNUM)
-		{
-			if (charlen == 1 && t_iseq(ptr, ','))
-				state = LQPRS_WAITSNUM;
-			else if (t_isdigit(ptr))
-			{
-				curqlevel->low = atoi(ptr);
-				state = LQPRS_WAITND;
-			}
-			else
-				UNCHAR;
-		}
-		else if (state == LQPRS_WAITSNUM)
-		{
-			if (t_isdigit(ptr))
-			{
-				curqlevel->high = atoi(ptr);
-				state = LQPRS_WAITCLOSE;
-			}
-			else if (charlen == 1 && t_iseq(ptr, '}'))
-			{
-				curqlevel->high = 0xffff;
-				state = LQPRS_WAITEND;
-			}
-			else
-				UNCHAR;
-		}
-		else if (state == LQPRS_WAITCLOSE)
-		{
-			if (charlen == 1 && t_iseq(ptr, '}'))
-				state = LQPRS_WAITEND;
-			else if (!t_isdigit(ptr))
-				UNCHAR;
-		}
-		else if (state == LQPRS_WAITND)
-		{
-			if (charlen == 1 && t_iseq(ptr, '}'))
-			{
-				curqlevel->high = curqlevel->low;
-				state = LQPRS_WAITEND;
-			}
-			else if (charlen == 1 && t_iseq(ptr, ','))
-				state = LQPRS_WAITSNUM;
-			else if (!t_isdigit(ptr))
-				UNCHAR;
-		}
-		else if (state == LQPRS_WAITEND)
-		{
-			if (charlen == 1 && t_iseq(ptr, '.'))
-			{
-				state = LQPRS_WAITLEVEL;
-				curqlevel = NEXTLEV(curqlevel);
-			}
-			else
-				UNCHAR;
+				break;
+
+			case LQPRS_WAITFNUM:
+				if (charlen == 1 && t_iseq(ptr, ','))
+					state = LQPRS_WAITSNUM;
+				else if (t_isdigit(ptr))
+				{
+					curqlevel->low = atoi(ptr);
+					state = LQPRS_WAITND;
+				}
+				else
+					UNCHAR;
+				break;
+
+			case LQPRS_WAITSNUM:
+				if (t_isdigit(ptr))
+				{
+					curqlevel->high = atoi(ptr);
+					state = LQPRS_WAITCLOSE;
+				}
+				else if (charlen == 1 && t_iseq(ptr, '}'))
+				{
+					curqlevel->high = 0xffff;
+					state = LQPRS_WAITEND;
+				}
+				else
+					UNCHAR;
+				break;
+
+			case LQPRS_WAITCLOSE:
+				if (charlen == 1 && t_iseq(ptr, '}'))
+					state = LQPRS_WAITEND;
+				else if (!t_isdigit(ptr))
+					UNCHAR;
+				break;
+
+			case LQPRS_WAITND:
+				if (charlen == 1 && t_iseq(ptr, '}'))
+				{
+					curqlevel->high = curqlevel->low;
+					state = LQPRS_WAITEND;
+				}
+				else if (charlen == 1 && t_iseq(ptr, ','))
+					state = LQPRS_WAITSNUM;
+				else if (!t_isdigit(ptr))
+					UNCHAR;
+				break;
+
+			case LQPRS_WAITEND:
+				if (charlen == 1 && t_iseq(ptr, '.'))
+				{
+					state = LQPRS_WAITLEVEL;
+					curqlevel = NEXTLEV(curqlevel);
+				}
+				else
+					UNCHAR;
+				break;
+
+			default:
+				/* internal error */
+				elog(ERROR, "internal error in parser");
 		}
-		else
-			/* internal error */
-			elog(ERROR, "internal error in parser");
 
 		ptr += charlen;
 		if (state == LQPRS_WAITDELIM)
-- 
2.7.4

0002-Ltree-syntax-improvements-20200307.patchtext/x-patch; name=0002-Ltree-syntax-improvements-20200307.patchDownload
From c508b4bae6b0727193de8f8dd196664511dbe317 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <n.gluhov@postgrespro.ru>
Date: Sat, 7 Mar 2020 03:36:16 +0300
Subject: [PATCH 2/2] Ltree syntax improvements

---
 contrib/ltree/expected/ltree.out | 1620 ++++++++++++++++++++++++++++++++++++++
 contrib/ltree/ltree.h            |   23 +-
 contrib/ltree/ltree_io.c         |  687 +++++++++++-----
 contrib/ltree/ltxtquery_io.c     |  158 ++--
 contrib/ltree/sql/ltree.sql      |  386 +++++++++
 doc/src/sgml/ltree.sgml          |   38 +-
 6 files changed, 2653 insertions(+), 259 deletions(-)

diff --git a/contrib/ltree/expected/ltree.out b/contrib/ltree/expected/ltree.out
index 8226930..6215f0e 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
@@ -313,6 +314,11 @@ SELECT lca('1.2.2.3','1.2.3.4.5.6','1');
  
 (1 row)
 
+SELECT ''::lquery;
+ERROR:  syntax error
+LINE 1: SELECT ''::lquery;
+               ^
+DETAIL:  Unexpected end of line.
 SELECT '1'::lquery;
  lquery 
 --------
@@ -445,6 +451,16 @@ SELECT '1.*.4|3|2.*{1}'::lquery;
  1.*.4|3|2.*{1}
 (1 row)
 
+SELECT '*'::lquery;
+ lquery 
+--------
+ *
+(1 row)
+
+SELECT '*{1}|2'::lquery;
+ERROR:  syntax error at position 4
+LINE 1: SELECT '*{1}|2'::lquery;
+               ^
 SELECT 'qwerty%@*.tu'::lquery;
     lquery    
 --------------
@@ -7679,3 +7695,1607 @@ 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:  syntax error at position 2
+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 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 ~ '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 '"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:  unclosed escape sequence at position 0
+LINE 1: SELECT E'\\'::ltree;
+               ^
+SELECT E'n\\'::ltree;
+ERROR:  unclosed escape sequence at position 1
+LINE 1: SELECT E'n\\'::ltree;
+               ^
+SELECT '"'::ltree;
+ERROR:  unclosed quote at position 0
+LINE 1: SELECT '"'::ltree;
+               ^
+SELECT '"a'::ltree;
+ERROR:  unclosed quote at position 0
+LINE 1: SELECT '"a'::ltree;
+               ^
+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:  unclosed quote at position 1
+LINE 1: SELECT 'a"b'::ltree;
+               ^
+SELECT E'\\"ab"'::ltree;
+ERROR:  unclosed quote at position 4
+LINE 1: SELECT E'\\"ab"'::ltree;
+               ^
+SELECT '"a"."a'::ltree;
+ERROR:  unclosed quote at position 4
+LINE 1: SELECT '"a"."a'::ltree;
+               ^
+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:  unclosed quote at position 0
+LINE 1: SELECT '"'::lquery;
+               ^
+SELECT '"a'::lquery;
+ERROR:  unclosed quote at position 0
+LINE 1: SELECT '"a'::lquery;
+               ^
+SELECT '"a"."a'::lquery;
+ERROR:  unclosed quote at position 4
+LINE 1: SELECT '"a"."a'::lquery;
+               ^
+SELECT '"a."a"'::lquery;
+ERROR:  syntax error at position 4
+LINE 1: SELECT '"a."a"'::lquery;
+               ^
+SELECT E'\\"ab"'::lquery;
+ERROR:  unclosed quote at position 4
+LINE 1: SELECT E'\\"ab"'::lquery;
+               ^
+SELECT 'a"b'::lquery;
+ERROR:  unclosed quote 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:  unquoted special symbol at position 1
+LINE 1: SELECT 'a.""'::ltxtquery;
+               ^
+SELECT '"'::ltxtquery;
+ERROR:  unclosed quote at position 0
+LINE 1: SELECT '"'::ltxtquery;
+               ^
+SELECT '"""'::ltxtquery;
+ERROR:  unclosed quote at position 2
+LINE 1: SELECT '"""'::ltxtquery;
+               ^
+SELECT '"a'::ltxtquery;
+ERROR:  unclosed quote at position 0
+LINE 1: SELECT '"a'::ltxtquery;
+               ^
+SELECT '"a" & "a'::ltxtquery;
+ERROR:  unclosed quote at position 6
+LINE 1: SELECT '"a" & "a'::ltxtquery;
+               ^
+SELECT '"a | "a"'::ltxtquery;
+ERROR:  unquoted special symbol at position 6
+LINE 1: SELECT '"a | "a"'::ltxtquery;
+               ^
+SELECT '"!tree" & aWdf@*"'::ltxtquery;
+ERROR:  unclosed quote at position 16
+LINE 1: SELECT '"!tree" & aWdf@*"'::ltxtquery;
+               ^
+SELECT 'a"b'::ltxtquery;
+ERROR:  unclosed quote at position 1
+LINE 1: SELECT 'a"b'::ltxtquery;
+               ^
+SELECT 'a!b'::ltxtquery;
+ERROR:  unquoted special symbol at position 1
+LINE 1: SELECT 'a!b'::ltxtquery;
+               ^
+SELECT 'a%b'::ltxtquery;
+ERROR:  unquoted special symbol at position 2
+LINE 1: SELECT 'a%b'::ltxtquery;
+               ^
+SELECT 'a*b'::ltxtquery;
+ERROR:  unquoted special symbol at position 2
+LINE 1: SELECT 'a*b'::ltxtquery;
+               ^
+SELECT 'a@b'::ltxtquery;
+ERROR:  unquoted special symbol at position 2
+LINE 1: SELECT 'a@b'::ltxtquery;
+               ^
+SELECT 'a{b'::ltxtquery;
+ERROR:  unquoted special symbol at position 1
+LINE 1: SELECT 'a{b'::ltxtquery;
+               ^
+SELECT 'a}b'::ltxtquery;
+ERROR:  unquoted special symbol at position 1
+LINE 1: SELECT 'a}b'::ltxtquery;
+               ^
+SELECT 'a|b'::ltxtquery;
+ERROR:  unquoted special symbol at position 1
+LINE 1: SELECT 'a|b'::ltxtquery;
+               ^
+SELECT 'a&b'::ltxtquery;
+ERROR:  unquoted special symbol at position 1
+LINE 1: SELECT 'a&b'::ltxtquery;
+               ^
+SELECT 'a(b'::ltxtquery;
+ERROR:  unquoted special symbol at position 1
+LINE 1: SELECT 'a(b'::ltxtquery;
+               ^
+SELECT 'a)b'::ltxtquery;
+ERROR:  unquoted special symbol at position 1
+LINE 1: SELECT 'a)b'::ltxtquery;
+               ^
+SELECT '"b'::ltxtquery;
+ERROR:  unclosed quote at position 0
+LINE 1: SELECT '"b'::ltxtquery;
+               ^
+SELECT '%b'::ltxtquery;
+ERROR:  unquoted special symbol at position 0
+LINE 1: SELECT '%b'::ltxtquery;
+               ^
+SELECT '*b'::ltxtquery;
+ERROR:  unquoted special symbol at position 0
+LINE 1: SELECT '*b'::ltxtquery;
+               ^
+SELECT '@b'::ltxtquery;
+ERROR:  unquoted special symbol at position 0
+LINE 1: SELECT '@b'::ltxtquery;
+               ^
+SELECT '{b'::ltxtquery;
+ERROR:  unquoted special symbol at position 0
+LINE 1: SELECT '{b'::ltxtquery;
+               ^
+SELECT '}b'::ltxtquery;
+ERROR:  unquoted special symbol at position 0
+LINE 1: SELECT '}b'::ltxtquery;
+               ^
+SELECT '|b'::ltxtquery;
+ERROR:  unquoted special symbol at position 0
+LINE 1: SELECT '|b'::ltxtquery;
+               ^
+SELECT '&b'::ltxtquery;
+ERROR:  unquoted special symbol at position 0
+LINE 1: SELECT '&b'::ltxtquery;
+               ^
+SELECT '(b'::ltxtquery;
+ERROR:  syntax error
+LINE 1: SELECT '(b'::ltxtquery;
+               ^
+SELECT ')b'::ltxtquery;
+ERROR:  unquoted special symbol at position 0
+LINE 1: SELECT ')b'::ltxtquery;
+               ^
+SELECT 'a"'::ltxtquery;
+ERROR:  unclosed quote at position 1
+LINE 1: SELECT 'a"'::ltxtquery;
+               ^
+SELECT 'a!'::ltxtquery;
+ERROR:  unquoted special symbol at position 1
+LINE 1: SELECT 'a!'::ltxtquery;
+               ^
+SELECT 'a{'::ltxtquery;
+ERROR:  unquoted special symbol at position 1
+LINE 1: SELECT 'a{'::ltxtquery;
+               ^
+SELECT 'a}'::ltxtquery;
+ERROR:  unquoted special symbol at position 1
+LINE 1: SELECT 'a}'::ltxtquery;
+               ^
+SELECT 'a|'::ltxtquery;
+ERROR:  unquoted special symbol at position 1
+LINE 1: SELECT 'a|'::ltxtquery;
+               ^
+SELECT 'a&'::ltxtquery;
+ERROR:  unquoted special symbol at position 1
+LINE 1: SELECT 'a&'::ltxtquery;
+               ^
+SELECT 'a('::ltxtquery;
+ERROR:  unquoted special symbol at position 1
+LINE 1: SELECT 'a('::ltxtquery;
+               ^
+SELECT 'a)'::ltxtquery;
+ERROR:  unquoted special symbol at position 1
+LINE 1: SELECT 'a)'::ltxtquery;
+               ^
diff --git a/contrib/ltree/ltree.h b/contrib/ltree/ltree.h
index 366e580..9774175 100644
--- a/contrib/ltree/ltree.h
+++ b/contrib/ltree/ltree.h
@@ -80,8 +80,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 */
 
 /*
@@ -128,6 +126,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);
@@ -164,6 +180,9 @@ 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);
+void		copy_level(char *dst, const char *src, int len, int extra_bytes);
+ltree_token	ltree_get_token(char *ptr, 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 e97f035..479cb90 100644
--- a/contrib/ltree/ltree_io.c
+++ b/contrib/ltree/ltree_io.c
@@ -34,6 +34,335 @@ typedef struct
 #define LTPRS_WAITNAME	0
 #define LTPRS_WAITDELIM 1
 
+/*
+ * 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)
+{
+	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");
+}
+
+static bool
+is_unquoted_char(const char *ptr, int charlen)
+{
+	return t_isalpha(ptr) || t_isdigit(ptr) || (charlen == 1 && t_iseq(ptr, '_'));
+}
+
+/*
+ * Function calculating bytes to escape
+ * 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 or backslashes, we need to escape all of them and also
+ * initial and final quote, so return 2 + number of quotes/backslashes
+ */
+int
+bytes_to_escape(const char *start, const int len)
+{
+	uint16		copied = 0;
+	int			charlen;
+	int			escapes = 0;
+	bool		quotes = false;
+	const char *buf = start;
+
+	if (len == 0)
+		return 2;
+
+	while (*start && copied < len)
+	{
+		charlen = pg_mblen(buf);
+
+		if (charlen == 1 && (t_iseq(buf, '"') || t_iseq(buf, '\\')))
+			escapes++;
+		else if (!is_unquoted_char(buf, charlen))
+			quotes = true;
+
+		if (copied + charlen > len)
+			elog(ERROR, "internal error during merging levels");
+
+		buf += charlen;
+		copied += charlen;
+	}
+
+	return (escapes > 0) ? escapes + 2 : quotes ? 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, '"') || 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] = '"';
+	}
+}
+
+/*
+ * 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)
+{
+	if (*lptr->start == '"')
+	{
+		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)));
+}
+
+static void
+init_nodeitem(nodeitem *lptr, char *start, int len, int wlen, int escapes, int pos)
+{
+	lptr->start = start;
+	lptr->len = len - escapes;
+	lptr->wlen = wlen - escapes;
+
+	adjust_quoted_nodeitem(lptr);
+	check_level_length(lptr, pos + len);
+}
+
+ltree_token
+ltree_get_token(char *ptr, int pos, int *len, int *wlen, int *escaped_count)
+{
+	char	   *ptr_start = ptr;
+	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))
+		{
+			charlen = pg_mblen(ptr);
+			ptr += charlen;
+			++*wlen;
+		}
+
+		*len = ptr - ptr_start;
+		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 (charlen == 1 && t_iseq(ptr, '\\'))
+		escaped = true;
+	else if (charlen == 1 && t_iseq(ptr, '"'))
+		quoted = true;
+	else if (!is_unquoted_char(ptr, charlen))
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("syntax error at position %d", pos)
+				 /*errdetail("Unexpected character")*/));
+
+	for (ptr += charlen, ++*wlen; *ptr; ptr += charlen, ++*wlen)
+	{
+		charlen = pg_mblen(ptr);
+
+		if (escaped)
+		{
+			++*escaped_count;
+			escaped = false;
+		}
+		else if (charlen == 1 && t_iseq(ptr, '\\'))
+			escaped = true;
+		else if (quoted)
+		{
+			if (charlen == 1 && t_iseq(ptr, '"'))
+			{
+				quoted = false;
+				ptr += charlen;
+				++*wlen;
+				break;
+			}
+		}
+		else if (!is_unquoted_char(ptr, charlen))
+			break;
+	}
+
+	if (quoted)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("unclosed quote at position %d", pos)));
+
+	if (escaped)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("unclosed escape sequence at position %d", pos + *wlen - 1)));
+
+	*len = ptr - ptr_start;
+
+	return LTREE_TOK_LABEL;
+}
+
 Datum
 ltree_in(PG_FUNCTION_ARGS)
 {
@@ -41,92 +370,65 @@ 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			wlen;
+	int			len;
 
 	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));
-	ptr = buf;
-	while (*ptr)
+						levels, (int) (MaxAllocSize / sizeof(nodeitem)))));
+	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, 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;
+
+				init_nodeitem(lptr, ptr, len, wlen, escaped_count, pos);
+				totallen += MAXALIGN(lptr->len + LEVEL_HDRSIZE);
+				lptr++;
+
+				state = LTPRS_WAITDELIM;
 				break;
 
 			case LTPRS_WAITDELIM:
-				if (charlen == 1 && t_iseq(ptr, '.'))
-				{
-					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;
-				}
-				else if (!ISALNUM(ptr))
+				if (tok != LTREE_TOK_DOT)
 					UNCHAR;
+
+				state = LTPRS_WAITNAME;
 				break;
 
 			default:
 				/* internal error */
 				elog(ERROR, "internal error in parser");
 		}
-
-		ptr += charlen;
-		lptr->wlen++;
-		pos++;
 	}
 
-	if (state == LTPRS_WAITDELIM)
-	{
-		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++;
-	}
-	else if (!(state == LTPRS_WAITNAME && lptr == list))
+	if (state == LTPRS_WAITNAME && lptr != list)	/* Empty string */
 		ereport(ERROR,
 				(errcode(ERRCODE_SYNTAX_ERROR),
 				 errmsg("syntax error"),
@@ -140,7 +442,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++;
 	}
@@ -157,8 +462,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++)
 	{
@@ -166,9 +473,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);
 	}
 
@@ -188,7 +508,6 @@ ltree_out(PG_FUNCTION_ARGS)
 #define LQPRS_WAITEND	7
 #define LQPRS_WAITVAR	8
 
-
 #define GETVAR(x) ( *((nodeitem**)LQL_FIRST(x)) )
 #define ITEMSIZE	MAXALIGN(LQL_HDRSIZE+sizeof(nodeitem*))
 #define NEXTLEV(x) ( (lquery_level*)( ((char*)(x)) + ITEMSIZE) )
@@ -198,7 +517,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;
@@ -210,145 +529,121 @@ lquery_in(PG_FUNCTION_ARGS)
 	lquery_variant *lrptr = NULL;
 	bool		hasnot = false;
 	bool		wasbad = false;
-	int			charlen;
+	int			real_levels = 0;
 	int			pos = 0;
+	int			wlen;
+	int			len;
 
 	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);
-	ptr = buf;
-	while (*ptr)
+						levels, (int) (MaxAllocSize / ITEMSIZE))));
+	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, 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 (charlen == 1 && 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;
-					state = LQPRS_WAITDELIM;
 					curqlevel->numvar = 1;
-					curqlevel->flag |= LQL_NOT;
-					hasnot = true;
+					init_nodeitem(lptr, ptr, len, wlen, escaped_count, pos);
+
+					state = LQPRS_WAITDELIM;
 				}
-				else if (charlen == 1 && 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 = 0xffff;
+
 					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++;
+				init_nodeitem(lptr, ptr, len, wlen, escaped_count, pos);
+
+				state = LQPRS_WAITDELIM;
 				break;
 
 			case LQPRS_WAITDELIM:
-				if (charlen == 1 && t_iseq(ptr, '@'))
+				if (tok == LTREE_TOK_SPACE)
+					break;
+				else if (tok == LTREE_TOK_AT)
 				{
-					if (lptr->start == ptr)
-						UNCHAR;
 					lptr->flag |= LVAR_INCASE;
 					curqlevel->flag |= LVAR_INCASE;
 				}
-				else if (charlen == 1 && t_iseq(ptr, '*'))
+				else if (tok == LTREE_TOK_ASTERISK)
 				{
-					if (lptr->start == ptr)
-						UNCHAR;
 					lptr->flag |= LVAR_ANYEND;
 					curqlevel->flag |= LVAR_ANYEND;
 				}
-				else if (charlen == 1 && t_iseq(ptr, '%'))
+				else if (tok == LTREE_TOK_PERCENT)
 				{
-					if (lptr->start == ptr)
-						UNCHAR;
 					lptr->flag |= LVAR_SUBLEXEME;
 					curqlevel->flag |= LVAR_SUBLEXEME;
 				}
-				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)));
-
+				else if (tok == LTREE_TOK_OR)
 					state = LQPRS_WAITVAR;
-				}
-				else if (charlen == 1 && t_iseq(ptr, '.'))
+				else if (tok == LTREE_TOK_DOT)
 				{
-					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_WAITLEVEL;
 					curqlevel = NEXTLEV(curqlevel);
-				}
-				else if (ISALNUM(ptr))
-				{
-					if (lptr->flag)
-						UNCHAR;
+					state = LQPRS_WAITLEVEL;
 				}
 				else
 					UNCHAR;
 				break;
 
 			case LQPRS_WAITOPEN:
-				if (charlen == 1 && t_iseq(ptr, '{'))
+				if (tok == LTREE_TOK_SPACE)
+					break;
+				else if (tok == LTREE_TOK_LBRACE)
 					state = LQPRS_WAITFNUM;
-				else if (charlen == 1 && t_iseq(ptr, '.'))
+				else if (tok == LTREE_TOK_DOT)
 				{
-					curqlevel->low = 0;
-					curqlevel->high = 0xffff;
 					curqlevel = NEXTLEV(curqlevel);
 					state = LQPRS_WAITLEVEL;
 				}
@@ -357,11 +652,12 @@ lquery_in(PG_FUNCTION_ARGS)
 				break;
 
 			case LQPRS_WAITFNUM:
-				if (charlen == 1 && t_iseq(ptr, ','))
+				if (tok == LTREE_TOK_COMMA)
 					state = LQPRS_WAITSNUM;
 				else if (t_isdigit(ptr))
 				{
 					curqlevel->low = atoi(ptr);
+					len = wlen = 1;
 					state = LQPRS_WAITND;
 				}
 				else
@@ -372,38 +668,36 @@ lquery_in(PG_FUNCTION_ARGS)
 				if (t_isdigit(ptr))
 				{
 					curqlevel->high = atoi(ptr);
+					len = wlen = 1;
 					state = LQPRS_WAITCLOSE;
 				}
-				else if (charlen == 1 && t_iseq(ptr, '}'))
-				{
-					curqlevel->high = 0xffff;
+				else if (tok == LTREE_TOK_RBRACE)
 					state = LQPRS_WAITEND;
-				}
 				else
 					UNCHAR;
 				break;
 
 			case LQPRS_WAITCLOSE:
-				if (charlen == 1 && t_iseq(ptr, '}'))
+				if (tok == LTREE_TOK_RBRACE)
 					state = LQPRS_WAITEND;
 				else if (!t_isdigit(ptr))
 					UNCHAR;
 				break;
 
 			case LQPRS_WAITND:
-				if (charlen == 1 && t_iseq(ptr, '}'))
+				if (tok == LTREE_TOK_RBRACE)
 				{
 					curqlevel->high = curqlevel->low;
 					state = LQPRS_WAITEND;
 				}
-				else if (charlen == 1 && t_iseq(ptr, ','))
+				else if (tok == LTREE_TOK_COMMA)
 					state = LQPRS_WAITSNUM;
 				else if (!t_isdigit(ptr))
 					UNCHAR;
 				break;
 
 			case LQPRS_WAITEND:
-				if (charlen == 1 && t_iseq(ptr, '.'))
+				if (tok == LTREE_TOK_DOT)
 				{
 					state = LQPRS_WAITLEVEL;
 					curqlevel = NEXTLEV(curqlevel);
@@ -416,42 +710,11 @@ lquery_in(PG_FUNCTION_ARGS)
 				/* internal error */
 				elog(ERROR, "internal error in parser");
 		}
-
-		ptr += charlen;
-		if (state == LQPRS_WAITDELIM)
-			lptr->wlen++;
-		pos++;
 	}
 
-	if (state == LQPRS_WAITDELIM)
-	{
-		if (lptr->start == ptr)
-			ereport(ERROR,
-					(errcode(ERRCODE_SYNTAX_ERROR),
-					 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.")));
-
-		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)));
-	}
-	else if (state == LQPRS_WAITOPEN)
-		curqlevel->high = 0xffff;
-	else if (state != LQPRS_WAITEND)
+	if (state != LQPRS_WAITDELIM &&
+		state != LQPRS_WAITOPEN &&
+		state != LQPRS_WAITEND)
 		ereport(ERROR,
 				(errcode(ERRCODE_SYNTAX_ERROR),
 				 errmsg("syntax error"),
@@ -459,7 +722,7 @@ lquery_in(PG_FUNCTION_ARGS)
 
 	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)
@@ -483,14 +746,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;
@@ -503,8 +766,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);
 			}
@@ -532,7 +795,8 @@ lquery_out(PG_FUNCTION_ARGS)
 			   *ptr;
 	int			i,
 				j,
-				totallen = 1;
+				totallen = 1,
+				filled = 0;
 	lquery_level *curqlevel;
 	lquery_variant *curtlevel;
 
@@ -555,6 +819,7 @@ lquery_out(PG_FUNCTION_ARGS)
 		{
 			*ptr = '.';
 			ptr++;
+			filled++;
 		}
 		if (curqlevel->numvar)
 		{
@@ -562,31 +827,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);
 			}
@@ -614,6 +894,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 db347f7..4c237b1 100644
--- a/contrib/ltree/ltxtquery_io.c
+++ b/contrib/ltree/ltxtquery_io.c
@@ -37,6 +37,7 @@ typedef struct NODE
 typedef struct
 {
 	char	   *buf;
+	int			pos;
 	int32		state;
 	int32		count;
 	/* reverse polish notation in list (for temporary usage) */
@@ -57,86 +58,136 @@ typedef struct
 static int32
 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			escaped_count;
+		int			pos = state->pos;
+		char	   *buf = state->buf;
+		ltree_token tok = ltree_get_token(buf, pos, &len, &wlen, &escaped_count);
+
+		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 = state->buf - buf;
 					*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("unquoted special symbol at position %d", pos)));
 				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("unquoted special symbol at position %d", pos)));
 				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;
 		}
+	}
+}
+
+/*
+ * 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;
 
-		state->buf += charlen;
+	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;
 }
 
 /*
@@ -171,14 +222,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 +242,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;
 }
 
 #define STACKDEPTH		32
@@ -324,6 +387,7 @@ queryin(char *buf)
 
 	/* init state */
 	state.buf = buf;
+	state.pos = 0;
 	state.state = WAITOPERAND;
 	state.count = 0;
 	state.num = 0;
@@ -421,14 +485,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 846b04e..b1f14f5 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
@@ -65,6 +67,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;
@@ -87,6 +90,8 @@ SELECT '1.*.4|3|2.*{1,4}'::lquery;
 SELECT '1.*.4|3|2.*{,4}'::lquery;
 SELECT '1.*.4|3|2.*{1,}'::lquery;
 SELECT '1.*.4|3|2.*{1}'::lquery;
+SELECT '*'::lquery;
+SELECT '*{1}|2'::lquery;
 SELECT 'qwerty%@*.tu'::lquery;
 
 SELECT nlevel('1.2.3.4');
@@ -291,3 +296,384 @@ 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 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 ~ '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 '"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 b4e07f6..f6d8541 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 bytes
-   long.
+   A <firstterm>label</firstterm> is a sequence of characters. Labels must be 
+   fewer than 256 characters in length. 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>
@@ -687,11 +709,13 @@ ltreetest=&gt; SELECT ins_label(path,2,'Space') FROM test WHERE path &lt;@ 'Top.
   <title>Authors</title>
 
   <para>
-   All work was done by Teodor Sigaev (<email>teodor@stack.net</email>) and
+   Initial version was done by Teodor Sigaev (<email>teodor@sigaev.ru</email>) and
    Oleg Bartunov (<email>oleg@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>beldmit@gmail.com</email>) directed by Teodor Sigaev. 
+   Comments and bug reports are welcome.
   </para>
  </sect2>
 
-- 
2.7.4

#25Tom Lane
tgl@sss.pgh.pa.us
In reply to: Nikita Glukhov (#24)
Re: Ltree syntax improvement

Nikita Glukhov <n.gluhov@postgrespro.ru> writes:

Attached new version of the patch.

I spent a little bit of time looking through this, and have a few
comments:

* You have a lot of places where tests for particular ASCII characters
are done like this:

if ((charlen == 1) && t_iseq(src, '\\'))

This is a tedious and expensive way to spell

if (*src == '\\')

because charlen is necessarily 1 if you are looking at an ASCII character;
there is no allowed backend encoding in which an ASCII character can be
the first byte of a multibyte character. Aside from the direct
simplifications of the tests that this makes possible, I see some places
where you'd not have to pass charlen around, either.

* I spent a fair amount of time thinking that a lot of the added code
was wrong because it was only considering escaping and not
double-quoting. I eventually concluded that the idea is to convert
double-quoting to a pure escape-based representation during input
and store it that way. However, I don't really see why that is either
necessary or a good idea --- the internal storage already has a length
counter for each label. So I think what you really ought to be doing
here is simplifying out both quotes and escapes during ltree_in
and just storing the notionally-represented string internally.
(If I've misunderstood what the plan is, well the utter lack of
documentation in the patch isn't helping.)

* The added test cases seem a bit excessive and repetitive.

regards, tom lane

#26Nikita Glukhov
n.gluhov@postgrespro.ru
In reply to: Tom Lane (#25)
2 attachment(s)
Re: Ltree syntax improvement

On 25.03.2020 2:08, Tom Lane wrote:

Nikita Glukhov <n.gluhov@postgrespro.ru> writes:

Attached new version of the patch.

I spent a little bit of time looking through this, and have a few
comments:

* You have a lot of places where tests for particular ASCII characters
are done like this:

if ((charlen == 1) && t_iseq(src, '\\'))

This is a tedious and expensive way to spell

if (*src == '\\')

because charlen is necessarily 1 if you are looking at an ASCII character;
there is no allowed backend encoding in which an ASCII character can be
the first byte of a multibyte character. Aside from the direct
simplifications of the tests that this makes possible, I see some places
where you'd not have to pass charlen around, either.

All unnecessary checks of charlen were removed, but t_iseq() were left for
consistency.

* I spent a fair amount of time thinking that a lot of the added code
was wrong because it was only considering escaping and not
double-quoting. I eventually concluded that the idea is to convert
double-quoting to a pure escape-based representation during input
and store it that way. However, I don't really see why that is either
necessary or a good idea --- the internal storage already has a length
counter for each label. So I think what you really ought to be doing
here is simplifying out both quotes and escapes during ltree_in
and just storing the notionally-represented string internally.
(If I've misunderstood what the plan is, well the utter lack of
documentation in the patch isn't helping.)

ltree_in() removes quotes and escapes before storing strings (see
copy_unescaped()), just as you suggest.

ltree_out() adds escapes and quotes if necessary (see copy_escaped(),
extra_bytes_for_escaping()).

I have refactored code a bit, removed duplicated code, fixed several
bugs in reallocation of output strings, and added some comments.

* The added test cases seem a bit excessive and repetitive.

I have removed some tests that have become redundant after changes in
parsing.

--
Nikita Glukhov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

Attachments:

0001-Replace-if-with-switch-in-ltree-code-20200326.patchtext/x-patch; name=0001-Replace-if-with-switch-in-ltree-code-20200326.patchDownload
From 8ec6f93203684d63bf3c5d006c2499a71a1a9dad Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <n.gluhov@postgrespro.ru>
Date: Thu, 5 Mar 2020 17:34:59 +0300
Subject: [PATCH 1/2] Replace 'if' with 'switch' in ltree code

---
 contrib/ltree/ltree_io.c | 402 ++++++++++++++++++++++++-----------------------
 1 file changed, 204 insertions(+), 198 deletions(-)

diff --git a/contrib/ltree/ltree_io.c b/contrib/ltree/ltree_io.c
index 900a46a..e97f035 100644
--- a/contrib/ltree/ltree_io.c
+++ b/contrib/ltree/ltree_io.c
@@ -69,40 +69,43 @@ ltree_in(PG_FUNCTION_ARGS)
 	{
 		charlen = pg_mblen(ptr);
 
-		if (state == LTPRS_WAITNAME)
+		switch (state)
 		{
-			if (ISALNUM(ptr))
-			{
-				lptr->start = ptr;
-				lptr->wlen = 0;
-				state = LTPRS_WAITDELIM;
-			}
-			else
-				UNCHAR;
-		}
-		else if (state == LTPRS_WAITDELIM)
-		{
-			if (charlen == 1 && t_iseq(ptr, '.'))
-			{
-				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;
-			}
-			else if (!ISALNUM(ptr))
-				UNCHAR;
+			case LTPRS_WAITNAME:
+				if (ISALNUM(ptr))
+				{
+					lptr->start = ptr;
+					lptr->wlen = 0;
+					state = LTPRS_WAITDELIM;
+				}
+				else
+					UNCHAR;
+				break;
+
+			case LTPRS_WAITDELIM:
+				if (charlen == 1 && t_iseq(ptr, '.'))
+				{
+					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;
+				}
+				else if (!ISALNUM(ptr))
+					UNCHAR;
+				break;
+
+			default:
+				/* internal error */
+				elog(ERROR, "internal error in parser");
 		}
-		else
-			/* internal error */
-			elog(ERROR, "internal error in parser");
 
 		ptr += charlen;
 		lptr->wlen++;
@@ -238,178 +241,181 @@ lquery_in(PG_FUNCTION_ARGS)
 	{
 		charlen = pg_mblen(ptr);
 
-		if (state == LQPRS_WAITLEVEL)
-		{
-			if (ISALNUM(ptr))
-			{
-				GETVAR(curqlevel) = lptr = (nodeitem *) palloc0(sizeof(nodeitem) * (numOR + 1));
-				lptr->start = ptr;
-				state = LQPRS_WAITDELIM;
-				curqlevel->numvar = 1;
-			}
-			else if (charlen == 1 && t_iseq(ptr, '!'))
-			{
-				GETVAR(curqlevel) = lptr = (nodeitem *) palloc0(sizeof(nodeitem) * (numOR + 1));
-				lptr->start = ptr + 1;
-				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))
-			{
-				lptr++;
-				lptr->start = ptr;
-				state = LQPRS_WAITDELIM;
-				curqlevel->numvar++;
-			}
-			else
-				UNCHAR;
-		}
-		else if (state == LQPRS_WAITDELIM)
+		switch (state)
 		{
-			if (charlen == 1 && t_iseq(ptr, '@'))
-			{
-				if (lptr->start == ptr)
+			case LQPRS_WAITLEVEL:
+				if (ISALNUM(ptr))
+				{
+					GETVAR(curqlevel) = lptr = (nodeitem *) palloc0(sizeof(nodeitem) * (numOR + 1));
+					lptr->start = ptr;
+					state = LQPRS_WAITDELIM;
+					curqlevel->numvar = 1;
+				}
+				else if (charlen == 1 && t_iseq(ptr, '!'))
+				{
+					GETVAR(curqlevel) = lptr = (nodeitem *) palloc0(sizeof(nodeitem) * (numOR + 1));
+					lptr->start = ptr + 1;
+					state = LQPRS_WAITDELIM;
+					curqlevel->numvar = 1;
+					curqlevel->flag |= LQL_NOT;
+					hasnot = true;
+				}
+				else if (charlen == 1 && t_iseq(ptr, '*'))
+					state = LQPRS_WAITOPEN;
+				else
 					UNCHAR;
-				lptr->flag |= LVAR_INCASE;
-				curqlevel->flag |= LVAR_INCASE;
-			}
-			else if (charlen == 1 && t_iseq(ptr, '*'))
-			{
-				if (lptr->start == ptr)
+				break;
+
+			case LQPRS_WAITVAR:
+				if (ISALNUM(ptr))
+				{
+					lptr++;
+					lptr->start = ptr;
+					state = LQPRS_WAITDELIM;
+					curqlevel->numvar++;
+				}
+				else
 					UNCHAR;
-				lptr->flag |= LVAR_ANYEND;
-				curqlevel->flag |= LVAR_ANYEND;
-			}
-			else if (charlen == 1 && t_iseq(ptr, '%'))
-			{
-				if (lptr->start == ptr)
+				break;
+
+			case LQPRS_WAITDELIM:
+				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, '|'))
+				{
+					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;
+				}
+				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_WAITLEVEL;
+					curqlevel = NEXTLEV(curqlevel);
+				}
+				else if (ISALNUM(ptr))
+				{
+					if (lptr->flag)
+						UNCHAR;
+				}
+				else
 					UNCHAR;
-				lptr->flag |= LVAR_SUBLEXEME;
-				curqlevel->flag |= LVAR_SUBLEXEME;
-			}
-			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;
-			}
-			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_WAITLEVEL;
-				curqlevel = NEXTLEV(curqlevel);
-			}
-			else if (ISALNUM(ptr))
-			{
-				if (lptr->flag)
+				break;
+
+			case LQPRS_WAITOPEN:
+				if (charlen == 1 && t_iseq(ptr, '{'))
+					state = LQPRS_WAITFNUM;
+				else if (charlen == 1 && t_iseq(ptr, '.'))
+				{
+					curqlevel->low = 0;
+					curqlevel->high = 0xffff;
+					curqlevel = NEXTLEV(curqlevel);
+					state = LQPRS_WAITLEVEL;
+				}
+				else
 					UNCHAR;
-			}
-			else
-				UNCHAR;
-		}
-		else if (state == LQPRS_WAITOPEN)
-		{
-			if (charlen == 1 && t_iseq(ptr, '{'))
-				state = LQPRS_WAITFNUM;
-			else if (charlen == 1 && t_iseq(ptr, '.'))
-			{
-				curqlevel->low = 0;
-				curqlevel->high = 0xffff;
-				curqlevel = NEXTLEV(curqlevel);
-				state = LQPRS_WAITLEVEL;
-			}
-			else
-				UNCHAR;
-		}
-		else if (state == LQPRS_WAITFNUM)
-		{
-			if (charlen == 1 && t_iseq(ptr, ','))
-				state = LQPRS_WAITSNUM;
-			else if (t_isdigit(ptr))
-			{
-				curqlevel->low = atoi(ptr);
-				state = LQPRS_WAITND;
-			}
-			else
-				UNCHAR;
-		}
-		else if (state == LQPRS_WAITSNUM)
-		{
-			if (t_isdigit(ptr))
-			{
-				curqlevel->high = atoi(ptr);
-				state = LQPRS_WAITCLOSE;
-			}
-			else if (charlen == 1 && t_iseq(ptr, '}'))
-			{
-				curqlevel->high = 0xffff;
-				state = LQPRS_WAITEND;
-			}
-			else
-				UNCHAR;
-		}
-		else if (state == LQPRS_WAITCLOSE)
-		{
-			if (charlen == 1 && t_iseq(ptr, '}'))
-				state = LQPRS_WAITEND;
-			else if (!t_isdigit(ptr))
-				UNCHAR;
-		}
-		else if (state == LQPRS_WAITND)
-		{
-			if (charlen == 1 && t_iseq(ptr, '}'))
-			{
-				curqlevel->high = curqlevel->low;
-				state = LQPRS_WAITEND;
-			}
-			else if (charlen == 1 && t_iseq(ptr, ','))
-				state = LQPRS_WAITSNUM;
-			else if (!t_isdigit(ptr))
-				UNCHAR;
-		}
-		else if (state == LQPRS_WAITEND)
-		{
-			if (charlen == 1 && t_iseq(ptr, '.'))
-			{
-				state = LQPRS_WAITLEVEL;
-				curqlevel = NEXTLEV(curqlevel);
-			}
-			else
-				UNCHAR;
+				break;
+
+			case LQPRS_WAITFNUM:
+				if (charlen == 1 && t_iseq(ptr, ','))
+					state = LQPRS_WAITSNUM;
+				else if (t_isdigit(ptr))
+				{
+					curqlevel->low = atoi(ptr);
+					state = LQPRS_WAITND;
+				}
+				else
+					UNCHAR;
+				break;
+
+			case LQPRS_WAITSNUM:
+				if (t_isdigit(ptr))
+				{
+					curqlevel->high = atoi(ptr);
+					state = LQPRS_WAITCLOSE;
+				}
+				else if (charlen == 1 && t_iseq(ptr, '}'))
+				{
+					curqlevel->high = 0xffff;
+					state = LQPRS_WAITEND;
+				}
+				else
+					UNCHAR;
+				break;
+
+			case LQPRS_WAITCLOSE:
+				if (charlen == 1 && t_iseq(ptr, '}'))
+					state = LQPRS_WAITEND;
+				else if (!t_isdigit(ptr))
+					UNCHAR;
+				break;
+
+			case LQPRS_WAITND:
+				if (charlen == 1 && t_iseq(ptr, '}'))
+				{
+					curqlevel->high = curqlevel->low;
+					state = LQPRS_WAITEND;
+				}
+				else if (charlen == 1 && t_iseq(ptr, ','))
+					state = LQPRS_WAITSNUM;
+				else if (!t_isdigit(ptr))
+					UNCHAR;
+				break;
+
+			case LQPRS_WAITEND:
+				if (charlen == 1 && t_iseq(ptr, '.'))
+				{
+					state = LQPRS_WAITLEVEL;
+					curqlevel = NEXTLEV(curqlevel);
+				}
+				else
+					UNCHAR;
+				break;
+
+			default:
+				/* internal error */
+				elog(ERROR, "internal error in parser");
 		}
-		else
-			/* internal error */
-			elog(ERROR, "internal error in parser");
 
 		ptr += charlen;
 		if (state == LQPRS_WAITDELIM)
-- 
2.7.4

0002-Ltree-syntax-improvements-20200326.patchtext/x-patch; name=0002-Ltree-syntax-improvements-20200326.patchDownload
From 17c0141dcef1bf5bbbfbcfb9ae96b3a431648706 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <n.gluhov@postgrespro.ru>
Date: Tue, 16 Jul 2019 17:59:32 +0300
Subject: [PATCH 2/2] Ltree syntax improvements

---
 contrib/ltree/expected/ltree.out | 1042 ++++++++++++++++++++++++++++++++++++++
 contrib/ltree/ltree.h            |   25 +-
 contrib/ltree/ltree_io.c         |  736 +++++++++++++++++++--------
 contrib/ltree/ltxtquery_io.c     |  127 +++--
 contrib/ltree/sql/ltree.sql      |  267 ++++++++++
 doc/src/sgml/ltree.sgml          |   38 +-
 6 files changed, 1971 insertions(+), 264 deletions(-)

diff --git a/contrib/ltree/expected/ltree.out b/contrib/ltree/expected/ltree.out
index 8226930..c89e402 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
@@ -313,6 +314,11 @@ SELECT lca('1.2.2.3','1.2.3.4.5.6','1');
  
 (1 row)
 
+SELECT ''::lquery;
+ERROR:  syntax error
+LINE 1: SELECT ''::lquery;
+               ^
+DETAIL:  Unexpected end of line.
 SELECT '1'::lquery;
  lquery 
 --------
@@ -445,6 +451,16 @@ SELECT '1.*.4|3|2.*{1}'::lquery;
  1.*.4|3|2.*{1}
 (1 row)
 
+SELECT '*'::lquery;
+ lquery 
+--------
+ *
+(1 row)
+
+SELECT '*{1}|2'::lquery;
+ERROR:  syntax error at position 4
+LINE 1: SELECT '*{1}|2'::lquery;
+               ^
 SELECT 'qwerty%@*.tu'::lquery;
     lquery    
 --------------
@@ -7679,3 +7695,1029 @@ 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:  syntax error at position 2
+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:  unclosed escape sequence at position 0
+LINE 1: SELECT E'\\'::ltree;
+               ^
+SELECT E'n\\'::ltree;
+ERROR:  unclosed escape sequence at position 1
+LINE 1: SELECT E'n\\'::ltree;
+               ^
+SELECT '"'::ltree;
+ERROR:  unclosed quote at position 0
+LINE 1: SELECT '"'::ltree;
+               ^
+SELECT '"a'::ltree;
+ERROR:  unclosed quote at position 0
+LINE 1: SELECT '"a'::ltree;
+               ^
+SELECT '""'::ltree;
+ERROR:  name of level is empty
+LINE 1: SELECT '""'::ltree;
+               ^
+DETAIL:  Name length is 0 in position 0.
+SELECT 'a"b'::ltree;
+ERROR:  unclosed quote at position 1
+LINE 1: SELECT 'a"b'::ltree;
+               ^
+SELECT E'\\"ab"'::ltree;
+ERROR:  unclosed quote at position 4
+LINE 1: SELECT E'\\"ab"'::ltree;
+               ^
+SELECT '"a"."a'::ltree;
+ERROR:  unclosed quote at position 4
+LINE 1: SELECT '"a"."a'::ltree;
+               ^
+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 0.
+SELECT 'a.""'::ltree;
+ERROR:  name of level is empty
+LINE 1: SELECT 'a.""'::ltree;
+               ^
+DETAIL:  Name length is 0 in position 2.
+SELECT '"".""'::ltree;
+ERROR:  name of level is empty
+LINE 1: SELECT '"".""'::ltree;
+               ^
+DETAIL:  Name length is 0 in position 0.
+SELECT '""'::lquery;
+ERROR:  name of level is empty
+LINE 1: SELECT '""'::lquery;
+               ^
+DETAIL:  Name length is 0 in position 0.
+SELECT '"".""'::lquery;
+ERROR:  name of level is empty
+LINE 1: SELECT '"".""'::lquery;
+               ^
+DETAIL:  Name length is 0 in position 0.
+SELECT 'a.""'::lquery;
+ERROR:  name of level is empty
+LINE 1: SELECT 'a.""'::lquery;
+               ^
+DETAIL:  Name length is 0 in position 2.
+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 0.
+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 0.
+SELECT '"'::lquery;
+ERROR:  unclosed quote at position 0
+LINE 1: SELECT '"'::lquery;
+               ^
+SELECT '"a'::lquery;
+ERROR:  unclosed quote at position 0
+LINE 1: SELECT '"a'::lquery;
+               ^
+SELECT '"a"."a'::lquery;
+ERROR:  unclosed quote at position 4
+LINE 1: SELECT '"a"."a'::lquery;
+               ^
+SELECT '"a."a"'::lquery;
+ERROR:  syntax error at position 4
+LINE 1: SELECT '"a."a"'::lquery;
+               ^
+SELECT E'\\"ab"'::lquery;
+ERROR:  unclosed quote at position 4
+LINE 1: SELECT E'\\"ab"'::lquery;
+               ^
+SELECT 'a"b'::lquery;
+ERROR:  unclosed quote 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 '%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 '"foo"bar.baz'::lquery;
+ERROR:  syntax error at position 5
+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:  name of level is too long
+DETAIL:  Name length is 256, must be < 256, in position 0.
+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:  unquoted special symbol at position 1
+LINE 1: SELECT 'a.""'::ltxtquery;
+               ^
+SELECT '"'::ltxtquery;
+ERROR:  unclosed quote at position 0
+LINE 1: SELECT '"'::ltxtquery;
+               ^
+SELECT '"""'::ltxtquery;
+ERROR:  unclosed quote at position 2
+LINE 1: SELECT '"""'::ltxtquery;
+               ^
+SELECT '"a'::ltxtquery;
+ERROR:  unclosed quote at position 0
+LINE 1: SELECT '"a'::ltxtquery;
+               ^
+SELECT '"a" & "a'::ltxtquery;
+ERROR:  unclosed quote at position 6
+LINE 1: SELECT '"a" & "a'::ltxtquery;
+               ^
+SELECT '"a | "a"'::ltxtquery;
+ERROR:  unquoted special symbol at position 6
+LINE 1: SELECT '"a | "a"'::ltxtquery;
+               ^
+SELECT '"!tree" & aWdf@*"'::ltxtquery;
+ERROR:  unclosed quote at position 16
+LINE 1: SELECT '"!tree" & aWdf@*"'::ltxtquery;
+               ^
+SELECT 'a"b'::ltxtquery;
+ERROR:  unclosed quote at position 1
+LINE 1: SELECT 'a"b'::ltxtquery;
+               ^
+SELECT 'a!b'::ltxtquery;
+ERROR:  unquoted special symbol at position 1
+LINE 1: SELECT 'a!b'::ltxtquery;
+               ^
+SELECT 'a%b'::ltxtquery;
+ERROR:  unquoted special symbol at position 2
+LINE 1: SELECT 'a%b'::ltxtquery;
+               ^
+SELECT '"b'::ltxtquery;
+ERROR:  unclosed quote at position 0
+LINE 1: SELECT '"b'::ltxtquery;
+               ^
+SELECT '%b'::ltxtquery;
+ERROR:  unquoted special symbol at position 0
+LINE 1: SELECT '%b'::ltxtquery;
+               ^
+SELECT 'a"'::ltxtquery;
+ERROR:  unclosed quote at position 1
+LINE 1: SELECT 'a"'::ltxtquery;
+               ^
+SELECT 'a!'::ltxtquery;
+ERROR:  unquoted special symbol at position 1
+LINE 1: SELECT 'a!'::ltxtquery;
+               ^
diff --git a/contrib/ltree/ltree.h b/contrib/ltree/ltree.h
index 366e580..03f5938 100644
--- a/contrib/ltree/ltree.h
+++ b/contrib/ltree/ltree.h
@@ -80,8 +80,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 */
 
 /*
@@ -128,6 +126,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);
@@ -164,6 +180,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, 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 e97f035..79572bb 100644
--- a/contrib/ltree/ltree_io.c
+++ b/contrib/ltree/ltree_io.c
@@ -34,6 +34,357 @@ typedef struct
 #define LTPRS_WAITNAME	0
 #define LTPRS_WAITDELIM 1
 
+/*
+ * 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] = '"';
+	}
+}
+
+/*
+ * Initialize nodeitem "lptr" with label starting at "start".
+ *
+ * "len" is a length of label in bytes.
+ * "wlen" is a length of label in wide characters.
+ * "escapes" is a count of escaped characters.
+ * "pos" is a position of label in the input string in wide characters.
+ */
+static void
+init_nodeitem(nodeitem *lptr, char *start, int len, int wlen, int escapes,
+			  int pos)
+{
+	lptr->start = start;
+	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, '"'))
+	{
+		lptr->start++;
+		lptr->len -= 2;
+		lptr->wlen -= 2;
+	}
+
+	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)));
+}
+
+/*
+ * 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, 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("syntax error at position %d", 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("unclosed quote at position %d", pos)));
+
+	if (escaped)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("unclosed escape sequence at position %d",
+						pos + *wlen - 1)));
+
+	*len = ptr - str;
+
+	return LTREE_TOK_LABEL;
+}
+
 Datum
 ltree_in(PG_FUNCTION_ARGS)
 {
@@ -41,92 +392,63 @@ 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;
-	int			pos = 0;
+	int			len;
+	int			wlen;
+	int			pos = 0;	/* Position in strings, in symbols */
 
 	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));
-	ptr = buf;
-	while (*ptr)
+						levels, (int) (MaxAllocSize / sizeof(nodeitem)))));
+	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, 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;
+
+				init_nodeitem(lptr, ptr, len, wlen, escaped_count, pos);
+				totallen += MAXALIGN(lptr->len + LEVEL_HDRSIZE);
+				lptr++;
+
+				state = LTPRS_WAITDELIM;
 				break;
 
 			case LTPRS_WAITDELIM:
-				if (charlen == 1 && t_iseq(ptr, '.'))
-				{
-					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;
-				}
-				else if (!ISALNUM(ptr))
+				if (tok != LTREE_TOK_DOT)
 					UNCHAR;
+
+				state = LTPRS_WAITNAME;
 				break;
 
 			default:
 				/* internal error */
 				elog(ERROR, "internal error in parser");
 		}
-
-		ptr += charlen;
-		lptr->wlen++;
-		pos++;
 	}
 
-	if (state == LTPRS_WAITDELIM)
-	{
-		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++;
-	}
-	else if (!(state == LTPRS_WAITNAME && lptr == list))
+	if (state == LTPRS_WAITNAME && lptr != list)	/* Empty string */
 		ereport(ERROR,
 				(errcode(ERRCODE_SYNTAX_ERROR),
 				 errmsg("syntax error"),
@@ -140,7 +462,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,21 +479,40 @@ ltree_out(PG_FUNCTION_ARGS)
 {
 	ltree	   *in = PG_GETARG_LTREE_P(0);
 	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);
 	}
 
@@ -198,7 +542,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;
@@ -210,145 +554,121 @@ lquery_in(PG_FUNCTION_ARGS)
 	lquery_variant *lrptr = NULL;
 	bool		hasnot = false;
 	bool		wasbad = false;
-	int			charlen;
+	int			real_levels = 0;
 	int			pos = 0;
+	int			wlen;
+	int			len;
 
 	ptr = buf;
-	while (*ptr)
-	{
-		charlen = pg_mblen(ptr);
+	count_parts_ors(ptr, &levels, &numOR);
 
-		if (charlen == 1)
-		{
-			if (t_iseq(ptr, '.'))
-				num++;
-			else if (t_iseq(ptr, '|'))
-				numOR++;
-		}
-
-		ptr += charlen;
-	}
-
-	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);
-	ptr = buf;
-	while (*ptr)
+						levels, (int) (MaxAllocSize / ITEMSIZE))));
+	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, 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 (charlen == 1 && 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;
-					state = LQPRS_WAITDELIM;
 					curqlevel->numvar = 1;
-					curqlevel->flag |= LQL_NOT;
-					hasnot = true;
+					init_nodeitem(lptr, ptr, len, wlen, escaped_count, pos);
+
+					state = LQPRS_WAITDELIM;
 				}
-				else if (charlen == 1 && 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 = 0xffff;
+
 					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++;
+				init_nodeitem(lptr, ptr, len, wlen, escaped_count, pos);
+
+				state = LQPRS_WAITDELIM;
 				break;
 
 			case LQPRS_WAITDELIM:
-				if (charlen == 1 && t_iseq(ptr, '@'))
+				if (tok == LTREE_TOK_SPACE)
+					break;
+				else if (tok == LTREE_TOK_AT)
 				{
-					if (lptr->start == ptr)
-						UNCHAR;
 					lptr->flag |= LVAR_INCASE;
 					curqlevel->flag |= LVAR_INCASE;
 				}
-				else if (charlen == 1 && t_iseq(ptr, '*'))
+				else if (tok == LTREE_TOK_ASTERISK)
 				{
-					if (lptr->start == ptr)
-						UNCHAR;
 					lptr->flag |= LVAR_ANYEND;
 					curqlevel->flag |= LVAR_ANYEND;
 				}
-				else if (charlen == 1 && t_iseq(ptr, '%'))
+				else if (tok == LTREE_TOK_PERCENT)
 				{
-					if (lptr->start == ptr)
-						UNCHAR;
 					lptr->flag |= LVAR_SUBLEXEME;
 					curqlevel->flag |= LVAR_SUBLEXEME;
 				}
-				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)));
-
+				else if (tok == LTREE_TOK_OR)
 					state = LQPRS_WAITVAR;
-				}
-				else if (charlen == 1 && t_iseq(ptr, '.'))
+				else if (tok == LTREE_TOK_DOT)
 				{
-					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_WAITLEVEL;
 					curqlevel = NEXTLEV(curqlevel);
-				}
-				else if (ISALNUM(ptr))
-				{
-					if (lptr->flag)
-						UNCHAR;
+					state = LQPRS_WAITLEVEL;
 				}
 				else
 					UNCHAR;
 				break;
 
 			case LQPRS_WAITOPEN:
-				if (charlen == 1 && t_iseq(ptr, '{'))
+				if (tok == LTREE_TOK_SPACE)
+					break;
+				else if (tok == LTREE_TOK_LBRACE)
 					state = LQPRS_WAITFNUM;
-				else if (charlen == 1 && t_iseq(ptr, '.'))
+				else if (tok == LTREE_TOK_DOT)
 				{
-					curqlevel->low = 0;
-					curqlevel->high = 0xffff;
 					curqlevel = NEXTLEV(curqlevel);
 					state = LQPRS_WAITLEVEL;
 				}
@@ -357,11 +677,12 @@ lquery_in(PG_FUNCTION_ARGS)
 				break;
 
 			case LQPRS_WAITFNUM:
-				if (charlen == 1 && t_iseq(ptr, ','))
+				if (tok == LTREE_TOK_COMMA)
 					state = LQPRS_WAITSNUM;
 				else if (t_isdigit(ptr))
 				{
 					curqlevel->low = atoi(ptr);
+					len = wlen = 1;
 					state = LQPRS_WAITND;
 				}
 				else
@@ -372,38 +693,36 @@ lquery_in(PG_FUNCTION_ARGS)
 				if (t_isdigit(ptr))
 				{
 					curqlevel->high = atoi(ptr);
+					len = wlen = 1;
 					state = LQPRS_WAITCLOSE;
 				}
-				else if (charlen == 1 && t_iseq(ptr, '}'))
-				{
-					curqlevel->high = 0xffff;
+				else if (tok == LTREE_TOK_RBRACE)
 					state = LQPRS_WAITEND;
-				}
 				else
 					UNCHAR;
 				break;
 
 			case LQPRS_WAITCLOSE:
-				if (charlen == 1 && t_iseq(ptr, '}'))
+				if (tok == LTREE_TOK_RBRACE)
 					state = LQPRS_WAITEND;
 				else if (!t_isdigit(ptr))
 					UNCHAR;
 				break;
 
 			case LQPRS_WAITND:
-				if (charlen == 1 && t_iseq(ptr, '}'))
+				if (tok == LTREE_TOK_RBRACE)
 				{
 					curqlevel->high = curqlevel->low;
 					state = LQPRS_WAITEND;
 				}
-				else if (charlen == 1 && t_iseq(ptr, ','))
+				else if (tok == LTREE_TOK_COMMA)
 					state = LQPRS_WAITSNUM;
 				else if (!t_isdigit(ptr))
 					UNCHAR;
 				break;
 
 			case LQPRS_WAITEND:
-				if (charlen == 1 && t_iseq(ptr, '.'))
+				if (tok == LTREE_TOK_DOT)
 				{
 					state = LQPRS_WAITLEVEL;
 					curqlevel = NEXTLEV(curqlevel);
@@ -416,42 +735,11 @@ lquery_in(PG_FUNCTION_ARGS)
 				/* internal error */
 				elog(ERROR, "internal error in parser");
 		}
-
-		ptr += charlen;
-		if (state == LQPRS_WAITDELIM)
-			lptr->wlen++;
-		pos++;
 	}
 
-	if (state == LQPRS_WAITDELIM)
-	{
-		if (lptr->start == ptr)
-			ereport(ERROR,
-					(errcode(ERRCODE_SYNTAX_ERROR),
-					 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.")));
-
-		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)));
-	}
-	else if (state == LQPRS_WAITOPEN)
-		curqlevel->high = 0xffff;
-	else if (state != LQPRS_WAITEND)
+	if (state != LQPRS_WAITDELIM &&
+		state != LQPRS_WAITOPEN &&
+		state != LQPRS_WAITEND)
 		ereport(ERROR,
 				(errcode(ERRCODE_SYNTAX_ERROR),
 				 errmsg("syntax error"),
@@ -459,7 +747,7 @@ lquery_in(PG_FUNCTION_ARGS)
 
 	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)
@@ -483,14 +771,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;
@@ -503,8 +791,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);
 			}
@@ -532,7 +820,9 @@ lquery_out(PG_FUNCTION_ARGS)
 			   *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;
 
@@ -540,14 +830,45 @@ lquery_out(PG_FUNCTION_ARGS)
 	for (i = 0; i < in->numlevel; i++)
 	{
 		totallen++;
+		var_num += curqlevel->numvar;
+
 		if (curqlevel->numvar)
 			totallen += 1 + (curqlevel->numvar * 4) + curqlevel->totallen;
 		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++)
 	{
@@ -564,15 +885,20 @@ lquery_out(PG_FUNCTION_ARGS)
 				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 = '%';
@@ -619,6 +945,8 @@ lquery_out(PG_FUNCTION_ARGS)
 		curqlevel = LQL_NEXT(curqlevel);
 	}
 
+	pfree(var_extra_bytes);
+
 	*ptr = '\0';
 	PG_FREE_IF_COPY(in, 0);
 
diff --git a/contrib/ltree/ltxtquery_io.c b/contrib/ltree/ltxtquery_io.c
index db347f7..d93e878 100644
--- a/contrib/ltree/ltxtquery_io.c
+++ b/contrib/ltree/ltxtquery_io.c
@@ -37,6 +37,7 @@ typedef struct NODE
 typedef struct
 {
 	char	   *buf;
+	int			pos;
 	int32		state;
 	int32		count;
 	/* reverse polish notation in list (for temporary usage) */
@@ -55,87 +56,98 @@ 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, int *escaped,
+			   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, 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 = state->buf - buf;
+					*escaped = 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("unquoted special symbol at position %d", pos)));
 				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("unquoted special symbol at position %d", pos)));
 				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,25 +181,35 @@ pushquery(QPRS_STATE *state, int32 type, int32 val, int32 distance, int32 lenval
  * This function is used for query text parsing
  */
 static void
-pushval_asis(QPRS_STATE *state, int type, char *strval, int lenval, uint16 flag)
+pushval_asis(QPRS_STATE *state, int type, char *strval, int lenval, int escaped,
+			 uint16 flag)
 {
+	if (lenval - escaped <= 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);
+
+	lenval -= escaped;
+
+	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++;
@@ -204,6 +226,7 @@ makepol(QPRS_STATE *state)
 	int32		val = 0,
 				type;
 	int32		lenval = 0;
+	int			escaped;
 	char	   *strval = NULL;
 	int32		stack[STACKDEPTH];
 	int32		lenstack = 0;
@@ -212,12 +235,13 @@ makepol(QPRS_STATE *state)
 	/* since this function recurses, it could be driven to stack overflow */
 	check_stack_depth();
 
-	while ((type = gettoken_query(state, &val, &lenval, &strval, &flag)) != END)
+	while ((type = gettoken_query(state, &val, &lenval, &escaped, &strval,
+								  &flag)) != END)
 	{
 		switch (type)
 		{
 			case VAL:
-				pushval_asis(state, VAL, strval, lenval, flag);
+				pushval_asis(state, VAL, strval, lenval, escaped, flag);
 				while (lenstack && (stack[lenstack - 1] == (int32) '&' ||
 									stack[lenstack - 1] == (int32) '!'))
 				{
@@ -324,6 +348,7 @@ queryin(char *buf)
 
 	/* init state */
 	state.buf = buf;
+	state.pos = 0;
 	state.state = WAITOPERAND;
 	state.count = 0;
 	state.num = 0;
@@ -421,14 +446,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 846b04e..b0cf5b6 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
@@ -65,6 +67,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;
@@ -87,6 +90,8 @@ SELECT '1.*.4|3|2.*{1,4}'::lquery;
 SELECT '1.*.4|3|2.*{,4}'::lquery;
 SELECT '1.*.4|3|2.*{1,}'::lquery;
 SELECT '1.*.4|3|2.*{1}'::lquery;
+SELECT '*'::lquery;
+SELECT '*{1}|2'::lquery;
 SELECT 'qwerty%@*.tu'::lquery;
 
 SELECT nlevel('1.2.3.4');
@@ -291,3 +296,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 b4e07f6..f6d8541 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 bytes
-   long.
+   A <firstterm>label</firstterm> is a sequence of characters. Labels must be 
+   fewer than 256 characters in length. 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>
@@ -687,11 +709,13 @@ ltreetest=&gt; SELECT ins_label(path,2,'Space') FROM test WHERE path &lt;@ 'Top.
   <title>Authors</title>
 
   <para>
-   All work was done by Teodor Sigaev (<email>teodor@stack.net</email>) and
+   Initial version was done by Teodor Sigaev (<email>teodor@sigaev.ru</email>) and
    Oleg Bartunov (<email>oleg@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>beldmit@gmail.com</email>) directed by Teodor Sigaev. 
+   Comments and bug reports are welcome.
   </para>
  </sect2>
 
-- 
2.7.4

#27Tom Lane
tgl@sss.pgh.pa.us
In reply to: Nikita Glukhov (#26)
Re: Ltree syntax improvement

Nikita Glukhov <n.gluhov@postgrespro.ru> writes:

[ latest version of ltree syntax extension ]

This is going to need another rebase after all the other ltree hacking
that just got done. However, I did include 0001 (use a switch) in
the commit I just pushed, so you don't need to worry about that.

regards, tom lane

#28Nikita Glukhov
n.gluhov@postgrespro.ru
In reply to: Tom Lane (#27)
1 attachment(s)
Re: Ltree syntax improvement

On 02.04.2020 2:46, Tom Lane wrote:

Nikita Glukhov <n.gluhov@postgrespro.ru> writes:

[ latest version of ltree syntax extension ]

This is going to need another rebase after all the other ltree hacking
that just got done. However, I did include 0001 (use a switch) in
the commit I just pushed, so you don't need to worry about that.

regards, tom lane

Rebased patch attached.

I’m not sure whether it's worth to introduce one LTREE_TOK_SPECIAL for
the whole set of special characters, and still check them with t_iseq().

--
Nikita Glukhov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

Attachments:

0001-Ltree-syntax-improvements-20200402.patchtext/x-patch; name=0001-Ltree-syntax-improvements-20200402.patchDownload
From 7db20b22e8bb79351e7d7b52761f7d63cd880973 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <n.gluhov@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     |  130 +++--
 contrib/ltree/sql/ltree.sql      |  267 ++++++++++
 doc/src/sgml/ltree.sgml          |   38 +-
 6 files changed, 1993 insertions(+), 215 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..0a4eac8 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,101 @@ 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, int *escaped,
+			   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 = state->buf - buf;
+					*escaped = 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;
 	}
 }
 
@@ -167,25 +182,35 @@ pushquery(QPRS_STATE *state, int32 type, int32 val, int32 distance, int32 lenval
  * This function is used for query text parsing
  */
 static void
-pushval_asis(QPRS_STATE *state, int type, char *strval, int lenval, uint16 flag)
+pushval_asis(QPRS_STATE *state, int type, char *strval, int lenval, int escaped,
+			 uint16 flag)
 {
+	if (lenval - escaped <= 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);
+
+	lenval -= escaped;
+
+	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++;
@@ -202,6 +227,7 @@ makepol(QPRS_STATE *state)
 	int32		val = 0,
 				type;
 	int32		lenval = 0;
+	int			escaped;
 	char	   *strval = NULL;
 	int32		stack[STACKDEPTH];
 	int32		lenstack = 0;
@@ -210,12 +236,13 @@ makepol(QPRS_STATE *state)
 	/* since this function recurses, it could be driven to stack overflow */
 	check_stack_depth();
 
-	while ((type = gettoken_query(state, &val, &lenval, &strval, &flag)) != END)
+	while ((type = gettoken_query(state, &val, &lenval, &escaped, &strval,
+								  &flag)) != END)
 	{
 		switch (type)
 		{
 			case VAL:
-				pushval_asis(state, VAL, strval, lenval, flag);
+				pushval_asis(state, VAL, strval, lenval, escaped, flag);
 				while (lenstack && (stack[lenstack - 1] == (int32) '&' ||
 									stack[lenstack - 1] == (int32) '!'))
 				{
@@ -322,6 +349,7 @@ queryin(char *buf)
 
 	/* init state */
 	state.buf = buf;
+	state.pos = 1;
 	state.state = WAITOPERAND;
 	state.count = 0;
 	state.num = 0;
@@ -448,14 +476,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=&gt; SELECT ins_label(path,2,'Space') FROM test WHERE path &lt;@ 'Top.
   <title>Authors</title>
 
   <para>
-   All work was done by Teodor Sigaev (<email>teodor@stack.net</email>) and
+   Initial version was done by Teodor Sigaev (<email>teodor@sigaev.ru</email>) and
    Oleg Bartunov (<email>oleg@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>beldmit@gmail.com</email>) directed by Teodor Sigaev. 
+   Comments and bug reports are welcome.
   </para>
  </sect2>
 
-- 
2.7.4

#29Tom Lane
tgl@sss.pgh.pa.us
In reply to: Nikita Glukhov (#28)
Re: Ltree syntax improvement

Nikita Glukhov <n.gluhov@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

#30Nikita Glukhov
n.gluhov@postgrespro.ru
In reply to: Tom Lane (#29)
1 attachment(s)
Re: Ltree syntax improvement

On 02.04.2020 19:16, Tom Lane wrote:

Nikita Glukhov <n.gluhov@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

Attachments:

0001-Ltree-syntax-improvements-20200403.patchtext/x-patch; name=0001-Ltree-syntax-improvements-20200403.patchDownload
From c4929c05423f5b29063c739f25dfebfe99681d01 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <n.gluhov@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=&gt; SELECT ins_label(path,2,'Space') FROM test WHERE path &lt;@ 'Top.
   <title>Authors</title>
 
   <para>
-   All work was done by Teodor Sigaev (<email>teodor@stack.net</email>) and
+   Initial version was done by Teodor Sigaev (<email>teodor@sigaev.ru</email>) and
    Oleg Bartunov (<email>oleg@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>beldmit@gmail.com</email>) directed by Teodor Sigaev. 
+   Comments and bug reports are welcome.
   </para>
  </sect2>
 
-- 
2.7.4

#31Tom Lane
tgl@sss.pgh.pa.us
In reply to: Nikita Glukhov (#30)
Re: Ltree syntax improvement

Nikita Glukhov <n.gluhov@postgrespro.ru> writes:

Fixed patch attached.

I looked through this again, and I still don't feel very happy with it.

As I mentioned upthread, I'm not convinced that we ought to have
*both* quoting and backslash-escaping in these datatypes. TIMTOWTDI
may be a virtue to the Perl crowd, but to a lot of other people it's
just confusing and extra complication. In particular it will translate
directly to extra complication in every application that wants to use
non-alphanumeric labels, since they'll have to be prepared to deparse
whatever style the backend happens to put out. It's also adding a far
from trivial amount of complication to the patch itself. And it adds
bug-prone ambiguity; for instance, the patch fails to document whether
backslashes do anything special within quotes.

On balance, since the existing limitations on labels are an approximation
of SQL identifier rules, I think it'd be a good idea to use SQL-identifier
quoting rules; that is, you have to use quotes for anything that isn't
alphanumeric, backslashes are not special, and the way to get a double
quote that's data is to write two adjacent double quotes. That's simple
and it doesn't create any confusion when writing an ltree value inside
a SQL literal. It's less great if you're trying to write an ltree within
an array or record literal value, but we can't have everything (and the
backslash alternative would be no better for that anyway).

I've still got mixed emotions about the exception of allowing whitespace
to be stripped before or after a label. While that probably doesn't pose
any forward-compatibility hazards (ie, it's unlikely we'd want to redefine
such syntax to mean something different), I'm not sure it passes the
least-confusion test. We were just getting beat up a couple days ago
about the fact that record_in is lax about whitespace [1]/messages/by-id/BCE3F9FC-6BE9-44D8-AD25-F5FF109C7BD4@yugabyte.com[2]/messages/by-id/158593753274.7901.11178770123847486396@wrigleys.postgresql.org, so maybe
we shouldn't make that same choice here.

As far as the code itself goes, I don't think the code structure in
ltree_io.c is very well chosen. There seem to be half a dozen routines
that are all intimately dependent on the quoting/escaping rules, and
it's really pretty hard to be sure that they're all in sync (and if
they're not, that's going to lead to crashes and perhaps security-grade
bugs). Moreover it looks like you end up making multiple passes over the
input, so it's inefficient as well as hard to understand/maintain. Given
the choice to have a separate frontend function that recognizes a label as
a single token, it seems like it'd be better if that function were also
responsible for generating a de-quoted label value as it went.

I also feel that not enough attention has been paid to the error
reporting. For instance, the patch changes the longstanding policy of
reporting an overlength label with its end position:

@@ -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  
 --------------

That might be an OK choice in a green field, but this isn't a green field,
even if we did change the wording of the message since v12. I also noted
some random inconsistencies such as

regression=# select '1234567890*1234567890'::ltree;
ERROR: ltree syntax error at character 11
LINE 1: select '1234567890*1234567890'::ltree;
^
regression=# select '1234567890+1234567890'::ltree;
ERROR: ltree syntax error at character 11
LINE 1: select '1234567890+1234567890'::ltree;
^
DETAIL: Unexpected character

Apparently that's because * is special to lquery while + isn't, but
that distinction really shouldn't matter to ltree. (This DETAIL
message isn't close to following project style for detail messages,
either.) It'd probably be better if the frontend function didn't
contain assumptions about which punctuation characters are important.

Another thought here, though it's not really the fault of this patch,
is that I really think ltree ought to go over to a treatment of
non-ASCII characters that's more like the core parser's, ie anything
that isn't ASCII is data (or assumed-to-be-alphanumeric, if you prefer).
The trouble with the current definition is that it's dependent on
LC_CTYPE, so labels that are acceptable to one database might not be
acceptable elsewhere. We could remove that hazard, and as a bonus
eliminate some possibly-expensive character classification tests,
if we just said all non-ASCII characters are data.

regards, tom lane

[1]: /messages/by-id/BCE3F9FC-6BE9-44D8-AD25-F5FF109C7BD4@yugabyte.com
[2]: /messages/by-id/158593753274.7901.11178770123847486396@wrigleys.postgresql.org

#32Daniel Gustafsson
daniel@yesql.se
In reply to: Nikita Glukhov (#30)
Re: Ltree syntax improvement

On 4 Apr 2020, at 01:26, Nikita Glukhov <n.gluhov@postgrespro.ru> wrote:

Fixed patch attached.

This patch cause a regression in the ltree_plpython module, it needs the below
trivial change to make it pass again:

--- a/contrib/ltree_plpython/expected/ltree_plpython.out
+++ b/contrib/ltree_plpython/expected/ltree_plpython.out
@@ -39,5 +39,6 @@ $$;
 -- string.
 SELECT test2();
 ERROR:  ltree syntax error at character 1
+DETAIL:  Unexpected character
 CONTEXT:  while creating return value
 PL/Python function "test2"

Please submit a rebased version of the patch.

cheers ./daniel

#33Michael Paquier
michael@paquier.xyz
In reply to: Daniel Gustafsson (#32)
Re: Ltree syntax improvement

On Wed, Jul 01, 2020 at 03:23:30PM +0200, Daniel Gustafsson wrote:

Please submit a rebased version of the patch.

Which has not happened after two months, so I have marked the patch as
returned with feedback.
--
Michael