From b1665bf01e5f4200d37addfc2ddc406ff7df14a5 Mon Sep 17 00:00:00 2001 From: Andrew Dunstan Date: Fri, 6 Jan 2023 16:03:19 -0500 Subject: [PATCH] Allow hyphens in ltree labels Also increase the allowed length of labels to 1000 characters Garen Torikian Discussion: https://postgr.es/m/CAGXsc+-mNg9Gc0rp-ER0sv+zkZSZp2wE9-LX6XcoWSLVz22tZA@mail.gmail.com --- contrib/ltree/expected/ltree.out | 50 ++++++++++++++++++++------------ contrib/ltree/ltree.h | 7 +++-- contrib/ltree/ltree_io.c | 10 +++---- contrib/ltree/ltxtquery_io.c | 4 +-- contrib/ltree/sql/ltree.sql | 18 ++++++++---- doc/src/sgml/ltree.sgml | 9 +++--- 6 files changed, 60 insertions(+), 38 deletions(-) diff --git a/contrib/ltree/expected/ltree.out b/contrib/ltree/expected/ltree.out index b95be71c78..d2a53b9f0c 100644 --- a/contrib/ltree/expected/ltree.out +++ b/contrib/ltree/expected/ltree.out @@ -1,4 +1,6 @@ CREATE EXTENSION ltree; +-- max length for a label +\set maxlbl 1000 -- Check whether any of our opclasses fail amvalidate SELECT amname, opcname FROM pg_opclass opc LEFT JOIN pg_am am ON am.oid = opcmethod @@ -25,6 +27,12 @@ SELECT '1.2'::ltree; 1.2 (1 row) +SELECT '1.2.-3'::ltree; + ltree +-------- + 1.2.-3 +(1 row) + SELECT '1.2._3'::ltree; ltree -------- @@ -45,15 +53,15 @@ ERROR: ltree syntax error LINE 1: SELECT '1.2.'::ltree; ^ DETAIL: Unexpected end of input. -SELECT repeat('x', 255)::ltree; - repeat ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ - xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +SELECT repeat('x', :maxlbl)::ltree; + repeat +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx (1 row) -SELECT repeat('x', 256)::ltree; +SELECT repeat('x', :maxlbl + 1)::ltree; ERROR: label string is too long -DETAIL: Label length is 256, must be at most 255, at character 257. +DETAIL: Label length is 1001, must be at most 1000, at character 1002. SELECT ltree2text('1.2.3.34.sdf'); ltree2text -------------- @@ -531,24 +539,24 @@ SELECT '1.2.3|@.4'::lquery; ERROR: lquery syntax error at character 7 LINE 1: SELECT '1.2.3|@.4'::lquery; ^ -SELECT (repeat('x', 255) || '*@@*')::lquery; - lquery -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx@* +SELECT (repeat('x', :maxlbl) || '*@@*')::lquery; + lquery +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx@* (1 row) -SELECT (repeat('x', 256) || '*@@*')::lquery; +SELECT (repeat('x', :maxlbl + 1) || '*@@*')::lquery; ERROR: label string is too long -DETAIL: Label length is 256, must be at most 255, at character 257. -SELECT ('!' || repeat('x', 255))::lquery; - lquery ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - !xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +DETAIL: Label length is 1001, must be at most 1000, at character 1002. +SELECT ('!' || repeat('x', :maxlbl))::lquery; + lquery +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + !xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx (1 row) -SELECT ('!' || repeat('x', 256))::lquery; +SELECT ('!' || repeat('x', :maxlbl + 1))::lquery; ERROR: label string is too long -DETAIL: Label length is 256, must be at most 255, at character 258. +DETAIL: Label length is 1001, must be at most 1000, at character 1003. SELECT nlevel('1.2.3.4'); nlevel -------- @@ -1195,6 +1203,12 @@ SELECT 'tree & aw_qw%*'::ltxtquery; tree & aw_qw%* (1 row) +SELECT 'tree & aw-qw%*'::ltxtquery; + ltxtquery +---------------- + tree & aw-qw%* +(1 row) + SELECT 'ltree.awdfg'::ltree @ '!tree & aWdf@*'::ltxtquery; ?column? ---------- diff --git a/contrib/ltree/ltree.h b/contrib/ltree/ltree.h index 2a80a02495..5e0761641d 100644 --- a/contrib/ltree/ltree.h +++ b/contrib/ltree/ltree.h @@ -12,10 +12,10 @@ /* * We want the maximum length of a label to be encoding-independent, so - * set it somewhat arbitrarily at 255 characters (not bytes), while using + * set it somewhat arbitrarily at 1000 characters (not bytes), while using * uint16 fields to hold the byte length. */ -#define LTREE_LABEL_MAX_CHARS 255 +#define LTREE_LABEL_MAX_CHARS 1000 /* * LOWER_NODE used to be defined in the Makefile via the compile flags. @@ -126,7 +126,8 @@ typedef struct #define LQUERY_HASNOT 0x01 -#define ISALNUM(x) ( t_isalnum(x) || t_iseq(x, '_') ) +/* valid label chars are alphanumerics, underscores and hyphens */ +#define ISLABEL(x) ( t_isalnum(x) || t_iseq(x, '_') || t_iseq(x, '-') ) /* full text query */ diff --git a/contrib/ltree/ltree_io.c b/contrib/ltree/ltree_io.c index f0dd3df511..56533172e3 100644 --- a/contrib/ltree/ltree_io.c +++ b/contrib/ltree/ltree_io.c @@ -74,7 +74,7 @@ parse_ltree(const char *buf, struct Node *escontext) switch (state) { case LTPRS_WAITNAME: - if (ISALNUM(ptr)) + if (ISLABEL(ptr)) { lptr->start = ptr; lptr->wlen = 0; @@ -92,7 +92,7 @@ parse_ltree(const char *buf, struct Node *escontext) lptr++; state = LTPRS_WAITNAME; } - else if (!ISALNUM(ptr)) + else if (!ISLABEL(ptr)) UNCHAR; break; default: @@ -316,7 +316,7 @@ parse_lquery(const char *buf, struct Node *escontext) switch (state) { case LQPRS_WAITLEVEL: - if (ISALNUM(ptr)) + if (ISLABEL(ptr)) { GETVAR(curqlevel) = lptr = (nodeitem *) palloc0(sizeof(nodeitem) * (numOR + 1)); lptr->start = ptr; @@ -339,7 +339,7 @@ parse_lquery(const char *buf, struct Node *escontext) UNCHAR; break; case LQPRS_WAITVAR: - if (ISALNUM(ptr)) + if (ISLABEL(ptr)) { lptr++; lptr->start = ptr; @@ -385,7 +385,7 @@ parse_lquery(const char *buf, struct Node *escontext) state = LQPRS_WAITLEVEL; curqlevel = NEXTLEV(curqlevel); } - else if (ISALNUM(ptr)) + else if (ISLABEL(ptr)) { /* disallow more chars after a flag */ if (lptr->flag) diff --git a/contrib/ltree/ltxtquery_io.c b/contrib/ltree/ltxtquery_io.c index a16e577303..c95f94df81 100644 --- a/contrib/ltree/ltxtquery_io.c +++ b/contrib/ltree/ltxtquery_io.c @@ -80,7 +80,7 @@ gettoken_query(QPRS_STATE *state, int32 *val, int32 *lenval, char **strval, uint (state->buf)++; return OPEN; } - else if (ISALNUM(state->buf)) + else if (ISLABEL(state->buf)) { state->state = INOPERAND; *strval = state->buf; @@ -93,7 +93,7 @@ gettoken_query(QPRS_STATE *state, int32 *val, int32 *lenval, char **strval, uint errmsg("operand syntax error"))); break; case INOPERAND: - if (ISALNUM(state->buf)) + if (ISLABEL(state->buf)) { if (*flag) ereturn(state->escontext, ERR, diff --git a/contrib/ltree/sql/ltree.sql b/contrib/ltree/sql/ltree.sql index eabef4f851..4a6e6266c3 100644 --- a/contrib/ltree/sql/ltree.sql +++ b/contrib/ltree/sql/ltree.sql @@ -1,5 +1,8 @@ CREATE EXTENSION ltree; +-- max length for a label +\set maxlbl 1000 + -- Check whether any of our opclasses fail amvalidate SELECT amname, opcname FROM pg_opclass opc LEFT JOIN pg_am am ON am.oid = opcmethod @@ -8,6 +11,7 @@ WHERE opc.oid >= 16384 AND NOT amvalidate(opc.oid); SELECT ''::ltree; SELECT '1'::ltree; SELECT '1.2'::ltree; +SELECT '1.2.-3'::ltree; SELECT '1.2._3'::ltree; -- empty labels not allowed @@ -15,8 +19,8 @@ SELECT '.2.3'::ltree; SELECT '1..3'::ltree; SELECT '1.2.'::ltree; -SELECT repeat('x', 255)::ltree; -SELECT repeat('x', 256)::ltree; +SELECT repeat('x', :maxlbl)::ltree; +SELECT repeat('x', :maxlbl + 1)::ltree; SELECT ltree2text('1.2.3.34.sdf'); SELECT text2ltree('1.2.3.34.sdf'); @@ -111,10 +115,10 @@ SELECT '1.!.3'::lquery; SELECT '1.2.!'::lquery; SELECT '1.2.3|@.4'::lquery; -SELECT (repeat('x', 255) || '*@@*')::lquery; -SELECT (repeat('x', 256) || '*@@*')::lquery; -SELECT ('!' || repeat('x', 255))::lquery; -SELECT ('!' || repeat('x', 256))::lquery; +SELECT (repeat('x', :maxlbl) || '*@@*')::lquery; +SELECT (repeat('x', :maxlbl + 1) || '*@@*')::lquery; +SELECT ('!' || repeat('x', :maxlbl))::lquery; +SELECT ('!' || repeat('x', :maxlbl + 1))::lquery; SELECT nlevel('1.2.3.4'); SELECT nlevel(('1' || repeat('.1', 65534))::ltree); @@ -233,6 +237,8 @@ SELECT 'QWER_GY'::ltree ~ 'q_t%@*'; --ltxtquery SELECT '!tree & aWdf@*'::ltxtquery; SELECT 'tree & aw_qw%*'::ltxtquery; +SELECT 'tree & aw-qw%*'::ltxtquery; + SELECT 'ltree.awdfg'::ltree @ '!tree & aWdf@*'::ltxtquery; SELECT 'tree.awdfg'::ltree @ '!tree & aWdf@*'::ltxtquery; SELECT 'tree.awdfg'::ltree @ '!tree | aWdf@*'::ltxtquery; diff --git a/doc/src/sgml/ltree.sgml b/doc/src/sgml/ltree.sgml index 508f404ae8..edea1eadb8 100644 --- a/doc/src/sgml/ltree.sgml +++ b/doc/src/sgml/ltree.sgml @@ -23,10 +23,11 @@ Definitions - A label is a sequence of alphanumeric characters - and underscores (for example, in C locale the characters - A-Za-z0-9_ are allowed). - Labels must be less than 256 characters long. + A label is a sequence of alphanumeric characters, + underscores, and hyphens. Valid alphanumeric character ranges are + dependent on the database locale. For example, in C locale, the characters + A-Za-z0-9_- are allowed. + Labels must be no more than 1000 characters long.