6 <indexterm zone=
"ltree">
7 <primary>ltree
</primary>
11 This module implements a data type
<type>ltree<
/> for representing
12 labels of data stored in a hierarchical tree-like structure.
13 Extensive facilities for searching through label trees are provided.
17 <title>Definitions
</title>
20 A
<firstterm>label
</firstterm> is a sequence of alphanumeric characters
21 and underscores (for example, in C locale the characters
22 <literal>A-Za-z0-
9_<
/> are allowed). Labels must be less than
256 bytes
27 Examples:
<literal>42<
/>,
<literal>Personal_Services<
/>
31 A
<firstterm>label path
</firstterm> is a sequence of zero or more
32 labels separated by dots, for example
<literal>L1.L2.L3<
/>, representing
33 a path from the root of a hierarchical tree to a particular node. The
34 length of a label path must be less than
65Kb, but keeping it under
2Kb is
35 preferable. In practice this is not a major limitation; for example,
36 the longest label path in the DMOZ catalogue (
<ulink
37 url=
"http://www.dmoz.org"></ulink>) is about
240 bytes.
41 Example:
<literal>Top.Countries.Europe.Russia
</literal>
45 The
<filename>ltree<
/> module provides several datatypes:
51 <type>ltree
</type> stores a label path.
57 <type>lquery
</type> represents a regular-expression-like pattern
58 for matching
<type>ltree<
/> values. A simple word matches that
59 label within a path. A star symbol (
<literal>*<
/>) matches zero
60 or more labels. For example:
62 foo
<lineannotation>Match the exact label path
<literal>foo<
/></lineannotation>
63 *.foo.*
<lineannotation>Match any label path containing the label
<literal>foo<
/></lineannotation>
64 *.foo
<lineannotation>Match any label path whose last label is
<literal>foo<
/></lineannotation>
69 Star symbols can also be quantified to restrict how many labels
72 *{
<replaceable>n<
/>}
<lineannotation>Match exactly
<replaceable>n<
/> labels
</lineannotation>
73 *{
<replaceable>n<
/>,}
<lineannotation>Match at least
<replaceable>n<
/> labels
</lineannotation>
74 *{
<replaceable>n<
/>,
<replaceable>m<
/>}
<lineannotation>Match at least
<replaceable>n<
/> but not more than
<replaceable>m<
/> labels
</lineannotation>
75 *{,
<replaceable>m<
/>}
<lineannotation>Match at most
<replaceable>m<
/> labels
— same as
</lineannotation> *{
0,
<replaceable>m<
/>}
80 There are several modifiers that can be put at the end of a non-star
81 label in
<type>lquery<
/> to make it match more than just the exact match:
83 @
<lineannotation>Match case-insensitively, for example
<literal>a@<
/> matches
<literal>A<
/></lineannotation>
84 *
<lineannotation>Match any label with this prefix, for example
<literal>foo*<
/> matches
<literal>foobar<
/></lineannotation>
85 %
<lineannotation>Match initial underscore-separated words
</lineannotation>
87 The behavior of
<literal>%<
/> is a bit complicated. It tries to match
88 words rather than the entire label. For example
89 <literal>foo_bar%<
/> matches
<literal>foo_bar_baz<
/> but not
90 <literal>foo_barbaz<
/>. If combined with
<literal>*<
/>, prefix
91 matching applies to each word separately, for example
92 <literal>foo_bar%*<
/> matches
<literal>foo1_bar2_baz<
/> but
93 not
<literal>foo1_br2_baz<
/>.
97 Also, you can write several possibly-modified labels separated with
98 <literal>|<
/> (OR) to match any of those labels, and you can put
99 <literal>!<
/> (NOT) at the start to match any label that doesn't
100 match any of the alternatives.
104 Here's an annotated example of
<type>lquery
</type>:
106 Top.*{
0,
2}.sport*@.!football|tennis.Russ*|Spain
109 This query will match any label path that:
111 <orderedlist numeration='loweralpha'
>
114 begins with the label
<literal>Top
</literal>
119 and next has zero to two labels before
124 a label beginning with the case-insensitive prefix
<literal>sport
</literal>
129 then a label not matching
<literal>football
</literal> nor
130 <literal>tennis
</literal>
135 and then ends with a label beginning with
<literal>Russ
</literal> or
136 exactly matching
<literal>Spain
</literal>.
143 <para><type>ltxtquery
</type> represents a full-text-search-like
144 pattern for matching
<type>ltree<
/> values. An
145 <type>ltxtquery
</type> value contains words, possibly with the
146 modifiers
<literal>@<
/>,
<literal>*<
/>,
<literal>%<
/> at the end;
147 the modifiers have the same meanings as in
<type>lquery<
/>.
148 Words can be combined with
<literal>&<
/> (AND),
149 <literal>|<
/> (OR),
<literal>!<
/> (NOT), and parentheses.
150 The key difference from
151 <type>lquery<
/> is that
<type>ltxtquery
</type> matches words without
152 regard to their position in the label path.
156 Here's an example
<type>ltxtquery
</type>:
158 Europe
& Russia*@
& !Transportation
160 This will match paths that contain the label
<literal>Europe
</literal> and
161 any label beginning with
<literal>Russia
</literal> (case-insensitive),
162 but not paths containing the label
<literal>Transportation
</literal>.
163 The location of these words within the path is not important.
164 Also, when
<literal>%<
/> is used, the word can be matched to any
165 underscore-separated word within a label, regardless of position.
172 Note:
<type>ltxtquery<
/> allows whitespace between symbols, but
173 <type>ltree<
/> and
<type>lquery<
/> do not.
178 <title>Operators and Functions
</title>
181 Type
<type>ltree<
/> has the usual comparison operators
182 <literal>=<
/>,
<literal><></literal>,
183 <literal><<
/>,
<literal>><
/>,
<literal><=<
/>,
<literal>>=<
/>.
184 Comparison sorts in the order of a tree traversal, with the children
185 of a node sorted by label text. In addition, there are the following
186 specialized operators:
189 <table id=
"ltree-op-table">
190 <title><type>ltree<
/> Operators
</title>
195 <entry>Operator
</entry>
196 <entry>Returns
</entry>
197 <entry>Description
</entry>
203 <entry><type>ltree<
/> <literal>@
><
/> <type>ltree<
/></entry>
204 <entry><type>boolean
</type></entry>
205 <entry>is left argument an ancestor of right (or equal)?
</entry>
209 <entry><type>ltree<
/> <literal><@<
/> <type>ltree<
/></entry>
210 <entry><type>boolean
</type></entry>
211 <entry>is left argument a descendant of right (or equal)?
</entry>
215 <entry><type>ltree<
/> <literal>~<
/> <type>lquery<
/></entry>
216 <entry><type>boolean
</type></entry>
217 <entry>does
<type>ltree<
/> match
<type>lquery<
/>?
</entry>
221 <entry><type>lquery<
/> <literal>~<
/> <type>ltree<
/></entry>
222 <entry><type>boolean
</type></entry>
223 <entry>does
<type>ltree<
/> match
<type>lquery<
/>?
</entry>
227 <entry><type>ltree<
/> <literal>?<
/> <type>lquery[]<
/></entry>
228 <entry><type>boolean
</type></entry>
229 <entry>does
<type>ltree<
/> match any
<type>lquery<
/> in array?
</entry>
233 <entry><type>lquery[]<
/> <literal>?<
/> <type>ltree<
/></entry>
234 <entry><type>boolean
</type></entry>
235 <entry>does
<type>ltree<
/> match any
<type>lquery<
/> in array?
</entry>
239 <entry><type>ltree<
/> <literal>@<
/> <type>ltxtquery<
/></entry>
240 <entry><type>boolean
</type></entry>
241 <entry>does
<type>ltree<
/> match
<type>ltxtquery<
/>?
</entry>
245 <entry><type>ltxtquery<
/> <literal>@<
/> <type>ltree<
/></entry>
246 <entry><type>boolean
</type></entry>
247 <entry>does
<type>ltree<
/> match
<type>ltxtquery<
/>?
</entry>
251 <entry><type>ltree<
/> <literal>||<
/> <type>ltree<
/></entry>
252 <entry><type>ltree
</type></entry>
253 <entry>concatenate
<type>ltree<
/> paths
</entry>
257 <entry><type>ltree<
/> <literal>||<
/> <type>text<
/></entry>
258 <entry><type>ltree
</type></entry>
259 <entry>convert text to
<type>ltree<
/> and concatenate
</entry>
263 <entry><type>text<
/> <literal>||<
/> <type>ltree<
/></entry>
264 <entry><type>ltree
</type></entry>
265 <entry>convert text to
<type>ltree<
/> and concatenate
</entry>
269 <entry><type>ltree[]<
/> <literal>@
><
/> <type>ltree<
/></entry>
270 <entry><type>boolean
</type></entry>
271 <entry>does array contain an ancestor of
<type>ltree<
/>?
</entry>
275 <entry><type>ltree<
/> <literal><@<
/> <type>ltree[]<
/></entry>
276 <entry><type>boolean
</type></entry>
277 <entry>does array contain an ancestor of
<type>ltree<
/>?
</entry>
281 <entry><type>ltree[]<
/> <literal><@<
/> <type>ltree<
/></entry>
282 <entry><type>boolean
</type></entry>
283 <entry>does array contain a descendant of
<type>ltree<
/>?
</entry>
287 <entry><type>ltree<
/> <literal>@
><
/> <type>ltree[]<
/></entry>
288 <entry><type>boolean
</type></entry>
289 <entry>does array contain a descendant of
<type>ltree<
/>?
</entry>
293 <entry><type>ltree[]<
/> <literal>~<
/> <type>lquery<
/></entry>
294 <entry><type>boolean
</type></entry>
295 <entry>does array contain any path matching
<type>lquery<
/>?
</entry>
299 <entry><type>lquery<
/> <literal>~<
/> <type>ltree[]<
/></entry>
300 <entry><type>boolean
</type></entry>
301 <entry>does array contain any path matching
<type>lquery<
/>?
</entry>
305 <entry><type>ltree[]<
/> <literal>?<
/> <type>lquery[]<
/></entry>
306 <entry><type>boolean
</type></entry>
307 <entry>does
<type>ltree<
/> array contain any path matching any
<type>lquery<
/>?
</entry>
311 <entry><type>lquery[]<
/> <literal>?<
/> <type>ltree[]<
/></entry>
312 <entry><type>boolean
</type></entry>
313 <entry>does
<type>ltree<
/> array contain any path matching any
<type>lquery<
/>?
</entry>
317 <entry><type>ltree[]<
/> <literal>@<
/> <type>ltxtquery<
/></entry>
318 <entry><type>boolean
</type></entry>
319 <entry>does array contain any path matching
<type>ltxtquery<
/>?
</entry>
323 <entry><type>ltxtquery<
/> <literal>@<
/> <type>ltree[]<
/></entry>
324 <entry><type>boolean
</type></entry>
325 <entry>does array contain any path matching
<type>ltxtquery<
/>?
</entry>
329 <entry><type>ltree[]<
/> <literal>?@
><
/> <type>ltree<
/></entry>
330 <entry><type>ltree
</type></entry>
331 <entry>first array entry that is an ancestor of
<type>ltree<
/>; NULL if none
</entry>
335 <entry><type>ltree[]<
/> <literal>?
<@<
/> <type>ltree<
/></entry>
336 <entry><type>ltree
</type></entry>
337 <entry>first array entry that is a descendant of
<type>ltree<
/>; NULL if none
</entry>
341 <entry><type>ltree[]<
/> <literal>?~<
/> <type>lquery<
/></entry>
342 <entry><type>ltree
</type></entry>
343 <entry>first array entry that matches
<type>lquery<
/>; NULL if none
</entry>
347 <entry><type>ltree[]<
/> <literal>?@<
/> <type>ltxtquery<
/></entry>
348 <entry><type>ltree
</type></entry>
349 <entry>first array entry that matches
<type>ltxtquery<
/>; NULL if none
</entry>
357 The operators
<literal><@
</literal>,
<literal>@
></literal>,
358 <literal>@
</literal> and
<literal>~
</literal> have analogues
359 <literal>^
<@<
/>,
<literal>^@
><
/>,
<literal>^@<
/>,
360 <literal>^~
</literal>, which are the same except they do not use
361 indexes. These are useful only for testing purposes.
365 The following functions are available:
368 <table id=
"ltree-func-table">
369 <title><type>ltree<
/> Functions
</title>
374 <entry>Function
</entry>
375 <entry>Return Type
</entry>
376 <entry>Description
</entry>
377 <entry>Example
</entry>
378 <entry>Result
</entry>
384 <entry><function>subltree(ltree, int start, int end)
</function></entry>
385 <entry><type>ltree
</type></entry>
386 <entry>subpath of
<type>ltree<
/> from position
<parameter>start<
/> to
387 position
<parameter>end<
/>-
1 (counting from
0)
</entry>
388 <entry><literal>subltree('Top.Child1.Child2',
1,
2)
</literal></entry>
389 <entry><literal>Child1
</literal></entry>
393 <entry><function>subpath(ltree, int offset, int len)
</function></entry>
394 <entry><type>ltree
</type></entry>
395 <entry>subpath of
<type>ltree<
/> starting at position
396 <parameter>offset<
/>, length
<parameter>len<
/>.
397 If
<parameter>offset<
/> is negative, subpath starts that far from the
398 end of the path. If
<parameter>len<
/> is negative, leaves that many
399 labels off the end of the path.
</entry>
400 <entry><literal>subpath('Top.Child1.Child2',
0,
2)
</literal></entry>
401 <entry><literal>Top.Child1
</literal></entry>
405 <entry><function>subpath(ltree, int offset)
</function></entry>
406 <entry><type>ltree
</type></entry>
407 <entry>subpath of
<type>ltree<
/> starting at position
408 <parameter>offset<
/>, extending to end of path.
409 If
<parameter>offset<
/> is negative, subpath starts that far from the
410 end of the path.
</entry>
411 <entry><literal>subpath('Top.Child1.Child2',
1)
</literal></entry>
412 <entry><literal>Child1.Child2
</literal></entry>
416 <entry><function>nlevel(ltree)
</function></entry>
417 <entry><type>integer
</type></entry>
418 <entry>number of labels in path
</entry>
419 <entry><literal>nlevel('Top.Child1.Child2')
</literal></entry>
420 <entry><literal>3</literal></entry>
424 <entry><function>index(ltree a, ltree b)
</function></entry>
425 <entry><type>integer
</type></entry>
426 <entry>position of first occurrence of
<parameter>b<
/> in
427 <parameter>a<
/>; -
1 if not found
</entry>
428 <entry><literal>index('
0.1.2.3.5.4.5.6.8.5.6.8','
5.6')
</literal></entry>
429 <entry><literal>6</literal></entry>
433 <entry><function>index(ltree a, ltree b, int offset)
</function></entry>
434 <entry><type>integer
</type></entry>
435 <entry>position of first occurrence of
<parameter>b<
/> in
436 <parameter>a<
/>, searching starting at
<parameter>offset<
/>;
437 negative
<parameter>offset<
/> means start
<parameter>-offset<
/>
438 labels from the end of the path
</entry>
439 <entry><literal>index('
0.1.2.3.5.4.5.6.8.5.6.8','
5.6',-
4)
</literal></entry>
440 <entry><literal>9</literal></entry>
444 <entry><function>text2ltree(text)
</function></entry>
445 <entry><type>ltree
</type></entry>
446 <entry>cast
<type>text<
/> to
<type>ltree<
/></entry>
447 <entry><literal></literal></entry>
448 <entry><literal></literal></entry>
452 <entry><function>ltree2text(ltree)
</function></entry>
453 <entry><type>text
</type></entry>
454 <entry>cast
<type>ltree<
/> to
<type>text<
/></entry>
455 <entry><literal></literal></entry>
456 <entry><literal></literal></entry>
460 <entry><function>lca(ltree, ltree, ...)
</function></entry>
461 <entry><type>ltree
</type></entry>
462 <entry>lowest common ancestor, i.e., longest common prefix of paths
463 (up to
8 arguments supported)
</entry>
464 <entry><literal>lca('
1.2.2.3','
1.2.3.4.5.6')
</literal></entry>
465 <entry><literal>1.2</literal></entry>
469 <entry><function>lca(ltree[])
</function></entry>
470 <entry><type>ltree
</type></entry>
471 <entry>lowest common ancestor, i.e., longest common prefix of paths
</entry>
472 <entry><literal>lca(array['
1.2.2.3'::ltree,'
1.2.3'])
</literal></entry>
473 <entry><literal>1.2</literal></entry>
482 <title>Indexes
</title>
484 <filename>ltree<
/> supports several types of indexes that can speed
485 up the indicated operators:
491 B-tree index over
<type>ltree<
/>:
492 <literal><<
/>,
<literal><=<
/>,
<literal>=<
/>,
493 <literal>>=<
/>,
<literal>></literal>
498 GiST index over
<type>ltree<
/>:
499 <literal><<
/>,
<literal><=<
/>,
<literal>=<
/>,
500 <literal>>=<
/>,
<literal>><
/>,
501 <literal>@
><
/>,
<literal><@<
/>,
502 <literal>@<
/>,
<literal>~<
/>,
<literal>?
</literal>
505 Example of creating such an index:
508 CREATE INDEX path_gist_idx ON test USING GIST (path);
513 GiST index over
<type>ltree[]<
/>:
514 <literal>ltree[]
<@ ltree<
/>,
<literal>ltree @
> ltree[]<
/>,
515 <literal>@<
/>,
<literal>~<
/>,
<literal>?
</literal>
518 Example of creating such an index:
521 CREATE INDEX path_gist_idx ON test USING GIST (array_path);
524 Note: This index type is lossy.
531 <title>Example
</title>
534 This example uses the following data (also available in file
535 <filename>contrib/ltree/ltreetest.sql<
/> in the source distribution):
539 CREATE TABLE test (path ltree);
540 INSERT INTO test VALUES ('Top');
541 INSERT INTO test VALUES ('Top.Science');
542 INSERT INTO test VALUES ('Top.Science.Astronomy');
543 INSERT INTO test VALUES ('Top.Science.Astronomy.Astrophysics');
544 INSERT INTO test VALUES ('Top.Science.Astronomy.Cosmology');
545 INSERT INTO test VALUES ('Top.Hobbies');
546 INSERT INTO test VALUES ('Top.Hobbies.Amateurs_Astronomy');
547 INSERT INTO test VALUES ('Top.Collections');
548 INSERT INTO test VALUES ('Top.Collections.Pictures');
549 INSERT INTO test VALUES ('Top.Collections.Pictures.Astronomy');
550 INSERT INTO test VALUES ('Top.Collections.Pictures.Astronomy.Stars');
551 INSERT INTO test VALUES ('Top.Collections.Pictures.Astronomy.Galaxies');
552 INSERT INTO test VALUES ('Top.Collections.Pictures.Astronomy.Astronauts');
553 CREATE INDEX path_gist_idx ON test USING gist(path);
554 CREATE INDEX path_idx ON test USING btree(path);
558 Now, we have a table
<structname>test<
/> populated with data describing
559 the hierarchy shown below:
565 Science Hobbies Collections
567 Astronomy Amateurs_Astronomy Pictures
569 Astrophysics Cosmology Astronomy
571 Galaxies Stars Astronauts
575 We can do inheritance:
579 ltreetest=# select path from test where path
<@ 'Top.Science';
581 ------------------------------------
583 Top.Science.Astronomy
584 Top.Science.Astronomy.Astrophysics
585 Top.Science.Astronomy.Cosmology
590 Here are some examples of path matching:
594 ltreetest=# select path from test where path ~ '*.Astronomy.*';
596 -----------------------------------------------
597 Top.Science.Astronomy
598 Top.Science.Astronomy.Astrophysics
599 Top.Science.Astronomy.Cosmology
600 Top.Collections.Pictures.Astronomy
601 Top.Collections.Pictures.Astronomy.Stars
602 Top.Collections.Pictures.Astronomy.Galaxies
603 Top.Collections.Pictures.Astronomy.Astronauts
606 ltreetest=# select path from test where path ~ '*.!pictures@.*.Astronomy.*';
608 ------------------------------------
609 Top.Science.Astronomy
610 Top.Science.Astronomy.Astrophysics
611 Top.Science.Astronomy.Cosmology
616 Here are some examples of full text search:
619 ltreetest=# select path from test where path @ 'Astro*%
& !pictures@';
621 ------------------------------------
622 Top.Science.Astronomy
623 Top.Science.Astronomy.Astrophysics
624 Top.Science.Astronomy.Cosmology
625 Top.Hobbies.Amateurs_Astronomy
628 ltreetest=# select path from test where path @ 'Astro*
& !pictures@';
630 ------------------------------------
631 Top.Science.Astronomy
632 Top.Science.Astronomy.Astrophysics
633 Top.Science.Astronomy.Cosmology
638 Path construction using functions:
641 ltreetest=# select subpath(path,
0,
2)||'Space'||subpath(path,
2) from test where path
<@ 'Top.Science.Astronomy';
643 ------------------------------------------
644 Top.Science.Space.Astronomy
645 Top.Science.Space.Astronomy.Astrophysics
646 Top.Science.Space.Astronomy.Cosmology
651 We could simplify this by creating a SQL function that inserts a label
652 at a specified position in a path:
655 CREATE FUNCTION ins_label(ltree, int, text) RETURNS ltree
656 AS 'select subpath($
1,
0,$
2) || $
3 || subpath($
1,$
2);'
657 LANGUAGE SQL IMMUTABLE;
659 ltreetest=# select ins_label(path,
2,'Space') from test where path
<@ 'Top.Science.Astronomy';
661 ------------------------------------------
662 Top.Science.Space.Astronomy
663 Top.Science.Space.Astronomy.Astrophysics
664 Top.Science.Space.Astronomy.Cosmology
670 <title>Authors
</title>
673 All work was done by Teodor Sigaev (
<email>teodor@stack.net
</email>) and
674 Oleg Bartunov (
<email>oleg@sai.msu.su
</email>). See
675 <ulink url=
"http://www.sai.msu.su/~megera/postgres/gist"></ulink> for
676 additional information. Authors would like to thank Eugeny Rodichev for
677 helpful discussions. Comments and bug reports are welcome.