On 25.03.2020 2:08, Tom Lane wrote:

Nikita Glukhov <n.glu...@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
>From 8ec6f93203684d63bf3c5d006c2499a71a1a9dad Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <n.glu...@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

>From 17c0141dcef1bf5bbbfbcfb9ae96b3a431648706 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <n.glu...@postgrespro.ru>
Date: Tue, 16 Jul 2019 17:59:32 +0300
Subject: [PATCH 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>teo...@stack.net</email>) and
+   Initial version was done by Teodor Sigaev (<email>teo...@sigaev.ru</email>) and
    Oleg Bartunov (<email>o...@sai.msu.su</email>). See
    <ulink url="http://www.sai.msu.su/~megera/postgres/gist/";></ulink> for
    additional information. Authors would like to thank Eugeny Rodichev for
-   helpful discussions. Comments and bug reports are welcome.
+   helpful discussions. Implementation of escaping syntax was done by Dmitry Belyavskiy
+   (<email>beld...@gmail.com</email>) directed by Teodor Sigaev. 
+   Comments and bug reports are welcome.
   </para>
  </sect2>
 
-- 
2.7.4

Reply via email to