Add ltree data type to contrib, from Teodor Sigaev and Oleg Bartunov.
This commit is contained in:
parent
210e64fe08
commit
1dedbf2da5
@ -1,4 +1,4 @@
|
|||||||
# $Header: /cvsroot/pgsql/contrib/Makefile,v 1.33 2002/07/30 16:32:20 momjian Exp $
|
# $Header: /cvsroot/pgsql/contrib/Makefile,v 1.34 2002/07/30 16:40:34 momjian Exp $
|
||||||
|
|
||||||
subdir = contrib
|
subdir = contrib
|
||||||
top_builddir = ..
|
top_builddir = ..
|
||||||
@ -20,6 +20,7 @@ WANTED_DIRS = \
|
|||||||
intarray \
|
intarray \
|
||||||
isbn_issn \
|
isbn_issn \
|
||||||
lo \
|
lo \
|
||||||
|
ltree \
|
||||||
miscutil \
|
miscutil \
|
||||||
noupdate \
|
noupdate \
|
||||||
oid2name \
|
oid2name \
|
||||||
|
@ -95,6 +95,10 @@ lo -
|
|||||||
Large Object maintenance
|
Large Object maintenance
|
||||||
by Peter Mount <peter@retep.org.uk>
|
by Peter Mount <peter@retep.org.uk>
|
||||||
|
|
||||||
|
ltree -
|
||||||
|
Tree-like data structures
|
||||||
|
by Teodor Sigaev <teodor@stack.net> and Oleg Bartunov <oleg@sai.msu.su>
|
||||||
|
|
||||||
mSQL-interface -
|
mSQL-interface -
|
||||||
mSQL API translation library
|
mSQL API translation library
|
||||||
by Aldrin Leal <aldrin@americasnet.com>
|
by Aldrin Leal <aldrin@americasnet.com>
|
||||||
|
13
contrib/ltree/Makefile
Normal file
13
contrib/ltree/Makefile
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
subdir = contrib/tree
|
||||||
|
top_builddir = ../..
|
||||||
|
include $(top_builddir)/src/Makefile.global
|
||||||
|
|
||||||
|
PG_CPPFLAGS = -DLOWER_NODE
|
||||||
|
MODULE_big = ltree
|
||||||
|
OBJS = ltree_io.o ltree_op.o lquery_op.o _ltree_op.o crc32.o \
|
||||||
|
ltxtquery_io.o ltxtquery_op.o ltree_gist.o _ltree_gist.o
|
||||||
|
DATA_built = ltree.sql
|
||||||
|
DOCS = README.ltree
|
||||||
|
REGRESS = ltree
|
||||||
|
|
||||||
|
include $(top_srcdir)/contrib/contrib-global.mk
|
465
contrib/ltree/README.ltree
Normal file
465
contrib/ltree/README.ltree
Normal file
@ -0,0 +1,465 @@
|
|||||||
|
contrib/ltree module
|
||||||
|
|
||||||
|
ltree - is a PostgreSQL contrib module which contains implementation of data
|
||||||
|
types, indexed access methods and queries for data organized as a tree-like
|
||||||
|
structures.
|
||||||
|
This module will works for PostgreSQL version 7.3.
|
||||||
|
(patch for 7.2 version is provided, see INSTALLATION)
|
||||||
|
-------------------------------------------------------------------------------
|
||||||
|
All work was done by Teodor Sigaev (teodor@stack.net) and Oleg Bartunov
|
||||||
|
(oleg@sai.msu.su). See http://www.sai.msu.su/~megera/postgres/gist for
|
||||||
|
additional information. Authors would like to thank Eugeny Rodichev for helpful
|
||||||
|
discussions. Comments and bug reports are welcome.
|
||||||
|
-------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
LEGAL NOTICES: This module is released under BSD license (as PostgreSQL
|
||||||
|
itself). This work was done in framework of Russian Scientific Network and
|
||||||
|
partially supported by Russian Foundation for Basic Research and Stack Group.
|
||||||
|
-------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
MOTIVATION
|
||||||
|
|
||||||
|
This is a placeholder for introduction to the problem. Hope, people reading
|
||||||
|
this document doesn't need it too much :-)
|
||||||
|
|
||||||
|
DEFINITIONS
|
||||||
|
|
||||||
|
A label of a node is a sequence of one or more words separated by blank
|
||||||
|
character '_' and containing letters and digits ( for example, [a-zA-Z0-9] for
|
||||||
|
C locale). The length of a label is limited by 256 bytes.
|
||||||
|
|
||||||
|
Example: 'Countries', 'Personal_Services'
|
||||||
|
|
||||||
|
A label path of a node is a sequence of one or more dot-separated labels
|
||||||
|
l1.l2...ln, represents path from root to the node. The length of a label path
|
||||||
|
is limited by 65Kb, but size <= 2Kb is preferrable. We consider it's not a
|
||||||
|
strict limitation ( maximal size of label path for DMOZ catalogue - http://
|
||||||
|
www.dmoz.org, is about 240 bytes !)
|
||||||
|
|
||||||
|
Example: 'Top.Countries.Europe.Russia'
|
||||||
|
|
||||||
|
We introduce several datatypes:
|
||||||
|
|
||||||
|
ltree
|
||||||
|
- is a datatype for label path.
|
||||||
|
|
||||||
|
ltree[]
|
||||||
|
- is a datatype for arrays of ltree.
|
||||||
|
|
||||||
|
lquery
|
||||||
|
- is a path expression that has regular expression in the label path and
|
||||||
|
used for ltree matching. Star symbol (*) is used to specify any number of
|
||||||
|
labels (levels) and could be used at the beginning and the end of lquery,
|
||||||
|
for example, '*.Europe.*'.
|
||||||
|
|
||||||
|
The following quantifiers are recognized for '*' (like in Perl):
|
||||||
|
|
||||||
|
{n} Match exactly n levels
|
||||||
|
{n,} Match at least n levels
|
||||||
|
{n,m} Match at least n but not more than m levels
|
||||||
|
{,m} Match at maximum m levels (eq. to {0,m})
|
||||||
|
|
||||||
|
It is possible to use several modifiers at the end of a label:
|
||||||
|
|
||||||
|
|
||||||
|
@ Do case-insensitive label matching
|
||||||
|
* Do prefix matching for a label
|
||||||
|
% Don't account word separator '_' in label matching, that is
|
||||||
|
'Russian%' would match 'Russian_nations', but not 'Russian'
|
||||||
|
|
||||||
|
lquery could contains logical '!' (NOT) at the beginning of the label and '
|
||||||
|
|' (OR) to specify possible alternatives for label matching.
|
||||||
|
|
||||||
|
Example of lquery:
|
||||||
|
|
||||||
|
|
||||||
|
Top.*{0,2}.sport*@.!football|tennis.Russ*|Spain
|
||||||
|
a) b) c) d) e)
|
||||||
|
|
||||||
|
A label path should
|
||||||
|
+ a) begins from a node with label 'Top'
|
||||||
|
+ b) and following zero or 2 labels until
|
||||||
|
+ c) a node with label beginning from case-insensitive prefix 'sport'
|
||||||
|
+ d) following node with label not matched 'football' or 'tennis' and
|
||||||
|
+ e) ends on node with label beginning from 'Russ' or strictly matched
|
||||||
|
'Spain'.
|
||||||
|
|
||||||
|
ltxtquery
|
||||||
|
- is a datatype for label searching (like type 'query' for full text
|
||||||
|
searching, see contrib/tsearch). It's possible to use modifiers @,%,* at
|
||||||
|
the end of word. The meaning of modifiers are the same as for lquery.
|
||||||
|
|
||||||
|
Example: 'Europe & Russia*@ & !Transportation'
|
||||||
|
|
||||||
|
Search paths contain words 'Europe' and 'Russia*' (case-insensitive) and
|
||||||
|
not 'Transportation'. Notice, the order of words as they appear in label
|
||||||
|
path is not important !
|
||||||
|
|
||||||
|
OPERATIONS
|
||||||
|
|
||||||
|
The following operations are defined for type ltree:
|
||||||
|
|
||||||
|
<,>,<=,>=,=, <>
|
||||||
|
- have their usual meanings. Comparison is doing in the order of direct
|
||||||
|
tree traversing, children of a node are sorted lexicographic.
|
||||||
|
ltree @> ltree
|
||||||
|
- returns TRUE if left argument is an ancestor of right argument (or
|
||||||
|
equal).
|
||||||
|
ltree <@ ltree
|
||||||
|
- returns TRUE if left argument is a descendant of right argument (or
|
||||||
|
equal).
|
||||||
|
ltree ~ lquery, lquery ~ ltree
|
||||||
|
- return TRUE if node represented by ltree satisfies lquery.
|
||||||
|
ltree @ ltxtquery, ltxtquery @ ltree
|
||||||
|
- return TRUE if node represented by ltree satisfies ltxtquery.
|
||||||
|
ltree || ltree, ltree || text, text || ltree
|
||||||
|
- return concatenated ltree.
|
||||||
|
|
||||||
|
Operations for arrays of ltree (ltree[]):
|
||||||
|
|
||||||
|
ltree[] @> ltree, ltree <@ ltree[]
|
||||||
|
- returns TRUE if array ltree[] contains an ancestor of ltree.
|
||||||
|
ltree @> ltree[], ltree[] <@ ltree
|
||||||
|
- returns TRUE if array ltree[] contains a descendant of ltree.
|
||||||
|
ltree[] ~ lquery, lquery ~ ltree[]
|
||||||
|
- returns TRUE if array ltree[] contains label paths matched lquery.
|
||||||
|
ltree[] @ ltxtquery, ltxtquery @ ltree[]
|
||||||
|
- returns TRUE if array ltree[] contains label paths matched ltxtquery
|
||||||
|
(full text search).
|
||||||
|
ltree[] ?@> ltree, ltree ?<@ ltree[], ltree[] ?~ lquery, ltree[] ?@ ltxtquery
|
||||||
|
- returns first element of array ltree[] satisfies corresponding condition
|
||||||
|
and NULL in vice versa.
|
||||||
|
|
||||||
|
REMARK
|
||||||
|
|
||||||
|
Operations <@, @>, @ and ~ have analogues - ^<@, ^@>, ^@, ^~, which doesn't use
|
||||||
|
indices !
|
||||||
|
|
||||||
|
INDICES
|
||||||
|
|
||||||
|
Various indices could be created to speed up execution of operations:
|
||||||
|
|
||||||
|
* B-tree index over ltree:
|
||||||
|
<, <=, =, =>, >
|
||||||
|
* GiST index over ltree:
|
||||||
|
<, <=, =, =>, >, @>, <@, @, ~
|
||||||
|
Example:
|
||||||
|
create index path_gist_idx on test using gist_ltree_ops (path);
|
||||||
|
* GiST index over ltree[]:
|
||||||
|
ltree[]<@ ltree, ltree @> ltree[], @, ~.
|
||||||
|
Example:
|
||||||
|
create index path_gist_idx on test using gist__ltree_ops (array_path);
|
||||||
|
Notices: This index is lossy.
|
||||||
|
|
||||||
|
FUNCTIONS
|
||||||
|
|
||||||
|
ltree subltree
|
||||||
|
ltree subltree(ltree, start, end)
|
||||||
|
returns subpath of ltree from start (inclusive) until the end.
|
||||||
|
# select subltree('Top.Child1.Child2',1,2);
|
||||||
|
subltree
|
||||||
|
--------
|
||||||
|
Child1
|
||||||
|
ltree subpath
|
||||||
|
ltree subpath(ltree, OFFSET,LEN)
|
||||||
|
ltree subpath(ltree, OFFSET)
|
||||||
|
returns subpath of ltree from OFFSET (inclusive) with length LEN.
|
||||||
|
If OFFSET is negative returns subpath starts that far from the end
|
||||||
|
of the path. If LENGTH is omitted, returns everything to the end
|
||||||
|
of the path. If LENGTH is negative, leaves that many labels off
|
||||||
|
the end of the path.
|
||||||
|
# select subpath('Top.Child1.Child2',1,2);
|
||||||
|
subpath
|
||||||
|
-------
|
||||||
|
Child1.Child2
|
||||||
|
|
||||||
|
# select subpath('Top.Child1.Child2',-2,1);
|
||||||
|
subpath
|
||||||
|
---------
|
||||||
|
Child1
|
||||||
|
int4 nlevel
|
||||||
|
|
||||||
|
int4 nlevel(ltree) - returns level of the node.
|
||||||
|
# select nlevel('Top.Child1.Child2');
|
||||||
|
nlevel
|
||||||
|
--------
|
||||||
|
3
|
||||||
|
|
||||||
|
Note, that arguments start, end, OFFSET, LEN have meaning of level of the node
|
||||||
|
!
|
||||||
|
|
||||||
|
INSTALLATION
|
||||||
|
|
||||||
|
cd contrib/ltree
|
||||||
|
make
|
||||||
|
make install
|
||||||
|
make installcheck
|
||||||
|
|
||||||
|
for 7.2 one needs to apply patch ( patch < patch.72) before installation !
|
||||||
|
|
||||||
|
EXAMPLE OF USAGE
|
||||||
|
|
||||||
|
createdb ltreetest
|
||||||
|
psql ltreetest < /usr/local/pgsql/share/contrib/ltree.sql
|
||||||
|
psql ltreetest < ltreetest.sql
|
||||||
|
|
||||||
|
Now, we have a database ltreetest populated with a data describing hierarchy
|
||||||
|
shown below:
|
||||||
|
|
||||||
|
|
||||||
|
TOP
|
||||||
|
/ | \
|
||||||
|
Science Hobbies Collections
|
||||||
|
/ | \
|
||||||
|
Astronomy Amateurs_Astronomy Pictures
|
||||||
|
/ \ |
|
||||||
|
Astrophysics Cosmology Astronomy
|
||||||
|
/ | \
|
||||||
|
Galaxies Stars Astronauts
|
||||||
|
|
||||||
|
Inheritance:
|
||||||
|
|
||||||
|
ltreetest=# select path from test where path <@ 'Top.Science';
|
||||||
|
path
|
||||||
|
------------------------------------
|
||||||
|
Top.Science
|
||||||
|
Top.Science.Astronomy
|
||||||
|
Top.Science.Astronomy.Astrophysics
|
||||||
|
Top.Science.Astronomy.Cosmology
|
||||||
|
(4 rows)
|
||||||
|
|
||||||
|
Matching:
|
||||||
|
|
||||||
|
ltreetest=# select path from test where path ~ '*.Astronomy.*';
|
||||||
|
path
|
||||||
|
-----------------------------------------------
|
||||||
|
Top.Science.Astronomy
|
||||||
|
Top.Science.Astronomy.Astrophysics
|
||||||
|
Top.Science.Astronomy.Cosmology
|
||||||
|
Top.Collections.Pictures.Astronomy
|
||||||
|
Top.Collections.Pictures.Astronomy.Stars
|
||||||
|
Top.Collections.Pictures.Astronomy.Galaxies
|
||||||
|
Top.Collections.Pictures.Astronomy.Astronauts
|
||||||
|
(7 rows)
|
||||||
|
ltreetest=# select path from test where path ~ '*.!pictures@.*.Astronomy.*';
|
||||||
|
path
|
||||||
|
------------------------------------
|
||||||
|
Top.Science.Astronomy
|
||||||
|
Top.Science.Astronomy.Astrophysics
|
||||||
|
Top.Science.Astronomy.Cosmology
|
||||||
|
(3 rows)
|
||||||
|
|
||||||
|
Full text search:
|
||||||
|
|
||||||
|
ltreetest=# select path from test where path @ 'Astro*% & !pictures@';
|
||||||
|
path
|
||||||
|
------------------------------------
|
||||||
|
Top.Science.Astronomy
|
||||||
|
Top.Science.Astronomy.Astrophysics
|
||||||
|
Top.Science.Astronomy.Cosmology
|
||||||
|
Top.Hobbies.Amateurs_Astronomy
|
||||||
|
(4 rows)
|
||||||
|
|
||||||
|
ltreetest=# select path from test where path @ 'Astro* & !pictures@';
|
||||||
|
path
|
||||||
|
------------------------------------
|
||||||
|
Top.Science.Astronomy
|
||||||
|
Top.Science.Astronomy.Astrophysics
|
||||||
|
Top.Science.Astronomy.Cosmology
|
||||||
|
(3 rows)
|
||||||
|
|
||||||
|
Using Functions:
|
||||||
|
|
||||||
|
ltreetest=# select subpath(path,0,2)||'Space'||subpath(path,2) from test where path <@ 'Top.Science.Astronomy';
|
||||||
|
?column?
|
||||||
|
------------------------------------------
|
||||||
|
Top.Science.Space.Astronomy
|
||||||
|
Top.Science.Space.Astronomy.Astrophysics
|
||||||
|
Top.Science.Space.Astronomy.Cosmology
|
||||||
|
(3 rows)
|
||||||
|
We could create SQL-function:
|
||||||
|
CREATE FUNCTION ins_label(ltree, int4, text) RETURNS ltree
|
||||||
|
AS 'select subpath($1,0,$2) || $3 || subpath($1,$2);'
|
||||||
|
LANGUAGE SQL WITH (ISCACHABLE);
|
||||||
|
|
||||||
|
and previous select could be rewritten as:
|
||||||
|
|
||||||
|
ltreetest=# select ins_label(path,2,'Space') from test where path <@ 'Top.Science.Astronomy';
|
||||||
|
ins_label
|
||||||
|
------------------------------------------
|
||||||
|
Top.Science.Space.Astronomy
|
||||||
|
Top.Science.Space.Astronomy.Astrophysics
|
||||||
|
Top.Science.Space.Astronomy.Cosmology
|
||||||
|
(3 rows)
|
||||||
|
|
||||||
|
Or with another arguments:
|
||||||
|
|
||||||
|
CREATE FUNCTION ins_label(ltree, ltree, text) RETURNS ltree
|
||||||
|
AS 'select subpath($1,0,nlevel($2)) || $3 || subpath($1,nlevel($2));'
|
||||||
|
LANGUAGE SQL WITH (ISCACHABLE);
|
||||||
|
|
||||||
|
ltreetest=# select ins_label(path,'Top.Science'::ltree,'Space') from test where path <@ 'Top.Science.Astronomy';
|
||||||
|
ins_label
|
||||||
|
------------------------------------------
|
||||||
|
Top.Science.Space.Astronomy
|
||||||
|
Top.Science.Space.Astronomy.Astrophysics
|
||||||
|
Top.Science.Space.Astronomy.Cosmology
|
||||||
|
(3 rows)
|
||||||
|
|
||||||
|
ADDITIONAL DATA
|
||||||
|
|
||||||
|
To get more feeling from our ltree module you could download
|
||||||
|
dmozltree-eng.sql.gz (about 3Mb tar.gz archive containing 300,274 nodes),
|
||||||
|
available from http://www.sai.msu.su/~megera/postgres/gist/ltree/
|
||||||
|
dmozltree-eng.sql.gz, which is DMOZ catalogue, prepared for use with ltree.
|
||||||
|
Setup your test database (dmoz), load ltree module and issue command:
|
||||||
|
|
||||||
|
zcat dmozltree-eng.sql.gz| psql dmoz
|
||||||
|
|
||||||
|
Data will be loaded into database dmoz and all indices will be created.
|
||||||
|
|
||||||
|
BENCHMARKS
|
||||||
|
|
||||||
|
All runs were performed on my IBM ThinkPad T21 (256 MB RAM, 750Mhz) using DMOZ
|
||||||
|
data, containing 300,274 nodes (see above for download link). We used some
|
||||||
|
basic queries typical for walking through catalog.
|
||||||
|
|
||||||
|
QUERIES
|
||||||
|
|
||||||
|
* Q0: Count all rows (sort of base time for comparison)
|
||||||
|
select count(*) from dmoz;
|
||||||
|
count
|
||||||
|
--------
|
||||||
|
300274
|
||||||
|
(1 row)
|
||||||
|
* Q1: Get direct children (without inheritance)
|
||||||
|
select path from dmoz where path ~ 'Top.Adult.Arts.Animation.*{1}';
|
||||||
|
path
|
||||||
|
-----------------------------------
|
||||||
|
Top.Adult.Arts.Animation.Cartoons
|
||||||
|
Top.Adult.Arts.Animation.Anime
|
||||||
|
(2 rows)
|
||||||
|
* Q2: The same as Q1 but with counting of successors
|
||||||
|
select path as parentpath , (select count(*)-1 from dmoz where path <@
|
||||||
|
p.path) as count from dmoz p where path ~ 'Top.Adult.Arts.Animation.*{1}';
|
||||||
|
parentpath | count
|
||||||
|
-----------------------------------+-------
|
||||||
|
Top.Adult.Arts.Animation.Cartoons | 2
|
||||||
|
Top.Adult.Arts.Animation.Anime | 61
|
||||||
|
(2 rows)
|
||||||
|
* Q3: Get all parents
|
||||||
|
select path from dmoz where path @> 'Top.Adult.Arts.Animation' order by
|
||||||
|
path asc;
|
||||||
|
path
|
||||||
|
--------------------------
|
||||||
|
Top
|
||||||
|
Top.Adult
|
||||||
|
Top.Adult.Arts
|
||||||
|
Top.Adult.Arts.Animation
|
||||||
|
(4 rows)
|
||||||
|
* Q4: Get all parents with counting of children
|
||||||
|
select path, (select count(*)-1 from dmoz where path <@ p.path) as count
|
||||||
|
from dmoz p where path @> 'Top.Adult.Arts.Animation' order by path asc;
|
||||||
|
path | count
|
||||||
|
--------------------------+--------
|
||||||
|
Top | 300273
|
||||||
|
Top.Adult | 4913
|
||||||
|
Top.Adult.Arts | 339
|
||||||
|
Top.Adult.Arts.Animation | 65
|
||||||
|
(4 rows)
|
||||||
|
* Q5: Get all children with levels
|
||||||
|
select path, nlevel(path) - nlevel('Top.Adult.Arts.Animation') as level
|
||||||
|
from dmoz where path ~ 'Top.Adult.Arts.Animation.*{1,2}' order by path asc;
|
||||||
|
path | level
|
||||||
|
------------------------------------------------+-------
|
||||||
|
Top.Adult.Arts.Animation.Anime | 1
|
||||||
|
Top.Adult.Arts.Animation.Anime.Fan_Works | 2
|
||||||
|
Top.Adult.Arts.Animation.Anime.Games | 2
|
||||||
|
Top.Adult.Arts.Animation.Anime.Genres | 2
|
||||||
|
Top.Adult.Arts.Animation.Anime.Image_Galleries | 2
|
||||||
|
Top.Adult.Arts.Animation.Anime.Multimedia | 2
|
||||||
|
Top.Adult.Arts.Animation.Anime.Resources | 2
|
||||||
|
Top.Adult.Arts.Animation.Anime.Titles | 2
|
||||||
|
Top.Adult.Arts.Animation.Cartoons | 1
|
||||||
|
Top.Adult.Arts.Animation.Cartoons.AVS | 2
|
||||||
|
Top.Adult.Arts.Animation.Cartoons.Members | 2
|
||||||
|
(11 rows)
|
||||||
|
|
||||||
|
Timings
|
||||||
|
|
||||||
|
+---------------------------------------------+
|
||||||
|
|Query|Rows|Time (ms) index|Time (ms) no index|
|
||||||
|
|-----+----+---------------+------------------|
|
||||||
|
| Q0| 1| NA| 1453.44|
|
||||||
|
|-----+----+---------------+------------------|
|
||||||
|
| Q1| 2| 0.49| 1001.54|
|
||||||
|
|-----+----+---------------+------------------|
|
||||||
|
| Q2| 2| 1.48| 3009.39|
|
||||||
|
|-----+----+---------------+------------------|
|
||||||
|
| Q3| 4| 0.55| 906.98|
|
||||||
|
|-----+----+---------------+------------------|
|
||||||
|
| Q4| 4| 24385.07| 4951.91|
|
||||||
|
|-----+----+---------------+------------------|
|
||||||
|
| Q5| 11| 0.85| 1003.23|
|
||||||
|
+---------------------------------------------+
|
||||||
|
Timings without indices were obtained using operations which doesn't use
|
||||||
|
indices (see above)
|
||||||
|
|
||||||
|
Remarks
|
||||||
|
|
||||||
|
We didn't run full-scale tests, also we didn't present (yet) data for
|
||||||
|
operations with arrays of ltree (ltree[]) and full text searching. We'll
|
||||||
|
appreciate your input. So far, below some (rather obvious) results:
|
||||||
|
|
||||||
|
* Indices does help execution of queries
|
||||||
|
* Q4 performs bad because one needs to read almost all data from the HDD
|
||||||
|
|
||||||
|
CHANGES
|
||||||
|
|
||||||
|
July 13, 2002
|
||||||
|
Initial release.
|
||||||
|
|
||||||
|
TODO
|
||||||
|
|
||||||
|
* Testing on 64-bit platforms. There are several known problems with byte
|
||||||
|
alignment;
|
||||||
|
* Better documentation;
|
||||||
|
* We plan (probably) to improve regular expressions processing using
|
||||||
|
non-deterministic automata;
|
||||||
|
* Some sort of XML support;
|
||||||
|
* Better full text searching;
|
||||||
|
|
||||||
|
SOME BACKGROUNDS
|
||||||
|
|
||||||
|
The approach we use for ltree is much like one we used in our other GiST based
|
||||||
|
contrib modules (intarray, tsearch, tree, btree_gist, rtree_gist). Theoretical
|
||||||
|
background is available in papers referenced from our GiST development page
|
||||||
|
(http://www.sai.msu.su/~megera/postgres/gist).
|
||||||
|
|
||||||
|
A hierarchical data structure (tree) is a set of nodes. Each node has a
|
||||||
|
signature (LPS) of a fixed size, which is a hashed label path of that node.
|
||||||
|
Traversing a tree we could *certainly* prune branches if
|
||||||
|
|
||||||
|
LQS (bitwise AND) LPS != LQS
|
||||||
|
|
||||||
|
where LQS is a signature of lquery or ltxtquery, obtained in the same way as
|
||||||
|
LPS.
|
||||||
|
|
||||||
|
ltree[]:
|
||||||
|
For array of ltree LPS is a bitwise OR-ed signatures of *ALL* children
|
||||||
|
reachable from that node. Signatures are stored in RD-tree, implemented using
|
||||||
|
GiST, which provides indexed access.
|
||||||
|
|
||||||
|
ltree:
|
||||||
|
For ltree we store LPS in a B-tree, implemented using GiST. Each node entry is
|
||||||
|
represented by (left_bound, signature, right_bound), so that we could speedup
|
||||||
|
operations <, <=, =, =>, > using left_bound, right_bound and prune branches of
|
||||||
|
a tree using signature.
|
||||||
|
-------------------------------------------------------------------------------
|
||||||
|
We ask people who find the module useful to send us a postcards to:
|
||||||
|
Moscow, 119899, Universitetski pr.13, Moscow State University, Sternberg
|
||||||
|
Astronomical Institute, Russia
|
||||||
|
For: Bartunov O.S.
|
||||||
|
and
|
||||||
|
Moscow, Bratislavskaya str.23, appt. 18, Russia
|
||||||
|
For: Sigaev F.G.
|
549
contrib/ltree/_ltree_gist.c
Normal file
549
contrib/ltree/_ltree_gist.c
Normal file
@ -0,0 +1,549 @@
|
|||||||
|
/*
|
||||||
|
* GiST support for ltree[]
|
||||||
|
* Teodor Sigaev <teodor@stack.net>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "ltree.h"
|
||||||
|
#include "access/gist.h"
|
||||||
|
#include "access/rtree.h"
|
||||||
|
#include "access/nbtree.h"
|
||||||
|
#include "utils/array.h"
|
||||||
|
|
||||||
|
#include "crc32.h"
|
||||||
|
|
||||||
|
PG_FUNCTION_INFO_V1( _ltree_compress );
|
||||||
|
Datum _ltree_compress(PG_FUNCTION_ARGS);
|
||||||
|
PG_FUNCTION_INFO_V1( _ltree_same );
|
||||||
|
Datum _ltree_same(PG_FUNCTION_ARGS);
|
||||||
|
PG_FUNCTION_INFO_V1( _ltree_union );
|
||||||
|
Datum _ltree_union(PG_FUNCTION_ARGS);
|
||||||
|
PG_FUNCTION_INFO_V1( _ltree_penalty );
|
||||||
|
Datum _ltree_penalty(PG_FUNCTION_ARGS);
|
||||||
|
PG_FUNCTION_INFO_V1( _ltree_picksplit );
|
||||||
|
Datum _ltree_picksplit(PG_FUNCTION_ARGS);
|
||||||
|
PG_FUNCTION_INFO_V1( _ltree_consistent );
|
||||||
|
Datum _ltree_consistent(PG_FUNCTION_ARGS);
|
||||||
|
|
||||||
|
#define GETENTRY(vec,pos) ((ltree_gist *) DatumGetPointer(((GISTENTRY *) VARDATA(vec))[(pos)].key))
|
||||||
|
#define NEXTVAL(x) ( (ltree*)( (char*)(x) + INTALIGN( VARSIZE(x) ) ) )
|
||||||
|
#define SUMBIT(val) ( \
|
||||||
|
GETBITBYTE(val,0) + \
|
||||||
|
GETBITBYTE(val,1) + \
|
||||||
|
GETBITBYTE(val,2) + \
|
||||||
|
GETBITBYTE(val,3) + \
|
||||||
|
GETBITBYTE(val,4) + \
|
||||||
|
GETBITBYTE(val,5) + \
|
||||||
|
GETBITBYTE(val,6) + \
|
||||||
|
GETBITBYTE(val,7) \
|
||||||
|
)
|
||||||
|
#define WISH_F(a,b,c) (double)( -(double)(((a)-(b))*((a)-(b))*((a)-(b)))*(c) )
|
||||||
|
|
||||||
|
static void
|
||||||
|
hashing(BITVECP sign, ltree *t) {
|
||||||
|
int tlen = t->numlevel;
|
||||||
|
ltree_level *cur = LTREE_FIRST(t);
|
||||||
|
int hash;
|
||||||
|
|
||||||
|
while(tlen > 0) {
|
||||||
|
hash = crc32_sz( cur->name, cur->len );
|
||||||
|
AHASH( sign, hash );
|
||||||
|
cur = LEVEL_NEXT(cur);
|
||||||
|
tlen--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Datum
|
||||||
|
_ltree_compress(PG_FUNCTION_ARGS) {
|
||||||
|
GISTENTRY *entry = (GISTENTRY *)PG_GETARG_POINTER(0);
|
||||||
|
GISTENTRY *retval = entry;
|
||||||
|
|
||||||
|
if ( entry->leafkey ) { /* ltree */
|
||||||
|
ltree_gist *key;
|
||||||
|
ArrayType *val = (ArrayType*)DatumGetPointer(PG_DETOAST_DATUM(entry->key));
|
||||||
|
int4 len = LTG_HDRSIZE + ASIGLEN;
|
||||||
|
int num=ArrayGetNItems( ARR_NDIM(val), ARR_DIMS(val) );
|
||||||
|
ltree *item = (ltree*)ARR_DATA_PTR(val);
|
||||||
|
|
||||||
|
if ( ARR_NDIM(val) != 1 )
|
||||||
|
elog(ERROR,"Dimension of array != 1");
|
||||||
|
|
||||||
|
key = (ltree_gist*)palloc( len );
|
||||||
|
key->len = len;
|
||||||
|
key->flag = 0;
|
||||||
|
|
||||||
|
MemSet( LTG_SIGN(key), 0, sizeof(ASIGLEN) );
|
||||||
|
while( num>0 ) {
|
||||||
|
hashing(LTG_SIGN(key), item);
|
||||||
|
num--;
|
||||||
|
item = NEXTVAL(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( PointerGetDatum(val) != entry->key )
|
||||||
|
pfree(val);
|
||||||
|
|
||||||
|
retval = (GISTENTRY*)palloc( sizeof(GISTENTRY) );
|
||||||
|
gistentryinit(*retval, PointerGetDatum(key),
|
||||||
|
entry->rel, entry->page,
|
||||||
|
entry->offset, key->len, FALSE);
|
||||||
|
} else {
|
||||||
|
int4 i,len;
|
||||||
|
ltree_gist *key;
|
||||||
|
|
||||||
|
BITVECP sign = LTG_SIGN(DatumGetPointer( entry->key ) );
|
||||||
|
|
||||||
|
ALOOPBYTE(
|
||||||
|
if ( sign[i] != 0xff )
|
||||||
|
PG_RETURN_POINTER(retval);
|
||||||
|
);
|
||||||
|
|
||||||
|
len = LTG_HDRSIZE;
|
||||||
|
key = (ltree_gist*)palloc( len );
|
||||||
|
key->len = len;
|
||||||
|
key->flag = LTG_ALLTRUE;
|
||||||
|
|
||||||
|
retval = (GISTENTRY*)palloc( sizeof(GISTENTRY) );
|
||||||
|
gistentryinit(*retval, PointerGetDatum(key),
|
||||||
|
entry->rel, entry->page,
|
||||||
|
entry->offset, key->len, FALSE);
|
||||||
|
}
|
||||||
|
PG_RETURN_POINTER(retval);
|
||||||
|
}
|
||||||
|
|
||||||
|
Datum
|
||||||
|
_ltree_same(PG_FUNCTION_ARGS) {
|
||||||
|
ltree_gist* a=(ltree_gist*)PG_GETARG_POINTER(0);
|
||||||
|
ltree_gist* b=(ltree_gist*)PG_GETARG_POINTER(1);
|
||||||
|
bool *result = (bool *)PG_GETARG_POINTER(2);
|
||||||
|
|
||||||
|
if ( LTG_ISALLTRUE(a) && LTG_ISALLTRUE(b) ) {
|
||||||
|
*result = true;
|
||||||
|
} else if ( LTG_ISALLTRUE(a) ) {
|
||||||
|
*result = false;
|
||||||
|
} else if ( LTG_ISALLTRUE(b) ) {
|
||||||
|
*result = false;
|
||||||
|
} else {
|
||||||
|
int4 i;
|
||||||
|
BITVECP sa=LTG_SIGN(a), sb=LTG_SIGN(b);
|
||||||
|
*result = true;
|
||||||
|
ALOOPBYTE(
|
||||||
|
if ( sa[i] != sb[i] ) {
|
||||||
|
*result = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
PG_RETURN_POINTER(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int4
|
||||||
|
unionkey( BITVECP sbase, ltree_gist *add ) {
|
||||||
|
int4 i;
|
||||||
|
BITVECP sadd = LTG_SIGN( add );
|
||||||
|
|
||||||
|
if ( LTG_ISALLTRUE(add) )
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
ALOOPBYTE(
|
||||||
|
sbase[i] |= sadd[i];
|
||||||
|
);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Datum
|
||||||
|
_ltree_union(PG_FUNCTION_ARGS) {
|
||||||
|
bytea *entryvec = (bytea *) PG_GETARG_POINTER(0);
|
||||||
|
int *size = (int *) PG_GETARG_POINTER(1);
|
||||||
|
ABITVEC base;
|
||||||
|
int4 len = (VARSIZE(entryvec) - VARHDRSZ) / sizeof(GISTENTRY);
|
||||||
|
int4 i;
|
||||||
|
int4 flag = 0;
|
||||||
|
ltree_gist *result;
|
||||||
|
|
||||||
|
MemSet( (void*)base, 0, sizeof(ABITVEC) );
|
||||||
|
for(i=0;i<len;i++) {
|
||||||
|
if ( unionkey( base, GETENTRY(entryvec, i) ) ) {
|
||||||
|
flag = LTG_ALLTRUE;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
len = LTG_HDRSIZE + ( ( flag & LTG_ALLTRUE ) ? 0 : ASIGLEN );
|
||||||
|
result = (ltree_gist*)palloc( len );
|
||||||
|
*size = result->len = len;
|
||||||
|
result->flag = flag;
|
||||||
|
if ( ! LTG_ISALLTRUE(result) )
|
||||||
|
memcpy((void*)LTG_SIGN(result), (void*)base, sizeof( ABITVEC ) );
|
||||||
|
|
||||||
|
PG_RETURN_POINTER(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int4
|
||||||
|
sizebitvec( BITVECP sign ) {
|
||||||
|
int4 size=0, i;
|
||||||
|
ALOOPBYTE(
|
||||||
|
size += SUMBIT(*(char*)sign);
|
||||||
|
sign = (BITVECP) ( ((char*)sign) + 1 );
|
||||||
|
);
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
Datum
|
||||||
|
_ltree_penalty(PG_FUNCTION_ARGS) {
|
||||||
|
ltree_gist *origval = (ltree_gist*)DatumGetPointer( ( (GISTENTRY *)PG_GETARG_POINTER(0) )->key );
|
||||||
|
ltree_gist *newval = (ltree_gist*)DatumGetPointer( ( (GISTENTRY *)PG_GETARG_POINTER(1) )->key );
|
||||||
|
float *penalty = (float *) PG_GETARG_POINTER(2);
|
||||||
|
BITVECP orig = LTG_SIGN(origval);
|
||||||
|
|
||||||
|
if ( LTG_ISALLTRUE(origval) ) {
|
||||||
|
*penalty = 0.0;
|
||||||
|
PG_RETURN_POINTER( penalty );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( LTG_ISALLTRUE(newval) ) {
|
||||||
|
*penalty = (float) (ASIGLENBIT - sizebitvec( orig ) );
|
||||||
|
} else {
|
||||||
|
unsigned char valtmp;
|
||||||
|
BITVECP nval = LTG_SIGN(newval);
|
||||||
|
int4 i, unionsize=0;
|
||||||
|
|
||||||
|
ALOOPBYTE(
|
||||||
|
valtmp = nval[i] | orig[i];
|
||||||
|
unionsize += SUMBIT(valtmp) - SUMBIT(orig[i]);
|
||||||
|
);
|
||||||
|
*penalty = (float)unionsize;
|
||||||
|
}
|
||||||
|
PG_RETURN_POINTER( penalty );
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
OffsetNumber pos;
|
||||||
|
int4 cost;
|
||||||
|
} SPLITCOST;
|
||||||
|
|
||||||
|
static int
|
||||||
|
comparecost( const void *a, const void *b ) {
|
||||||
|
return ((SPLITCOST*)a)->cost - ((SPLITCOST*)b)->cost;
|
||||||
|
}
|
||||||
|
|
||||||
|
Datum
|
||||||
|
_ltree_picksplit(PG_FUNCTION_ARGS) {
|
||||||
|
bytea *entryvec = (bytea*) PG_GETARG_POINTER(0);
|
||||||
|
GIST_SPLITVEC *v = (GIST_SPLITVEC*) PG_GETARG_POINTER(1);
|
||||||
|
OffsetNumber k,j;
|
||||||
|
ltree_gist *datum_l, *datum_r;
|
||||||
|
ABITVEC union_l, union_r;
|
||||||
|
bool firsttime = true;
|
||||||
|
int4 size_alpha,size_beta,sizeu,sizei;
|
||||||
|
int4 size_waste, waste = 0.0;
|
||||||
|
int4 size_l, size_r;
|
||||||
|
int4 nbytes;
|
||||||
|
OffsetNumber seed_1=0, seed_2=0;
|
||||||
|
OffsetNumber *left, *right;
|
||||||
|
OffsetNumber maxoff;
|
||||||
|
BITVECP ptra, ptrb, ptrc;
|
||||||
|
int i;
|
||||||
|
unsigned char valtmp;
|
||||||
|
SPLITCOST *costvector;
|
||||||
|
ltree_gist *_k, *_j;
|
||||||
|
|
||||||
|
maxoff = ((VARSIZE(entryvec) - VARHDRSZ) / sizeof(GISTENTRY)) - 2;
|
||||||
|
nbytes = (maxoff + 2) * sizeof(OffsetNumber);
|
||||||
|
v->spl_left = (OffsetNumber *) palloc(nbytes);
|
||||||
|
v->spl_right = (OffsetNumber *) palloc(nbytes);
|
||||||
|
|
||||||
|
for (k = FirstOffsetNumber; k < maxoff; k = OffsetNumberNext(k)) {
|
||||||
|
_k = GETENTRY(entryvec,k);
|
||||||
|
for (j = OffsetNumberNext(k); j <= maxoff; j = OffsetNumberNext(j)) {
|
||||||
|
_j = GETENTRY(entryvec,j);
|
||||||
|
if ( LTG_ISALLTRUE(_k) || LTG_ISALLTRUE(_j) ) {
|
||||||
|
sizeu = ASIGLENBIT;
|
||||||
|
if ( LTG_ISALLTRUE(_k) && LTG_ISALLTRUE(_j) )
|
||||||
|
sizei = ASIGLENBIT;
|
||||||
|
else
|
||||||
|
sizei = ( LTG_ISALLTRUE(_k) ) ?
|
||||||
|
sizebitvec( LTG_SIGN(_j) ) : sizebitvec( LTG_SIGN(_k) );
|
||||||
|
} else {
|
||||||
|
sizeu = sizei = 0;
|
||||||
|
ptra = LTG_SIGN(_j);
|
||||||
|
ptrb = LTG_SIGN(_k);
|
||||||
|
/* critical section for bench !!! */
|
||||||
|
|
||||||
|
#define COUNT(pos) do { \
|
||||||
|
if ( GETBITBYTE(*(char*)ptra,pos) ) { \
|
||||||
|
sizeu++; \
|
||||||
|
if ( GETBITBYTE(*(char*)ptrb, pos) ) \
|
||||||
|
sizei++; \
|
||||||
|
} else if ( GETBITBYTE(*(char*)ptrb, pos) ) \
|
||||||
|
sizeu++; \
|
||||||
|
} while(0)
|
||||||
|
|
||||||
|
ALOOPBYTE(
|
||||||
|
COUNT(0);
|
||||||
|
COUNT(1);
|
||||||
|
COUNT(2);
|
||||||
|
COUNT(3);
|
||||||
|
COUNT(4);
|
||||||
|
COUNT(5);
|
||||||
|
COUNT(6);
|
||||||
|
COUNT(7);
|
||||||
|
ptra = (BITVECP) ( ((char*)ptra) + 1 );
|
||||||
|
ptrb = (BITVECP) ( ((char*)ptrb) + 1 );
|
||||||
|
);
|
||||||
|
}
|
||||||
|
size_waste = sizeu - sizei;
|
||||||
|
if (size_waste > waste || firsttime) {
|
||||||
|
waste = size_waste;
|
||||||
|
seed_1 = k;
|
||||||
|
seed_2 = j;
|
||||||
|
firsttime = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
left = v->spl_left;
|
||||||
|
v->spl_nleft = 0;
|
||||||
|
right = v->spl_right;
|
||||||
|
v->spl_nright = 0;
|
||||||
|
|
||||||
|
if ( seed_1 == 0 || seed_2 == 0 ) {
|
||||||
|
seed_1 = 1;
|
||||||
|
seed_2 = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* form initial .. */
|
||||||
|
if ( LTG_ISALLTRUE(GETENTRY(entryvec,seed_1)) ) {
|
||||||
|
datum_l = (ltree_gist*)palloc( LTG_HDRSIZE );
|
||||||
|
datum_l->len = LTG_HDRSIZE; datum_l->flag = LTG_ALLTRUE;
|
||||||
|
size_l = ASIGLENBIT;
|
||||||
|
} else {
|
||||||
|
datum_l = (ltree_gist*)palloc( LTG_HDRSIZE + ASIGLEN );
|
||||||
|
datum_l->len = LTG_HDRSIZE + ASIGLEN; datum_l->flag = 0;
|
||||||
|
memcpy((void*)LTG_SIGN(datum_l), (void*)LTG_SIGN(GETENTRY(entryvec,seed_1)), sizeof(ABITVEC));
|
||||||
|
size_l = sizebitvec( LTG_SIGN(datum_l) );
|
||||||
|
}
|
||||||
|
if ( LTG_ISALLTRUE(GETENTRY(entryvec,seed_2)) ) {
|
||||||
|
datum_r = (ltree_gist*)palloc( LTG_HDRSIZE );
|
||||||
|
datum_r->len = LTG_HDRSIZE; datum_r->flag = LTG_ALLTRUE;
|
||||||
|
size_r = ASIGLENBIT;
|
||||||
|
} else {
|
||||||
|
datum_r = (ltree_gist*)palloc( LTG_HDRSIZE + ASIGLEN );
|
||||||
|
datum_r->len = LTG_HDRSIZE + ASIGLEN; datum_r->flag = 0;
|
||||||
|
memcpy((void*)LTG_SIGN(datum_r), (void*)LTG_SIGN(GETENTRY(entryvec,seed_2)), sizeof(ABITVEC));
|
||||||
|
size_r = sizebitvec( LTG_SIGN(datum_r) );
|
||||||
|
}
|
||||||
|
|
||||||
|
maxoff = OffsetNumberNext(maxoff);
|
||||||
|
/* sort before ... */
|
||||||
|
costvector=(SPLITCOST*)palloc( sizeof(SPLITCOST)*maxoff );
|
||||||
|
for (j = FirstOffsetNumber; j <= maxoff; j = OffsetNumberNext(j)) {
|
||||||
|
costvector[j-1].pos = j;
|
||||||
|
_j = GETENTRY(entryvec,j);
|
||||||
|
if ( LTG_ISALLTRUE(_j) ) {
|
||||||
|
size_alpha = ASIGLENBIT - size_l;
|
||||||
|
size_beta = ASIGLENBIT - size_r;
|
||||||
|
} else {
|
||||||
|
ptra = LTG_SIGN( datum_l );
|
||||||
|
ptrb = LTG_SIGN( datum_r );
|
||||||
|
ptrc = LTG_SIGN( _j );
|
||||||
|
size_beta = size_alpha = 0;
|
||||||
|
if ( LTG_ISALLTRUE(datum_l) ) {
|
||||||
|
if ( !LTG_ISALLTRUE(datum_r) ) {
|
||||||
|
ALOOPBIT(
|
||||||
|
if ( GETBIT(ptrc,i) && ! GETBIT(ptrb,i) )
|
||||||
|
size_beta++;
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if ( LTG_ISALLTRUE(datum_r) ) {
|
||||||
|
if ( !LTG_ISALLTRUE(datum_l) ) {
|
||||||
|
ALOOPBIT(
|
||||||
|
if ( GETBIT(ptrc,i) && ! GETBIT(ptra,i) )
|
||||||
|
size_alpha++;
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ALOOPBIT(
|
||||||
|
if ( GETBIT(ptrc,i) && ! GETBIT(ptra,i) )
|
||||||
|
size_alpha++;
|
||||||
|
if ( GETBIT(ptrc,i) && ! GETBIT(ptrb,i) )
|
||||||
|
size_beta++;
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
costvector[j-1].cost = abs( size_alpha - size_beta );
|
||||||
|
}
|
||||||
|
qsort( (void*)costvector, maxoff, sizeof(SPLITCOST), comparecost );
|
||||||
|
|
||||||
|
for (k = 0; k < maxoff; k++) {
|
||||||
|
j = costvector[k].pos;
|
||||||
|
_j = GETENTRY(entryvec,j);
|
||||||
|
if ( j == seed_1 ) {
|
||||||
|
*left++ = j;
|
||||||
|
v->spl_nleft++;
|
||||||
|
continue;
|
||||||
|
} else if ( j == seed_2 ) {
|
||||||
|
*right++ = j;
|
||||||
|
v->spl_nright++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( LTG_ISALLTRUE(datum_l) || LTG_ISALLTRUE(_j) ) {
|
||||||
|
size_alpha = ASIGLENBIT;
|
||||||
|
} else {
|
||||||
|
ptra = LTG_SIGN(_j);
|
||||||
|
ptrb = LTG_SIGN(datum_l);
|
||||||
|
size_alpha = 0;
|
||||||
|
ALOOPBYTE(
|
||||||
|
valtmp = union_l[i] = ptra[i] | ptrb[i];
|
||||||
|
size_alpha += SUMBIT( valtmp );
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( LTG_ISALLTRUE(datum_r) || LTG_ISALLTRUE(_j) ) {
|
||||||
|
size_beta = ASIGLENBIT;
|
||||||
|
} else {
|
||||||
|
ptra = LTG_SIGN(_j);
|
||||||
|
ptrb = LTG_SIGN(datum_r);
|
||||||
|
size_beta = 0;
|
||||||
|
ALOOPBYTE(
|
||||||
|
valtmp = union_r[i] = ptra[i] | ptrb[i];
|
||||||
|
size_beta += SUMBIT( valtmp );
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (size_alpha - size_l < size_beta - size_r + WISH_F(v->spl_nleft, v->spl_nright, 0.1)) {
|
||||||
|
if ( ! LTG_ISALLTRUE( datum_l ) ) {
|
||||||
|
if ( size_alpha == ASIGLENBIT ) {
|
||||||
|
if ( size_alpha != size_l )
|
||||||
|
MemSet( (void*)LTG_SIGN(datum_l),0xff, sizeof(ABITVEC));
|
||||||
|
} else
|
||||||
|
memcpy( (void*)LTG_SIGN(datum_l), (void*)union_l, sizeof(ABITVEC) );
|
||||||
|
}
|
||||||
|
size_l = size_alpha;
|
||||||
|
*left++ = j;
|
||||||
|
v->spl_nleft++;
|
||||||
|
} else {
|
||||||
|
if ( ! LTG_ISALLTRUE( datum_r ) ) {
|
||||||
|
if ( size_beta == ASIGLENBIT ) {
|
||||||
|
if ( size_beta != size_r )
|
||||||
|
MemSet( (void*)LTG_SIGN(datum_r),0xff, sizeof(ABITVEC));
|
||||||
|
} else
|
||||||
|
memcpy( (void*)LTG_SIGN(datum_r), (void*)union_r, sizeof(ABITVEC) );
|
||||||
|
}
|
||||||
|
size_r = size_beta;
|
||||||
|
*right++ = j;
|
||||||
|
v->spl_nright++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*right = *left = FirstOffsetNumber;
|
||||||
|
pfree(costvector);
|
||||||
|
|
||||||
|
v->spl_ldatum = PointerGetDatum(datum_l);
|
||||||
|
v->spl_rdatum = PointerGetDatum(datum_r);
|
||||||
|
|
||||||
|
PG_RETURN_POINTER( v );
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
gist_te(ltree_gist *key, ltree* query) {
|
||||||
|
ltree_level *curq = LTREE_FIRST(query);
|
||||||
|
BITVECP sign = LTG_SIGN(key);
|
||||||
|
int qlen = query->numlevel;
|
||||||
|
unsigned int hv;
|
||||||
|
|
||||||
|
if ( LTG_ISALLTRUE(key) )
|
||||||
|
return true;
|
||||||
|
|
||||||
|
while( qlen>0 ) {
|
||||||
|
hv = crc32_sz(curq->name,curq->len);
|
||||||
|
if ( ! GETBIT( sign, AHASHVAL(hv) ) )
|
||||||
|
return false;
|
||||||
|
curq = LEVEL_NEXT(curq);
|
||||||
|
qlen--;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
checkcondition_bit(void *checkval, ITEM* val ) {
|
||||||
|
return ( FLG_CANLOOKSIGN(val->flag) ) ? GETBIT( checkval, AHASHVAL( val->val ) ) : true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
gist_qtxt(ltree_gist *key, ltxtquery* query) {
|
||||||
|
if ( LTG_ISALLTRUE(key) )
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return execute(
|
||||||
|
GETQUERY(query),
|
||||||
|
(void*)LTG_SIGN(key), false,
|
||||||
|
checkcondition_bit
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
gist_qe(ltree_gist *key, lquery* query) {
|
||||||
|
lquery_level *curq = LQUERY_FIRST(query);
|
||||||
|
BITVECP sign = LTG_SIGN(key);
|
||||||
|
int qlen = query->numlevel;
|
||||||
|
|
||||||
|
if ( LTG_ISALLTRUE(key) )
|
||||||
|
return true;
|
||||||
|
|
||||||
|
while( qlen>0 ) {
|
||||||
|
if ( curq->numvar && LQL_CANLOOKSIGN(curq) ) {
|
||||||
|
bool isexist=false;
|
||||||
|
int vlen = curq->numvar;
|
||||||
|
lquery_variant *curv = LQL_FIRST(curq);
|
||||||
|
while( vlen>0 ) {
|
||||||
|
if ( GETBIT( sign, AHASHVAL( curv->val ) ) ) {
|
||||||
|
isexist=true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
curv = LVAR_NEXT(curv);
|
||||||
|
vlen--;
|
||||||
|
}
|
||||||
|
if ( !isexist )
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
curq = LQL_NEXT(curq);
|
||||||
|
qlen--;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Datum
|
||||||
|
_ltree_consistent(PG_FUNCTION_ARGS) {
|
||||||
|
GISTENTRY *entry = (GISTENTRY*)PG_GETARG_POINTER(0);
|
||||||
|
char *query = (char*)DatumGetPointer( PG_DETOAST_DATUM(PG_GETARG_DATUM(1)) );
|
||||||
|
ltree_gist *key = (ltree_gist*)DatumGetPointer( entry->key );
|
||||||
|
StrategyNumber strategy = (StrategyNumber) PG_GETARG_UINT16(2);
|
||||||
|
bool res = false;
|
||||||
|
|
||||||
|
#ifndef assert_enabled
|
||||||
|
#define assert_enabled 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
switch( strategy ) {
|
||||||
|
case 10:
|
||||||
|
case 11:
|
||||||
|
res = gist_te(key, (ltree*)query);
|
||||||
|
break;
|
||||||
|
case 12:
|
||||||
|
case 13:
|
||||||
|
res = gist_qe(key, (lquery*)query);
|
||||||
|
break;
|
||||||
|
case 14:
|
||||||
|
case 15:
|
||||||
|
res = gist_qtxt(key, (ltxtquery*)query);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
elog(ERROR,"Unknown StrategyNumber: %d", strategy);
|
||||||
|
}
|
||||||
|
PG_RETURN_BOOL(res);
|
||||||
|
}
|
||||||
|
|
212
contrib/ltree/_ltree_op.c
Normal file
212
contrib/ltree/_ltree_op.c
Normal file
@ -0,0 +1,212 @@
|
|||||||
|
/*
|
||||||
|
* op function for ltree[]
|
||||||
|
* Teodor Sigaev <teodor@stack.net>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "ltree.h"
|
||||||
|
#include <ctype.h>
|
||||||
|
#include "utils/array.h"
|
||||||
|
|
||||||
|
PG_FUNCTION_INFO_V1(_ltree_isparent);
|
||||||
|
PG_FUNCTION_INFO_V1(_ltree_r_isparent);
|
||||||
|
PG_FUNCTION_INFO_V1(_ltree_risparent);
|
||||||
|
PG_FUNCTION_INFO_V1(_ltree_r_risparent);
|
||||||
|
PG_FUNCTION_INFO_V1(_ltq_regex);
|
||||||
|
PG_FUNCTION_INFO_V1(_ltq_rregex);
|
||||||
|
PG_FUNCTION_INFO_V1(_ltxtq_exec);
|
||||||
|
PG_FUNCTION_INFO_V1(_ltxtq_rexec);
|
||||||
|
|
||||||
|
Datum _ltree_r_isparent(PG_FUNCTION_ARGS);
|
||||||
|
Datum _ltree_r_risparent(PG_FUNCTION_ARGS);
|
||||||
|
|
||||||
|
PG_FUNCTION_INFO_V1(_ltree_extract_isparent);
|
||||||
|
PG_FUNCTION_INFO_V1(_ltree_extract_risparent);
|
||||||
|
PG_FUNCTION_INFO_V1(_ltq_extract_regex);
|
||||||
|
PG_FUNCTION_INFO_V1(_ltxtq_extract_exec);
|
||||||
|
Datum _ltree_extract_isparent(PG_FUNCTION_ARGS);
|
||||||
|
Datum _ltree_extract_risparent(PG_FUNCTION_ARGS);
|
||||||
|
Datum _ltq_extract_regex(PG_FUNCTION_ARGS);
|
||||||
|
Datum _ltxtq_extract_exec(PG_FUNCTION_ARGS);
|
||||||
|
|
||||||
|
|
||||||
|
typedef Datum (*PGCALL2)(PG_FUNCTION_ARGS);
|
||||||
|
#define NEXTVAL(x) ( (ltree*)( (char*)(x) + INTALIGN( VARSIZE(x) ) ) )
|
||||||
|
|
||||||
|
static bool
|
||||||
|
array_iterator( ArrayType *la, PGCALL2 callback, void* param, ltree ** found) {
|
||||||
|
int num=ArrayGetNItems( ARR_NDIM(la), ARR_DIMS(la));
|
||||||
|
ltree *item = (ltree*)ARR_DATA_PTR(la);
|
||||||
|
|
||||||
|
if ( ARR_NDIM(la) !=1 )
|
||||||
|
elog(ERROR,"Dimension of array != 1");
|
||||||
|
|
||||||
|
if ( found )
|
||||||
|
*found=NULL;
|
||||||
|
while( num>0 ) {
|
||||||
|
if ( DatumGetBool( DirectFunctionCall2( callback,
|
||||||
|
PointerGetDatum(item), PointerGetDatum(param) ) ) ) {
|
||||||
|
|
||||||
|
if ( found )
|
||||||
|
*found = item;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
num--;
|
||||||
|
item = NEXTVAL(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Datum
|
||||||
|
_ltree_isparent(PG_FUNCTION_ARGS) {
|
||||||
|
ArrayType *la = (ArrayType *)DatumGetPointer(PG_DETOAST_DATUM(PG_GETARG_DATUM(0)));
|
||||||
|
ltree *query = PG_GETARG_LTREE(1);
|
||||||
|
bool res = array_iterator( la, ltree_isparent, (void*)query, NULL );
|
||||||
|
PG_FREE_IF_COPY(la,0);
|
||||||
|
PG_FREE_IF_COPY(query,1);
|
||||||
|
PG_RETURN_BOOL(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
Datum
|
||||||
|
_ltree_r_isparent(PG_FUNCTION_ARGS) {
|
||||||
|
PG_RETURN_DATUM( DirectFunctionCall2( _ltree_isparent,
|
||||||
|
PG_GETARG_DATUM(1),
|
||||||
|
PG_GETARG_DATUM(0)
|
||||||
|
) );
|
||||||
|
}
|
||||||
|
|
||||||
|
Datum
|
||||||
|
_ltree_risparent(PG_FUNCTION_ARGS) {
|
||||||
|
ArrayType *la = (ArrayType *)DatumGetPointer(PG_DETOAST_DATUM(PG_GETARG_DATUM(0)));
|
||||||
|
ltree *query = PG_GETARG_LTREE(1);
|
||||||
|
bool res = array_iterator( la, ltree_risparent, (void*)query, NULL );
|
||||||
|
PG_FREE_IF_COPY(la,0);
|
||||||
|
PG_FREE_IF_COPY(query,1);
|
||||||
|
PG_RETURN_BOOL(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
Datum
|
||||||
|
_ltree_r_risparent(PG_FUNCTION_ARGS) {
|
||||||
|
PG_RETURN_DATUM( DirectFunctionCall2( _ltree_risparent,
|
||||||
|
PG_GETARG_DATUM(1),
|
||||||
|
PG_GETARG_DATUM(0)
|
||||||
|
) );
|
||||||
|
}
|
||||||
|
|
||||||
|
Datum
|
||||||
|
_ltq_regex(PG_FUNCTION_ARGS) {
|
||||||
|
ArrayType *la = (ArrayType *)DatumGetPointer(PG_DETOAST_DATUM(PG_GETARG_DATUM(0)));
|
||||||
|
lquery *query = PG_GETARG_LQUERY(1);
|
||||||
|
bool res = array_iterator( la, ltq_regex, (void*)query, NULL );
|
||||||
|
PG_FREE_IF_COPY(la,0);
|
||||||
|
PG_FREE_IF_COPY(query,1);
|
||||||
|
PG_RETURN_BOOL(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
Datum
|
||||||
|
_ltq_rregex(PG_FUNCTION_ARGS) {
|
||||||
|
PG_RETURN_DATUM( DirectFunctionCall2( _ltq_regex,
|
||||||
|
PG_GETARG_DATUM(1),
|
||||||
|
PG_GETARG_DATUM(0)
|
||||||
|
) );
|
||||||
|
}
|
||||||
|
|
||||||
|
Datum
|
||||||
|
_ltxtq_exec(PG_FUNCTION_ARGS) {
|
||||||
|
ArrayType *la = (ArrayType *)DatumGetPointer(PG_DETOAST_DATUM(PG_GETARG_DATUM(0)));
|
||||||
|
ltxtquery *query = PG_GETARG_LTXTQUERY(1);
|
||||||
|
bool res = array_iterator( la, ltxtq_exec, (void*)query, NULL );
|
||||||
|
PG_FREE_IF_COPY(la,0);
|
||||||
|
PG_FREE_IF_COPY(query,1);
|
||||||
|
PG_RETURN_BOOL(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
Datum
|
||||||
|
_ltxtq_rexec(PG_FUNCTION_ARGS) {
|
||||||
|
PG_RETURN_DATUM( DirectFunctionCall2( _ltxtq_exec,
|
||||||
|
PG_GETARG_DATUM(1),
|
||||||
|
PG_GETARG_DATUM(0)
|
||||||
|
) );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Datum
|
||||||
|
_ltree_extract_isparent(PG_FUNCTION_ARGS) {
|
||||||
|
ArrayType *la = (ArrayType *)DatumGetPointer(PG_DETOAST_DATUM(PG_GETARG_DATUM(0)));
|
||||||
|
ltree *query = PG_GETARG_LTREE(1);
|
||||||
|
ltree *found,*item;
|
||||||
|
|
||||||
|
if ( !array_iterator( la, ltree_isparent, (void*)query, &found ) ) {
|
||||||
|
PG_FREE_IF_COPY(la,0);
|
||||||
|
PG_FREE_IF_COPY(query,1);
|
||||||
|
PG_RETURN_NULL();
|
||||||
|
}
|
||||||
|
|
||||||
|
item = (ltree*)palloc( found->len );
|
||||||
|
memcpy( item, found, found->len );
|
||||||
|
|
||||||
|
PG_FREE_IF_COPY(la,0);
|
||||||
|
PG_FREE_IF_COPY(query,1);
|
||||||
|
PG_RETURN_POINTER(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
Datum
|
||||||
|
_ltree_extract_risparent(PG_FUNCTION_ARGS) {
|
||||||
|
ArrayType *la = (ArrayType *)DatumGetPointer(PG_DETOAST_DATUM(PG_GETARG_DATUM(0)));
|
||||||
|
ltree *query = PG_GETARG_LTREE(1);
|
||||||
|
ltree *found,*item;
|
||||||
|
|
||||||
|
if ( !array_iterator( la, ltree_risparent, (void*)query, &found ) ) {
|
||||||
|
PG_FREE_IF_COPY(la,0);
|
||||||
|
PG_FREE_IF_COPY(query,1);
|
||||||
|
PG_RETURN_NULL();
|
||||||
|
}
|
||||||
|
|
||||||
|
item = (ltree*)palloc( found->len );
|
||||||
|
memcpy( item, found, found->len );
|
||||||
|
|
||||||
|
PG_FREE_IF_COPY(la,0);
|
||||||
|
PG_FREE_IF_COPY(query,1);
|
||||||
|
PG_RETURN_POINTER(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
Datum
|
||||||
|
_ltq_extract_regex(PG_FUNCTION_ARGS) {
|
||||||
|
ArrayType *la = (ArrayType *)DatumGetPointer(PG_DETOAST_DATUM(PG_GETARG_DATUM(0)));
|
||||||
|
lquery *query = PG_GETARG_LQUERY(1);
|
||||||
|
ltree *found,*item;
|
||||||
|
|
||||||
|
if ( !array_iterator( la, ltq_regex, (void*)query, &found ) ) {
|
||||||
|
PG_FREE_IF_COPY(la,0);
|
||||||
|
PG_FREE_IF_COPY(query,1);
|
||||||
|
PG_RETURN_NULL();
|
||||||
|
}
|
||||||
|
|
||||||
|
item = (ltree*)palloc( found->len );
|
||||||
|
memcpy( item, found, found->len );
|
||||||
|
|
||||||
|
PG_FREE_IF_COPY(la,0);
|
||||||
|
PG_FREE_IF_COPY(query,1);
|
||||||
|
PG_RETURN_POINTER(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
Datum
|
||||||
|
_ltxtq_extract_exec(PG_FUNCTION_ARGS) {
|
||||||
|
ArrayType *la = (ArrayType *)DatumGetPointer(PG_DETOAST_DATUM(PG_GETARG_DATUM(0)));
|
||||||
|
ltxtquery *query = PG_GETARG_LTXTQUERY(1);
|
||||||
|
ltree *found,*item;
|
||||||
|
|
||||||
|
if ( !array_iterator( la, ltxtq_exec, (void*)query, &found ) ) {
|
||||||
|
PG_FREE_IF_COPY(la,0);
|
||||||
|
PG_FREE_IF_COPY(query,1);
|
||||||
|
PG_RETURN_NULL();
|
||||||
|
}
|
||||||
|
|
||||||
|
item = (ltree*)palloc( found->len );
|
||||||
|
memcpy( item, found, found->len );
|
||||||
|
|
||||||
|
PG_FREE_IF_COPY(la,0);
|
||||||
|
PG_FREE_IF_COPY(query,1);
|
||||||
|
PG_RETURN_POINTER(item);
|
||||||
|
}
|
||||||
|
|
110
contrib/ltree/crc32.c
Normal file
110
contrib/ltree/crc32.c
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
/* Both POSIX and CRC32 checksums */
|
||||||
|
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
|
||||||
|
#ifdef LOWER_NODE
|
||||||
|
#include <ctype.h>
|
||||||
|
#define TOLOWER(x) tolower(x)
|
||||||
|
#else
|
||||||
|
#define TOLOWER(x) (x)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "crc32.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This code implements the AUTODIN II polynomial
|
||||||
|
* The variable corresponding to the macro argument "crc" should
|
||||||
|
* be an unsigned long.
|
||||||
|
* Oroginal code by Spencer Garrett <srg@quick.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define _CRC32_(crc, ch) (crc = (crc >> 8) ^ crc32tab[(crc ^ (ch)) & 0xff])
|
||||||
|
|
||||||
|
/* generated using the AUTODIN II polynomial
|
||||||
|
* x^32 + x^26 + x^23 + x^22 + x^16 +
|
||||||
|
* x^12 + x^11 + x^10 + x^8 + x^7 + x^5 + x^4 + x^2 + x^1 + 1
|
||||||
|
*/
|
||||||
|
|
||||||
|
static const unsigned int crc32tab[256] = {
|
||||||
|
0x00000000, 0x77073096, 0xee0e612c, 0x990951ba,
|
||||||
|
0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3,
|
||||||
|
0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
|
||||||
|
0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91,
|
||||||
|
0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de,
|
||||||
|
0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
|
||||||
|
0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec,
|
||||||
|
0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5,
|
||||||
|
0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
|
||||||
|
0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b,
|
||||||
|
0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940,
|
||||||
|
0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
|
||||||
|
0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116,
|
||||||
|
0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f,
|
||||||
|
0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
|
||||||
|
0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d,
|
||||||
|
0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a,
|
||||||
|
0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
|
||||||
|
0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818,
|
||||||
|
0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01,
|
||||||
|
0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
|
||||||
|
0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457,
|
||||||
|
0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c,
|
||||||
|
0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
|
||||||
|
0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2,
|
||||||
|
0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb,
|
||||||
|
0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
|
||||||
|
0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9,
|
||||||
|
0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086,
|
||||||
|
0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
|
||||||
|
0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4,
|
||||||
|
0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad,
|
||||||
|
0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
|
||||||
|
0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683,
|
||||||
|
0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8,
|
||||||
|
0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
|
||||||
|
0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe,
|
||||||
|
0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7,
|
||||||
|
0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
|
||||||
|
0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5,
|
||||||
|
0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252,
|
||||||
|
0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
|
||||||
|
0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60,
|
||||||
|
0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79,
|
||||||
|
0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
|
||||||
|
0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f,
|
||||||
|
0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04,
|
||||||
|
0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
|
||||||
|
0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a,
|
||||||
|
0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713,
|
||||||
|
0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
|
||||||
|
0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21,
|
||||||
|
0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e,
|
||||||
|
0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
|
||||||
|
0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c,
|
||||||
|
0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45,
|
||||||
|
0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
|
||||||
|
0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db,
|
||||||
|
0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0,
|
||||||
|
0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
|
||||||
|
0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6,
|
||||||
|
0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf,
|
||||||
|
0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
|
||||||
|
0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d,
|
||||||
|
};
|
||||||
|
|
||||||
|
unsigned int
|
||||||
|
crc32_sz(char *buf, int size)
|
||||||
|
{
|
||||||
|
unsigned int crc = ~0;
|
||||||
|
char *p;
|
||||||
|
int len,
|
||||||
|
nr;
|
||||||
|
|
||||||
|
len = 0;
|
||||||
|
nr = size;
|
||||||
|
for (len += nr, p = buf; nr--; ++p)
|
||||||
|
_CRC32_(crc, TOLOWER(*p));
|
||||||
|
return ~crc;
|
||||||
|
}
|
10
contrib/ltree/crc32.h
Normal file
10
contrib/ltree/crc32.h
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
#ifndef _CRC32_H
|
||||||
|
#define _CRC32_H
|
||||||
|
|
||||||
|
/* Returns crc32 of data block */
|
||||||
|
extern unsigned int crc32_sz(char *buf, int size);
|
||||||
|
|
||||||
|
/* Returns crc32 of null-terminated string */
|
||||||
|
#define crc32(buf) crc32_sz((buf),strlen(buf))
|
||||||
|
|
||||||
|
#endif
|
1000
contrib/ltree/data/_ltree.data
Normal file
1000
contrib/ltree/data/_ltree.data
Normal file
File diff suppressed because it is too large
Load Diff
1006
contrib/ltree/data/ltree.data
Normal file
1006
contrib/ltree/data/ltree.data
Normal file
File diff suppressed because it is too large
Load Diff
7371
contrib/ltree/expected/ltree.out
Normal file
7371
contrib/ltree/expected/ltree.out
Normal file
File diff suppressed because it is too large
Load Diff
240
contrib/ltree/lquery_op.c
Normal file
240
contrib/ltree/lquery_op.c
Normal file
@ -0,0 +1,240 @@
|
|||||||
|
/*
|
||||||
|
* op function for ltree and lquery
|
||||||
|
* Teodor Sigaev <teodor@stack.net>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "ltree.h"
|
||||||
|
#include <ctype.h>
|
||||||
|
|
||||||
|
PG_FUNCTION_INFO_V1(ltq_regex);
|
||||||
|
PG_FUNCTION_INFO_V1(ltq_rregex);
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
lquery_level *q;
|
||||||
|
int nq;
|
||||||
|
ltree_level *t;
|
||||||
|
int nt;
|
||||||
|
int posq;
|
||||||
|
int post;
|
||||||
|
} FieldNot;
|
||||||
|
|
||||||
|
static char *
|
||||||
|
getlexem(char *start, char *end, int *len) {
|
||||||
|
char *ptr;
|
||||||
|
|
||||||
|
while( start<end && *start == '_' )
|
||||||
|
start++;
|
||||||
|
|
||||||
|
ptr = start;
|
||||||
|
if ( ptr == end )
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
while( ptr < end && *ptr != '_')
|
||||||
|
ptr++;
|
||||||
|
|
||||||
|
*len = ptr - start;
|
||||||
|
return start;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
compare_subnode( ltree_level *t, char *qn, int len, int (*cmpptr)(const char *,const char *,size_t), bool anyend ) {
|
||||||
|
char *endt = t->name + t->len;
|
||||||
|
char *endq = qn + len;
|
||||||
|
char *tn;
|
||||||
|
int lent,lenq;
|
||||||
|
bool isok;
|
||||||
|
|
||||||
|
while( (qn=getlexem(qn,endq,&lenq)) != NULL ) {
|
||||||
|
tn=t->name;
|
||||||
|
isok = false;
|
||||||
|
while( (tn=getlexem(tn,endt,&lent)) != NULL ) {
|
||||||
|
if (
|
||||||
|
(
|
||||||
|
lent == lenq ||
|
||||||
|
( lent > lenq && anyend )
|
||||||
|
) &&
|
||||||
|
(*cmpptr)(qn,tn,lenq) == 0 ) {
|
||||||
|
|
||||||
|
isok = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
tn += lent;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( !isok )
|
||||||
|
return false;
|
||||||
|
qn += lenq;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
checkLevel( lquery_level *curq, ltree_level *curt ) {
|
||||||
|
int (*cmpptr)(const char *,const char *,size_t);
|
||||||
|
lquery_variant *curvar = LQL_FIRST(curq);
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for(i=0;i<curq->numvar;i++) {
|
||||||
|
cmpptr = ( curvar->flag & LVAR_INCASE ) ? strncasecmp : strncmp;
|
||||||
|
|
||||||
|
if ( curvar->flag & LVAR_SUBLEXEM ) {
|
||||||
|
if ( compare_subnode(curt, curvar->name, curvar->len, cmpptr, (curvar->flag & LVAR_ANYEND) ) )
|
||||||
|
return true;
|
||||||
|
} else if (
|
||||||
|
(
|
||||||
|
curvar->len == curt->len ||
|
||||||
|
( curt->len > curvar->len && (curvar->flag & LVAR_ANYEND) )
|
||||||
|
) &&
|
||||||
|
(*cmpptr)( curvar->name, curt->name, curvar->len) == 0 ) {
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
curvar = LVAR_NEXT(curvar);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
void
|
||||||
|
printFieldNot(FieldNot *fn ) {
|
||||||
|
while(fn->q) {
|
||||||
|
elog(NOTICE,"posQ:%d lenQ:%d posT:%d lenT:%d", fn->posq,fn->nq,fn->post,fn->nt);
|
||||||
|
fn++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
static bool
|
||||||
|
checkCond( lquery_level *curq, int query_numlevel, ltree_level *curt, int tree_numlevel, FieldNot *ptr ) {
|
||||||
|
uint32 low_pos=0,high_pos=0,cur_tpos=0;
|
||||||
|
int tlen = tree_numlevel, qlen = query_numlevel;
|
||||||
|
int isok;
|
||||||
|
lquery_level *prevq=NULL;
|
||||||
|
ltree_level *prevt=NULL;
|
||||||
|
|
||||||
|
while( tlen >0 && qlen>0 ) {
|
||||||
|
if ( curq->numvar ) {
|
||||||
|
prevt = curt;
|
||||||
|
while ( cur_tpos < low_pos ) {
|
||||||
|
curt = LEVEL_NEXT(curt);
|
||||||
|
tlen--;
|
||||||
|
cur_tpos++;
|
||||||
|
if ( tlen==0 )
|
||||||
|
return false;
|
||||||
|
if ( ptr && ptr->q )
|
||||||
|
ptr->nt++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( ptr && curq->flag & LQL_NOT ) {
|
||||||
|
if ( !(prevq && prevq->numvar == 0) )
|
||||||
|
prevq = curq;
|
||||||
|
if ( ptr->q == NULL ) {
|
||||||
|
ptr->t = prevt;
|
||||||
|
ptr->q = prevq;
|
||||||
|
ptr->nt=1;
|
||||||
|
ptr->nq=1 + ( (prevq==curq) ? 0 : 1 );
|
||||||
|
ptr->posq = query_numlevel - qlen - ( (prevq==curq) ? 0 : 1 );
|
||||||
|
ptr->post = cur_tpos;
|
||||||
|
} else {
|
||||||
|
ptr->nt++;
|
||||||
|
ptr->nq++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( qlen == 1 && ptr->q->numvar==0 )
|
||||||
|
ptr->nt = tree_numlevel - ptr->post;
|
||||||
|
curt = LEVEL_NEXT(curt);
|
||||||
|
tlen--;
|
||||||
|
cur_tpos++;
|
||||||
|
if ( high_pos < cur_tpos )
|
||||||
|
high_pos++;
|
||||||
|
} else {
|
||||||
|
isok = false;
|
||||||
|
while( cur_tpos <= high_pos && tlen > 0 && !isok) {
|
||||||
|
isok = checkLevel(curq, curt);
|
||||||
|
curt = LEVEL_NEXT(curt);
|
||||||
|
tlen--;
|
||||||
|
cur_tpos++;
|
||||||
|
if ( !isok && ptr )
|
||||||
|
ptr->nt++;
|
||||||
|
}
|
||||||
|
if ( !isok )
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (ptr && ptr->q) {
|
||||||
|
if ( checkCond(ptr->q,ptr->nq,ptr->t,ptr->nt,NULL) )
|
||||||
|
return false;
|
||||||
|
ptr->q = NULL;
|
||||||
|
}
|
||||||
|
low_pos=cur_tpos; high_pos=cur_tpos;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
low_pos = cur_tpos + curq->low;
|
||||||
|
high_pos = cur_tpos + curq->high;
|
||||||
|
if ( ptr && ptr->q ) {
|
||||||
|
ptr->nq++;
|
||||||
|
if ( qlen==1 )
|
||||||
|
ptr->nt = tree_numlevel - ptr->post;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
prevq = curq;
|
||||||
|
curq = LQL_NEXT(curq);
|
||||||
|
qlen--;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( low_pos > tree_numlevel || tree_numlevel > high_pos )
|
||||||
|
return false;
|
||||||
|
|
||||||
|
while( qlen>0 ) {
|
||||||
|
if ( curq->numvar ) {
|
||||||
|
if ( ! (curq->flag & LQL_NOT) )
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
low_pos = cur_tpos + curq->low;
|
||||||
|
high_pos = cur_tpos + curq->high;
|
||||||
|
}
|
||||||
|
|
||||||
|
curq = LQL_NEXT(curq);
|
||||||
|
qlen--;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( low_pos > tree_numlevel || tree_numlevel > high_pos )
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if ( ptr && ptr->q && checkCond(ptr->q,ptr->nq,ptr->t,ptr->nt,NULL) )
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Datum
|
||||||
|
ltq_regex(PG_FUNCTION_ARGS) {
|
||||||
|
ltree *tree = PG_GETARG_LTREE(0);
|
||||||
|
lquery *query = PG_GETARG_LQUERY(1);
|
||||||
|
bool res= false;
|
||||||
|
|
||||||
|
if ( query->flag & LQUERY_HASNOT ) {
|
||||||
|
FieldNot fn;
|
||||||
|
|
||||||
|
fn.q=NULL;
|
||||||
|
|
||||||
|
res = checkCond( LQUERY_FIRST(query), query->numlevel,
|
||||||
|
LTREE_FIRST(tree), tree->numlevel, &fn );
|
||||||
|
} else {
|
||||||
|
res = checkCond( LQUERY_FIRST(query), query->numlevel,
|
||||||
|
LTREE_FIRST(tree), tree->numlevel, NULL );
|
||||||
|
}
|
||||||
|
|
||||||
|
PG_FREE_IF_COPY(tree,0);
|
||||||
|
PG_FREE_IF_COPY(query,1);
|
||||||
|
PG_RETURN_BOOL(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
Datum
|
||||||
|
ltq_rregex(PG_FUNCTION_ARGS) {
|
||||||
|
PG_RETURN_DATUM( DirectFunctionCall2( ltq_regex,
|
||||||
|
PG_GETARG_DATUM(1),
|
||||||
|
PG_GETARG_DATUM(0)
|
||||||
|
) );
|
||||||
|
}
|
251
contrib/ltree/ltree.h
Normal file
251
contrib/ltree/ltree.h
Normal file
@ -0,0 +1,251 @@
|
|||||||
|
#ifndef __LTREE_H__
|
||||||
|
#define __LTREE_H__
|
||||||
|
|
||||||
|
#include "postgres.h"
|
||||||
|
#include "utils/elog.h"
|
||||||
|
#include "utils/palloc.h"
|
||||||
|
#include "utils/builtins.h"
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint8 len;
|
||||||
|
char name[1];
|
||||||
|
} ltree_level;
|
||||||
|
|
||||||
|
#define LEVEL_HDRSIZE (sizeof(uint8))
|
||||||
|
#define LEVEL_NEXT(x) ( (ltree_level*)( ((char*)(x)) + ((ltree_level*)(x))->len + LEVEL_HDRSIZE ) )
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int32 len;
|
||||||
|
uint16 numlevel;
|
||||||
|
char data[1];
|
||||||
|
} ltree;
|
||||||
|
|
||||||
|
#define LTREE_HDRSIZE ( sizeof(int32) + sizeof(uint16) )
|
||||||
|
#define LTREE_FIRST(x) ( (ltree_level*)( ((ltree*)(x))->data ) )
|
||||||
|
|
||||||
|
|
||||||
|
/* lquery */
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int4 val;
|
||||||
|
uint8 len;
|
||||||
|
uint8 flag;
|
||||||
|
char name[1];
|
||||||
|
} lquery_variant;
|
||||||
|
|
||||||
|
#define LVAR_HDRSIZE (sizeof(uint8)*2 + sizeof(int4))
|
||||||
|
#define LVAR_NEXT(x) ( (lquery_variant*)( ((char*)(x)) + ((lquery_variant*)(x))->len + LVAR_HDRSIZE ) )
|
||||||
|
|
||||||
|
#define LVAR_ANYEND 0x01
|
||||||
|
#define LVAR_INCASE 0x02
|
||||||
|
#define LVAR_SUBLEXEM 0x04
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint16 totallen;
|
||||||
|
uint16 flag;
|
||||||
|
uint16 numvar;
|
||||||
|
uint16 low;
|
||||||
|
uint16 high;
|
||||||
|
char variants[1];
|
||||||
|
} lquery_level;
|
||||||
|
|
||||||
|
#define LQL_HDRSIZE ( sizeof(uint16)*5 )
|
||||||
|
#define LQL_NEXT(x) ( (lquery_level*)( ((char*)(x)) + ((lquery_level*)(x))->totallen ) )
|
||||||
|
#define LQL_FIRST(x) ( (lquery_variant*)( ((lquery_level*)(x))->variants ) )
|
||||||
|
|
||||||
|
#define LQL_NOT 0x10
|
||||||
|
#ifdef LOWER_NODE
|
||||||
|
#define FLG_CANLOOKSIGN(x) ( ( (x) & ( LQL_NOT | LVAR_ANYEND | LVAR_SUBLEXEM ) ) == 0 )
|
||||||
|
#else
|
||||||
|
#define FLG_CANLOOKSIGN(x) ( ( (x) & ( LQL_NOT | LVAR_ANYEND | LVAR_SUBLEXEM | LVAR_INCASE ) ) == 0 )
|
||||||
|
#endif
|
||||||
|
#define LQL_CANLOOKSIGN(x) FLG_CANLOOKSIGN( ((lquery_level*)(x))->flag )
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int32 len;
|
||||||
|
uint16 numlevel;
|
||||||
|
uint16 firstgood;
|
||||||
|
uint16 flag;
|
||||||
|
char data[1];
|
||||||
|
} lquery;
|
||||||
|
|
||||||
|
#define LQUERY_HDRSIZE ( sizeof(int32) + 3*sizeof(uint16) )
|
||||||
|
#define LQUERY_FIRST(x) ( (lquery_level*)( ((lquery*)(x))->data ) )
|
||||||
|
|
||||||
|
#define LQUERY_HASNOT 0x01
|
||||||
|
|
||||||
|
#ifndef max
|
||||||
|
#define max(a,b) ((a) > (b) ? (a) : (b))
|
||||||
|
#endif
|
||||||
|
#ifndef min
|
||||||
|
#define min(a,b) ((a) <= (b) ? (a) : (b))
|
||||||
|
#endif
|
||||||
|
#ifndef abs
|
||||||
|
#define abs(a) ((a) < (0) ? -(a) : (a))
|
||||||
|
#endif
|
||||||
|
#define ISALNUM(x) ( isalnum(x) || (x) == '_' )
|
||||||
|
|
||||||
|
/* full text query */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* item in polish notation with back link
|
||||||
|
* to left operand
|
||||||
|
*/
|
||||||
|
typedef struct ITEM
|
||||||
|
{
|
||||||
|
int2 type;
|
||||||
|
int2 left;
|
||||||
|
int4 val;
|
||||||
|
uint8 flag;
|
||||||
|
/* user-friendly value */
|
||||||
|
uint8 length;
|
||||||
|
uint16 distance;
|
||||||
|
} ITEM;
|
||||||
|
|
||||||
|
/*
|
||||||
|
*Storage:
|
||||||
|
* (len)(size)(array of ITEM)(array of operand in user-friendly form)
|
||||||
|
*/
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
int4 len;
|
||||||
|
int4 size;
|
||||||
|
char data[1];
|
||||||
|
} ltxtquery;
|
||||||
|
|
||||||
|
#define HDRSIZEQT ( 2*sizeof(int4) )
|
||||||
|
#define COMPUTESIZE(size,lenofoperand) ( HDRSIZEQT + size * sizeof(ITEM) + lenofoperand )
|
||||||
|
#define GETQUERY(x) (ITEM*)( (char*)(x)+HDRSIZEQT )
|
||||||
|
#define GETOPERAND(x) ( (char*)GETQUERY(x) + ((ltxtquery*)x)->size * sizeof(ITEM) )
|
||||||
|
|
||||||
|
#define ISOPERATOR(x) ( (x)=='!' || (x)=='&' || (x)=='|' || (x)=='(' || (x)==')' )
|
||||||
|
|
||||||
|
#define END 0
|
||||||
|
#define ERR 1
|
||||||
|
#define VAL 2
|
||||||
|
#define OPR 3
|
||||||
|
#define OPEN 4
|
||||||
|
#define CLOSE 5
|
||||||
|
#define VALTRUE 6 /* for stop words */
|
||||||
|
#define VALFALSE 7
|
||||||
|
|
||||||
|
|
||||||
|
/* use in array iterator */
|
||||||
|
Datum ltree_isparent(PG_FUNCTION_ARGS);
|
||||||
|
Datum ltree_risparent(PG_FUNCTION_ARGS);
|
||||||
|
Datum ltq_regex(PG_FUNCTION_ARGS);
|
||||||
|
Datum ltq_rregex(PG_FUNCTION_ARGS);
|
||||||
|
Datum ltxtq_exec(PG_FUNCTION_ARGS);
|
||||||
|
Datum ltxtq_rexec(PG_FUNCTION_ARGS);
|
||||||
|
Datum _ltq_regex(PG_FUNCTION_ARGS);
|
||||||
|
Datum _ltq_rregex(PG_FUNCTION_ARGS);
|
||||||
|
Datum _ltxtq_exec(PG_FUNCTION_ARGS);
|
||||||
|
Datum _ltxtq_rexec(PG_FUNCTION_ARGS);
|
||||||
|
Datum _ltree_isparent(PG_FUNCTION_ARGS);
|
||||||
|
Datum _ltree_risparent(PG_FUNCTION_ARGS);
|
||||||
|
|
||||||
|
/* Concatenation functions */
|
||||||
|
Datum ltree_addltree(PG_FUNCTION_ARGS);
|
||||||
|
Datum ltree_addtext(PG_FUNCTION_ARGS);
|
||||||
|
Datum ltree_textadd(PG_FUNCTION_ARGS);
|
||||||
|
|
||||||
|
/* Util function */
|
||||||
|
Datum ltree_in(PG_FUNCTION_ARGS);
|
||||||
|
|
||||||
|
bool execute(ITEM * curitem, void *checkval,
|
||||||
|
bool calcnot, bool (*chkcond) (void *checkval, ITEM * val));
|
||||||
|
|
||||||
|
int ltree_compare(const ltree *a, const ltree *b);
|
||||||
|
bool inner_isparent(const ltree *c, const ltree *p);
|
||||||
|
bool compare_subnode( ltree_level *t, char *q, int len,
|
||||||
|
int (*cmpptr)(const char *,const char *,size_t), bool anyend );
|
||||||
|
|
||||||
|
#define PG_GETARG_LTREE(x) ((ltree*)DatumGetPointer(PG_DETOAST_DATUM(PG_GETARG_DATUM(x))))
|
||||||
|
#define PG_GETARG_LQUERY(x) ((lquery*)DatumGetPointer(PG_DETOAST_DATUM(PG_GETARG_DATUM(x))))
|
||||||
|
#define PG_GETARG_LTXTQUERY(x) ((ltxtquery*)DatumGetPointer(PG_DETOAST_DATUM(PG_GETARG_DATUM(x))))
|
||||||
|
|
||||||
|
/* GiST support for ltree */
|
||||||
|
|
||||||
|
#define BITBYTE 8
|
||||||
|
#define SIGLENINT 8
|
||||||
|
#define SIGLEN ( sizeof(int4)*SIGLENINT )
|
||||||
|
#define SIGLENBIT (SIGLEN*BITBYTE)
|
||||||
|
typedef unsigned char BITVEC[SIGLEN];
|
||||||
|
typedef unsigned char *BITVECP;
|
||||||
|
|
||||||
|
#define LOOPBYTE(a) \
|
||||||
|
for(i=0;i<SIGLEN;i++) {\
|
||||||
|
a;\
|
||||||
|
}
|
||||||
|
#define LOOPBIT(a) \
|
||||||
|
for(i=0;i<SIGLENBIT;i++) {\
|
||||||
|
a;\
|
||||||
|
}
|
||||||
|
|
||||||
|
#define GETBYTE(x,i) ( *( (BITVECP)(x) + (int)( (i) / BITBYTE ) ) )
|
||||||
|
#define GETBITBYTE(x,i) ( ((unsigned char)(x)) >> i & 0x01 )
|
||||||
|
#define CLRBIT(x,i) GETBYTE(x,i) &= ~( 0x01 << ( (i) % BITBYTE ) )
|
||||||
|
#define SETBIT(x,i) GETBYTE(x,i) |= ( 0x01 << ( (i) % BITBYTE ) )
|
||||||
|
#define GETBIT(x,i) ( (GETBYTE(x,i) >> ( (i) % BITBYTE )) & 0x01 )
|
||||||
|
|
||||||
|
#define HASHVAL(val) (((unsigned int)(val)) % SIGLENBIT)
|
||||||
|
#define HASH(sign, val) SETBIT((sign), HASHVAL(val))
|
||||||
|
|
||||||
|
/*
|
||||||
|
* type of index key for ltree. Tree are combined B-Tree and R-Tree
|
||||||
|
* Storage:
|
||||||
|
* Leaf pages
|
||||||
|
* (len)(flag)(ltree)
|
||||||
|
* Non-Leaf
|
||||||
|
* (len)(flag)(sign)(left_ltree)(right_ltree)
|
||||||
|
* ALLTRUE: (len)(flag)(left_ltree)(right_ltree)
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int4 len;
|
||||||
|
uint32 flag;
|
||||||
|
char data[1];
|
||||||
|
} ltree_gist;
|
||||||
|
|
||||||
|
#define LTG_ONENODE 0x01
|
||||||
|
#define LTG_ALLTRUE 0x02
|
||||||
|
#define LTG_NORIGHT 0x04
|
||||||
|
|
||||||
|
#define LTG_HDRSIZE ( sizeof(int4) + sizeof(uint32) )
|
||||||
|
#define LTG_SIGN(x) ( (BITVECP)( ((ltree_gist*)(x))->data ) )
|
||||||
|
#define LTG_NODE(x) ( (ltree*)( ((ltree_gist*)(x))->data ) )
|
||||||
|
#define LTG_ISONENODE(x) ( ((ltree_gist*)(x))->flag & LTG_ONENODE )
|
||||||
|
#define LTG_ISALLTRUE(x) ( ((ltree_gist*)(x))->flag & LTG_ALLTRUE )
|
||||||
|
#define LTG_ISNORIGHT(x) ( ((ltree_gist*)(x))->flag & LTG_NORIGHT )
|
||||||
|
#define LTG_LNODE(x) ( (ltree*)( ( (char*)( ((ltree_gist*)(x))->data ) ) + ( LTG_ISALLTRUE(x) ? 0 : SIGLEN ) ) )
|
||||||
|
#define LTG_RENODE(x) ( (ltree*)( ((char*)LTG_LNODE(x)) + LTG_LNODE(x)->len ) )
|
||||||
|
#define LTG_RNODE(x) ( LTG_ISNORIGHT(x) ? LTG_LNODE(x) : LTG_RENODE(x) )
|
||||||
|
|
||||||
|
#define LTG_GETLNODE(x) ( LTG_ISONENODE(x) ? LTG_NODE(x) : LTG_LNODE(x) )
|
||||||
|
#define LTG_GETRNODE(x) ( LTG_ISONENODE(x) ? LTG_NODE(x) : LTG_RNODE(x) )
|
||||||
|
|
||||||
|
|
||||||
|
/* GiST support for ltree[] */
|
||||||
|
|
||||||
|
#define ASIGLENINT (2*SIGLENINT)
|
||||||
|
#define ASIGLEN (sizeof(int4)*ASIGLENINT)
|
||||||
|
#define ASIGLENBIT (ASIGLEN*BITBYTE)
|
||||||
|
typedef unsigned char ABITVEC[ASIGLEN];
|
||||||
|
|
||||||
|
#define ALOOPBYTE(a) \
|
||||||
|
for(i=0;i<ASIGLEN;i++) {\
|
||||||
|
a;\
|
||||||
|
}
|
||||||
|
#define ALOOPBIT(a) \
|
||||||
|
for(i=0;i<ASIGLENBIT;i++) {\
|
||||||
|
a;\
|
||||||
|
}
|
||||||
|
|
||||||
|
#define AHASHVAL(val) (((unsigned int)(val)) % ASIGLENBIT)
|
||||||
|
#define AHASH(sign, val) SETBIT((sign), AHASHVAL(val))
|
||||||
|
|
||||||
|
/* type of key is the same to ltree_gist */
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
849
contrib/ltree/ltree.sql.in
Normal file
849
contrib/ltree/ltree.sql.in
Normal file
@ -0,0 +1,849 @@
|
|||||||
|
BEGIN;
|
||||||
|
|
||||||
|
CREATE FUNCTION ltree_in(opaque)
|
||||||
|
RETURNS opaque
|
||||||
|
AS 'MODULE_PATHNAME'
|
||||||
|
LANGUAGE 'c' with (isstrict);
|
||||||
|
|
||||||
|
CREATE FUNCTION ltree_out(opaque)
|
||||||
|
RETURNS opaque
|
||||||
|
AS 'MODULE_PATHNAME'
|
||||||
|
LANGUAGE 'c' with (isstrict);
|
||||||
|
|
||||||
|
CREATE TYPE ltree (
|
||||||
|
internallength = -1,
|
||||||
|
input = ltree_in,
|
||||||
|
output = ltree_out,
|
||||||
|
storage = extended
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
--Compare function for ltree
|
||||||
|
CREATE FUNCTION ltree_cmp(ltree,ltree)
|
||||||
|
RETURNS int4
|
||||||
|
AS 'MODULE_PATHNAME'
|
||||||
|
LANGUAGE 'c' with (isstrict,iscachable);
|
||||||
|
|
||||||
|
CREATE FUNCTION ltree_lt(ltree,ltree)
|
||||||
|
RETURNS bool
|
||||||
|
AS 'MODULE_PATHNAME'
|
||||||
|
LANGUAGE 'c' with (isstrict,iscachable);
|
||||||
|
|
||||||
|
CREATE FUNCTION ltree_le(ltree,ltree)
|
||||||
|
RETURNS bool
|
||||||
|
AS 'MODULE_PATHNAME'
|
||||||
|
LANGUAGE 'c' with (isstrict,iscachable);
|
||||||
|
|
||||||
|
CREATE FUNCTION ltree_eq(ltree,ltree)
|
||||||
|
RETURNS bool
|
||||||
|
AS 'MODULE_PATHNAME'
|
||||||
|
LANGUAGE 'c' with (isstrict,iscachable);
|
||||||
|
|
||||||
|
CREATE FUNCTION ltree_ge(ltree,ltree)
|
||||||
|
RETURNS bool
|
||||||
|
AS 'MODULE_PATHNAME'
|
||||||
|
LANGUAGE 'c' with (isstrict,iscachable);
|
||||||
|
|
||||||
|
CREATE FUNCTION ltree_gt(ltree,ltree)
|
||||||
|
RETURNS bool
|
||||||
|
AS 'MODULE_PATHNAME'
|
||||||
|
LANGUAGE 'c' with (isstrict,iscachable);
|
||||||
|
|
||||||
|
CREATE FUNCTION ltree_ne(ltree,ltree)
|
||||||
|
RETURNS bool
|
||||||
|
AS 'MODULE_PATHNAME'
|
||||||
|
LANGUAGE 'c' with (isstrict,iscachable);
|
||||||
|
|
||||||
|
|
||||||
|
CREATE OPERATOR < (
|
||||||
|
LEFTARG = ltree, RIGHTARG = ltree, PROCEDURE = ltree_lt,
|
||||||
|
COMMUTATOR = '>', NEGATOR = '>=',
|
||||||
|
RESTRICT = contsel, JOIN = contjoinsel
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE OPERATOR <= (
|
||||||
|
LEFTARG = ltree, RIGHTARG = ltree, PROCEDURE = ltree_le,
|
||||||
|
COMMUTATOR = '>=', NEGATOR = '>',
|
||||||
|
RESTRICT = contsel, JOIN = contjoinsel
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE OPERATOR >= (
|
||||||
|
LEFTARG = ltree, RIGHTARG = ltree, PROCEDURE = ltree_ge,
|
||||||
|
COMMUTATOR = '<=', NEGATOR = '<',
|
||||||
|
RESTRICT = contsel, JOIN = contjoinsel
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE OPERATOR > (
|
||||||
|
LEFTARG = ltree, RIGHTARG = ltree, PROCEDURE = ltree_gt,
|
||||||
|
COMMUTATOR = '<', NEGATOR = '<=',
|
||||||
|
RESTRICT = contsel, JOIN = contjoinsel
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE OPERATOR = (
|
||||||
|
LEFTARG = ltree, RIGHTARG = ltree, PROCEDURE = ltree_eq,
|
||||||
|
COMMUTATOR = '=', NEGATOR = '<>',
|
||||||
|
RESTRICT = eqsel, JOIN = eqjoinsel,
|
||||||
|
SORT1 = '<', SORT2 = '<'
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE OPERATOR <> (
|
||||||
|
LEFTARG = ltree, RIGHTARG = ltree, PROCEDURE = ltree_ne,
|
||||||
|
COMMUTATOR = '<>', NEGATOR = '=',
|
||||||
|
RESTRICT = neqsel, JOIN = neqjoinsel
|
||||||
|
);
|
||||||
|
|
||||||
|
--util functions
|
||||||
|
|
||||||
|
CREATE FUNCTION subltree(ltree,int4,int4)
|
||||||
|
RETURNS ltree
|
||||||
|
AS 'MODULE_PATHNAME'
|
||||||
|
LANGUAGE 'c' with (isstrict,iscachable);
|
||||||
|
|
||||||
|
CREATE FUNCTION subpath(ltree,int4,int4)
|
||||||
|
RETURNS ltree
|
||||||
|
AS 'MODULE_PATHNAME'
|
||||||
|
LANGUAGE 'c' with (isstrict,iscachable);
|
||||||
|
|
||||||
|
CREATE FUNCTION subpath(ltree,int4)
|
||||||
|
RETURNS ltree
|
||||||
|
AS 'MODULE_PATHNAME'
|
||||||
|
LANGUAGE 'c' with (isstrict,iscachable);
|
||||||
|
|
||||||
|
CREATE FUNCTION nlevel(ltree)
|
||||||
|
RETURNS int4
|
||||||
|
AS 'MODULE_PATHNAME'
|
||||||
|
LANGUAGE 'c' with (isstrict,iscachable);
|
||||||
|
|
||||||
|
CREATE FUNCTION ltree_isparent(ltree,ltree)
|
||||||
|
RETURNS bool
|
||||||
|
AS 'MODULE_PATHNAME'
|
||||||
|
LANGUAGE 'c' with (isstrict,iscachable);
|
||||||
|
|
||||||
|
CREATE FUNCTION ltree_risparent(ltree,ltree)
|
||||||
|
RETURNS bool
|
||||||
|
AS 'MODULE_PATHNAME'
|
||||||
|
LANGUAGE 'c' with (isstrict,iscachable);
|
||||||
|
|
||||||
|
CREATE FUNCTION ltree_addltree(ltree,ltree)
|
||||||
|
RETURNS ltree
|
||||||
|
AS 'MODULE_PATHNAME'
|
||||||
|
LANGUAGE 'c' with (isstrict,iscachable);
|
||||||
|
|
||||||
|
CREATE FUNCTION ltree_addtext(ltree,text)
|
||||||
|
RETURNS ltree
|
||||||
|
AS 'MODULE_PATHNAME'
|
||||||
|
LANGUAGE 'c' with (isstrict,iscachable);
|
||||||
|
|
||||||
|
CREATE FUNCTION ltree_textadd(text,ltree)
|
||||||
|
RETURNS ltree
|
||||||
|
AS 'MODULE_PATHNAME'
|
||||||
|
LANGUAGE 'c' with (isstrict,iscachable);
|
||||||
|
|
||||||
|
CREATE OPERATOR @> (
|
||||||
|
LEFTARG = ltree, RIGHTARG = ltree, PROCEDURE = ltree_isparent,
|
||||||
|
COMMUTATOR = '<@',
|
||||||
|
RESTRICT = contsel, JOIN = contjoinsel
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE OPERATOR ^@> (
|
||||||
|
LEFTARG = ltree, RIGHTARG = ltree, PROCEDURE = ltree_isparent,
|
||||||
|
COMMUTATOR = '^<@',
|
||||||
|
RESTRICT = contsel, JOIN = contjoinsel
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE OPERATOR <@ (
|
||||||
|
LEFTARG = ltree, RIGHTARG = ltree, PROCEDURE = ltree_risparent,
|
||||||
|
COMMUTATOR = '@>',
|
||||||
|
RESTRICT = contsel, JOIN = contjoinsel
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE OPERATOR ^<@ (
|
||||||
|
LEFTARG = ltree, RIGHTARG = ltree, PROCEDURE = ltree_risparent,
|
||||||
|
COMMUTATOR = '^@>',
|
||||||
|
RESTRICT = contsel, JOIN = contjoinsel
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE OPERATOR || (
|
||||||
|
LEFTARG = ltree, RIGHTARG = ltree, PROCEDURE = ltree_addltree
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE OPERATOR || (
|
||||||
|
LEFTARG = ltree, RIGHTARG = text, PROCEDURE = ltree_addtext
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE OPERATOR || (
|
||||||
|
LEFTARG = text, RIGHTARG = ltree, PROCEDURE = ltree_textadd
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
-- B-tree support
|
||||||
|
INSERT INTO pg_opclass (opcamid, opcname, opcnamespace, opcowner, opcintype, opcdefault, opckeytype)
|
||||||
|
VALUES (
|
||||||
|
(SELECT oid FROM pg_am WHERE amname = 'btree'),
|
||||||
|
'ltree_ops',
|
||||||
|
(SELECT oid FROM pg_namespace WHERE nspname = 'pg_catalog'),
|
||||||
|
1, -- UID of superuser is hardwired to 1 as of PG 7.3
|
||||||
|
(SELECT oid FROM pg_type WHERE typname = 'ltree'),
|
||||||
|
true,
|
||||||
|
0);
|
||||||
|
|
||||||
|
SELECT o.oid AS opoid, o.oprname
|
||||||
|
INTO TEMP TABLE ltree_ops_tmp
|
||||||
|
FROM pg_operator o, pg_type t
|
||||||
|
WHERE o.oprleft = t.oid and o.oprright = t.oid
|
||||||
|
and t.typname = 'ltree';
|
||||||
|
|
||||||
|
INSERT INTO pg_amop (amopclaid, amopstrategy, amopreqcheck, amopopr)
|
||||||
|
SELECT opcl.oid, 1, false, c.opoid
|
||||||
|
FROM pg_opclass opcl, ltree_ops_tmp c
|
||||||
|
WHERE
|
||||||
|
opcamid = (SELECT oid FROM pg_am WHERE amname = 'btree') AND
|
||||||
|
opcname = 'ltree_ops' AND
|
||||||
|
c.oprname = '<';
|
||||||
|
|
||||||
|
INSERT INTO pg_amop (amopclaid, amopstrategy, amopreqcheck, amopopr)
|
||||||
|
SELECT opcl.oid, 2, false, c.opoid
|
||||||
|
FROM pg_opclass opcl, ltree_ops_tmp c
|
||||||
|
WHERE
|
||||||
|
opcamid = (SELECT oid FROM pg_am WHERE amname = 'btree') AND
|
||||||
|
opcname = 'ltree_ops' AND
|
||||||
|
c.oprname = '<=';
|
||||||
|
INSERT INTO pg_amop (amopclaid, amopstrategy, amopreqcheck, amopopr)
|
||||||
|
SELECT opcl.oid, 3, false, c.opoid
|
||||||
|
FROM pg_opclass opcl, ltree_ops_tmp c
|
||||||
|
WHERE
|
||||||
|
opcamid = (SELECT oid FROM pg_am WHERE amname = 'btree') AND
|
||||||
|
opcname = 'ltree_ops' AND
|
||||||
|
c.oprname = '=';
|
||||||
|
|
||||||
|
INSERT INTO pg_amop (amopclaid, amopstrategy, amopreqcheck, amopopr)
|
||||||
|
SELECT opcl.oid, 4, false, c.opoid
|
||||||
|
FROM pg_opclass opcl, ltree_ops_tmp c
|
||||||
|
WHERE
|
||||||
|
opcamid = (SELECT oid FROM pg_am WHERE amname = 'btree') AND
|
||||||
|
opcname = 'ltree_ops' AND
|
||||||
|
c.oprname = '>=';
|
||||||
|
|
||||||
|
INSERT INTO pg_amop (amopclaid, amopstrategy, amopreqcheck, amopopr)
|
||||||
|
SELECT opcl.oid, 5, false, c.opoid
|
||||||
|
FROM pg_opclass opcl, ltree_ops_tmp c
|
||||||
|
WHERE
|
||||||
|
opcamid = (SELECT oid FROM pg_am WHERE amname = 'btree') AND
|
||||||
|
opcname = 'ltree_ops' AND
|
||||||
|
c.oprname = '>';
|
||||||
|
|
||||||
|
INSERT INTO pg_amproc (amopclaid, amprocnum, amproc)
|
||||||
|
SELECT opcl.oid, 1, p.oid
|
||||||
|
FROM pg_opclass opcl, pg_proc p
|
||||||
|
WHERE
|
||||||
|
opcamid = (SELECT oid FROM pg_am WHERE amname = 'btree') AND
|
||||||
|
opcname = 'ltree_ops' AND
|
||||||
|
p.proname = 'ltree_cmp';
|
||||||
|
|
||||||
|
drop table ltree_ops_tmp;
|
||||||
|
|
||||||
|
--lquery type
|
||||||
|
CREATE FUNCTION lquery_in(opaque)
|
||||||
|
RETURNS opaque
|
||||||
|
AS 'MODULE_PATHNAME'
|
||||||
|
LANGUAGE 'c' with (isstrict);
|
||||||
|
|
||||||
|
CREATE FUNCTION lquery_out(opaque)
|
||||||
|
RETURNS opaque
|
||||||
|
AS 'MODULE_PATHNAME'
|
||||||
|
LANGUAGE 'c' with (isstrict);
|
||||||
|
|
||||||
|
CREATE TYPE lquery (
|
||||||
|
internallength = -1,
|
||||||
|
input = lquery_in,
|
||||||
|
output = lquery_out,
|
||||||
|
storage = extended
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE FUNCTION ltq_regex(ltree,lquery)
|
||||||
|
RETURNS bool
|
||||||
|
AS 'MODULE_PATHNAME'
|
||||||
|
LANGUAGE 'c' with (isstrict,iscachable);
|
||||||
|
|
||||||
|
CREATE FUNCTION ltq_rregex(lquery,ltree)
|
||||||
|
RETURNS bool
|
||||||
|
AS 'MODULE_PATHNAME'
|
||||||
|
LANGUAGE 'c' with (isstrict,iscachable);
|
||||||
|
|
||||||
|
CREATE OPERATOR ~ (
|
||||||
|
LEFTARG = ltree, RIGHTARG = lquery, PROCEDURE = ltq_regex,
|
||||||
|
COMMUTATOR = '~',
|
||||||
|
RESTRICT = contsel, JOIN = contjoinsel
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE OPERATOR ~ (
|
||||||
|
LEFTARG = lquery, RIGHTARG = ltree, PROCEDURE = ltq_rregex,
|
||||||
|
COMMUTATOR = '~',
|
||||||
|
RESTRICT = contsel, JOIN = contjoinsel
|
||||||
|
);
|
||||||
|
|
||||||
|
--not-indexed
|
||||||
|
CREATE OPERATOR ^~ (
|
||||||
|
LEFTARG = ltree, RIGHTARG = lquery, PROCEDURE = ltq_regex,
|
||||||
|
COMMUTATOR = '^~',
|
||||||
|
RESTRICT = contsel, JOIN = contjoinsel
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE OPERATOR ^~ (
|
||||||
|
LEFTARG = lquery, RIGHTARG = ltree, PROCEDURE = ltq_rregex,
|
||||||
|
COMMUTATOR = '^~',
|
||||||
|
RESTRICT = contsel, JOIN = contjoinsel
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE FUNCTION ltxtq_in(opaque)
|
||||||
|
RETURNS opaque
|
||||||
|
AS 'MODULE_PATHNAME'
|
||||||
|
LANGUAGE 'c' with (isstrict);
|
||||||
|
|
||||||
|
CREATE FUNCTION ltxtq_out(opaque)
|
||||||
|
RETURNS opaque
|
||||||
|
AS 'MODULE_PATHNAME'
|
||||||
|
LANGUAGE 'c' with (isstrict);
|
||||||
|
|
||||||
|
CREATE TYPE ltxtquery (
|
||||||
|
internallength = -1,
|
||||||
|
input = ltxtq_in,
|
||||||
|
output = ltxtq_out,
|
||||||
|
storage = extended
|
||||||
|
);
|
||||||
|
|
||||||
|
-- operations with ltxtquery
|
||||||
|
|
||||||
|
CREATE FUNCTION ltxtq_exec(ltree, ltxtquery)
|
||||||
|
RETURNS bool
|
||||||
|
AS 'MODULE_PATHNAME'
|
||||||
|
LANGUAGE 'c' with (isstrict, iscachable);
|
||||||
|
|
||||||
|
CREATE FUNCTION ltxtq_rexec(ltxtquery, ltree)
|
||||||
|
RETURNS bool
|
||||||
|
AS 'MODULE_PATHNAME'
|
||||||
|
LANGUAGE 'c' with (isstrict, iscachable);
|
||||||
|
|
||||||
|
CREATE OPERATOR @ (
|
||||||
|
LEFTARG = ltree, RIGHTARG = ltxtquery, PROCEDURE = ltxtq_exec,
|
||||||
|
COMMUTATOR = '@',
|
||||||
|
RESTRICT = contsel, JOIN = contjoinsel
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE OPERATOR @ (
|
||||||
|
LEFTARG = ltxtquery, RIGHTARG = ltree, PROCEDURE = ltxtq_rexec,
|
||||||
|
COMMUTATOR = '@',
|
||||||
|
RESTRICT = contsel, JOIN = contjoinsel
|
||||||
|
);
|
||||||
|
|
||||||
|
--not-indexed
|
||||||
|
CREATE OPERATOR ^@ (
|
||||||
|
LEFTARG = ltree, RIGHTARG = ltxtquery, PROCEDURE = ltxtq_exec,
|
||||||
|
COMMUTATOR = '^@',
|
||||||
|
RESTRICT = contsel, JOIN = contjoinsel
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE OPERATOR ^@ (
|
||||||
|
LEFTARG = ltxtquery, RIGHTARG = ltree, PROCEDURE = ltxtq_rexec,
|
||||||
|
COMMUTATOR = '^@',
|
||||||
|
RESTRICT = contsel, JOIN = contjoinsel
|
||||||
|
);
|
||||||
|
|
||||||
|
--GiST support for ltree
|
||||||
|
CREATE FUNCTION ltree_gist_in(opaque)
|
||||||
|
RETURNS opaque
|
||||||
|
AS 'MODULE_PATHNAME'
|
||||||
|
LANGUAGE 'c' with (isstrict);
|
||||||
|
|
||||||
|
CREATE FUNCTION ltree_gist_out(opaque)
|
||||||
|
RETURNS opaque
|
||||||
|
AS 'MODULE_PATHNAME'
|
||||||
|
LANGUAGE 'c' with (isstrict);
|
||||||
|
|
||||||
|
CREATE TYPE ltree_gist (
|
||||||
|
internallength = -1,
|
||||||
|
input = ltree_gist_in,
|
||||||
|
output = ltree_gist_out,
|
||||||
|
storage = plain
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
create function ltree_consistent(opaque,opaque,int2) returns bool as 'MODULE_PATHNAME' language 'C';
|
||||||
|
create function ltree_compress(opaque) returns opaque as 'MODULE_PATHNAME' language 'C';
|
||||||
|
create function ltree_decompress(opaque) returns opaque as 'MODULE_PATHNAME' language 'C';
|
||||||
|
create function ltree_penalty(opaque,opaque,opaque) returns opaque as 'MODULE_PATHNAME' language 'C' with(isstrict);
|
||||||
|
create function ltree_picksplit(opaque, opaque) returns opaque as 'MODULE_PATHNAME' language 'C';
|
||||||
|
create function ltree_union(bytea, opaque) returns int4 as 'MODULE_PATHNAME' language 'C';
|
||||||
|
create function ltree_same(opaque, opaque, opaque) returns opaque as 'MODULE_PATHNAME' language 'C';
|
||||||
|
|
||||||
|
INSERT INTO pg_opclass (opcamid, opcname, opcnamespace, opcowner, opcintype, opckeytype, opcdefault)
|
||||||
|
SELECT pg_am.oid, 'gist_ltree_ops',
|
||||||
|
(SELECT oid FROM pg_namespace WHERE nspname = 'pg_catalog'),
|
||||||
|
1, -- UID of superuser is hardwired to 1 as of PG 7.3
|
||||||
|
pg_type.oid, pg_key.oid, true
|
||||||
|
FROM pg_type, pg_am, pg_type pg_key
|
||||||
|
WHERE pg_type.typname = 'ltree' and
|
||||||
|
pg_am.amname='gist' and
|
||||||
|
pg_key.typname = 'ltree_gist';
|
||||||
|
|
||||||
|
SELECT o.oid AS opoid, o.oprname
|
||||||
|
INTO TABLE ltree_ops_tmp
|
||||||
|
FROM pg_operator o, pg_type t
|
||||||
|
WHERE o.oprleft = t.oid and o.oprright = t.oid
|
||||||
|
and t.typname = 'ltree';
|
||||||
|
|
||||||
|
INSERT INTO pg_amop (amopclaid, amopopr, amopstrategy, amopreqcheck)
|
||||||
|
SELECT opcl.oid, c.opoid, 1, 'f'
|
||||||
|
FROM pg_opclass opcl, ltree_ops_tmp c
|
||||||
|
WHERE opcname = 'gist_ltree_ops'
|
||||||
|
and c.oprname = '<';
|
||||||
|
|
||||||
|
INSERT INTO pg_amop (amopclaid, amopopr, amopstrategy, amopreqcheck)
|
||||||
|
SELECT opcl.oid, c.opoid, 2, 'f'
|
||||||
|
FROM pg_opclass opcl, ltree_ops_tmp c
|
||||||
|
WHERE opcname = 'gist_ltree_ops'
|
||||||
|
and c.oprname = '<=';
|
||||||
|
|
||||||
|
INSERT INTO pg_amop (amopclaid, amopopr, amopstrategy, amopreqcheck)
|
||||||
|
SELECT opcl.oid, c.opoid, 3, 'f'
|
||||||
|
FROM pg_opclass opcl, ltree_ops_tmp c
|
||||||
|
WHERE opcname = 'gist_ltree_ops'
|
||||||
|
and c.oprname = '=';
|
||||||
|
|
||||||
|
INSERT INTO pg_amop (amopclaid, amopopr, amopstrategy, amopreqcheck)
|
||||||
|
SELECT opcl.oid, c.opoid, 4, 'f'
|
||||||
|
FROM pg_opclass opcl, ltree_ops_tmp c
|
||||||
|
WHERE opcname = 'gist_ltree_ops'
|
||||||
|
and c.oprname = '>=';
|
||||||
|
|
||||||
|
INSERT INTO pg_amop (amopclaid, amopopr, amopstrategy, amopreqcheck)
|
||||||
|
SELECT opcl.oid, c.opoid, 5, 'f'
|
||||||
|
FROM pg_opclass opcl, ltree_ops_tmp c
|
||||||
|
WHERE opcname = 'gist_ltree_ops'
|
||||||
|
and c.oprname = '>';
|
||||||
|
|
||||||
|
INSERT INTO pg_amop (amopclaid, amopstrategy, amopreqcheck, amopopr)
|
||||||
|
SELECT opcl.oid, 10, false, c.opoid
|
||||||
|
FROM pg_opclass opcl, ltree_ops_tmp c
|
||||||
|
WHERE
|
||||||
|
opcamid = (SELECT oid FROM pg_am WHERE amname = 'gist')
|
||||||
|
and opcname = 'gist_ltree_ops'
|
||||||
|
and c.oprname = '@>';
|
||||||
|
|
||||||
|
INSERT INTO pg_amop (amopclaid, amopstrategy, amopreqcheck, amopopr)
|
||||||
|
SELECT opcl.oid, 11, false, c.opoid
|
||||||
|
FROM pg_opclass opcl, ltree_ops_tmp c
|
||||||
|
WHERE
|
||||||
|
opcamid = (SELECT oid FROM pg_am WHERE amname = 'gist')
|
||||||
|
and opcname = 'gist_ltree_ops'
|
||||||
|
and c.oprname = '<@';
|
||||||
|
|
||||||
|
DROP TABLE ltree_ops_tmp;
|
||||||
|
|
||||||
|
INSERT INTO pg_amop (amopclaid, amopstrategy, amopreqcheck, amopopr)
|
||||||
|
SELECT opcl.oid, 12, false, o.oid
|
||||||
|
FROM pg_opclass opcl, pg_operator o, pg_type t, pg_type tq
|
||||||
|
WHERE
|
||||||
|
opcamid = (SELECT oid FROM pg_am WHERE amname = 'gist')
|
||||||
|
and opcname = 'gist_ltree_ops'
|
||||||
|
and t.typname = 'ltree' and tq.typname = 'lquery'
|
||||||
|
and o.oprleft = t.oid and o.oprright = tq.oid
|
||||||
|
and o.oprname = '~';
|
||||||
|
|
||||||
|
INSERT INTO pg_amop (amopclaid, amopstrategy, amopreqcheck, amopopr)
|
||||||
|
SELECT opcl.oid, 13, false, o.oid
|
||||||
|
FROM pg_opclass opcl, pg_operator o, pg_type t, pg_type tq
|
||||||
|
WHERE
|
||||||
|
opcamid = (SELECT oid FROM pg_am WHERE amname = 'gist')
|
||||||
|
and opcname = 'gist_ltree_ops'
|
||||||
|
and t.typname = 'lquery' and tq.typname = 'ltree'
|
||||||
|
and o.oprleft = t.oid and o.oprright = tq.oid
|
||||||
|
and o.oprname = '~';
|
||||||
|
|
||||||
|
INSERT INTO pg_amop (amopclaid, amopstrategy, amopreqcheck, amopopr)
|
||||||
|
SELECT opcl.oid, 14, false, o.oid
|
||||||
|
FROM pg_opclass opcl, pg_operator o, pg_type t, pg_type tq
|
||||||
|
WHERE
|
||||||
|
opcamid = (SELECT oid FROM pg_am WHERE amname = 'gist')
|
||||||
|
and opcname = 'gist_ltree_ops'
|
||||||
|
and t.typname = 'ltree' and tq.typname = 'ltxtquery'
|
||||||
|
and o.oprleft = t.oid and o.oprright = tq.oid
|
||||||
|
and o.oprname = '@';
|
||||||
|
|
||||||
|
INSERT INTO pg_amop (amopclaid, amopstrategy, amopreqcheck, amopopr)
|
||||||
|
SELECT opcl.oid, 15, false, o.oid
|
||||||
|
FROM pg_opclass opcl, pg_operator o, pg_type t, pg_type tq
|
||||||
|
WHERE
|
||||||
|
opcamid = (SELECT oid FROM pg_am WHERE amname = 'gist')
|
||||||
|
and opcname = 'gist_ltree_ops'
|
||||||
|
and t.typname = 'ltxtquery' and tq.typname = 'ltree'
|
||||||
|
and o.oprleft = t.oid and o.oprright = tq.oid
|
||||||
|
and o.oprname = '@';
|
||||||
|
|
||||||
|
INSERT INTO pg_amproc (amopclaid, amprocnum, amproc)
|
||||||
|
SELECT opcl.oid, 1, pro.oid
|
||||||
|
FROM pg_opclass opcl, pg_proc pro
|
||||||
|
WHERE
|
||||||
|
opcamid = (SELECT oid FROM pg_am WHERE amname = 'gist')
|
||||||
|
and opcname = 'gist_ltree_ops'
|
||||||
|
and proname = 'ltree_consistent';
|
||||||
|
|
||||||
|
INSERT INTO pg_amproc (amopclaid, amprocnum, amproc)
|
||||||
|
SELECT opcl.oid, 2, pro.oid
|
||||||
|
FROM pg_opclass opcl, pg_proc pro
|
||||||
|
WHERE
|
||||||
|
opcamid = (SELECT oid FROM pg_am WHERE amname = 'gist')
|
||||||
|
and opcname = 'gist_ltree_ops'
|
||||||
|
and proname = 'ltree_union';
|
||||||
|
|
||||||
|
INSERT INTO pg_amproc (amopclaid, amprocnum, amproc)
|
||||||
|
SELECT opcl.oid, 3, pro.oid
|
||||||
|
FROM pg_opclass opcl, pg_proc pro
|
||||||
|
WHERE
|
||||||
|
opcamid = (SELECT oid FROM pg_am WHERE amname = 'gist')
|
||||||
|
and opcname = 'gist_ltree_ops'
|
||||||
|
and proname = 'ltree_compress';
|
||||||
|
INSERT INTO pg_amproc (amopclaid, amprocnum, amproc)
|
||||||
|
SELECT opcl.oid, 4, pro.oid
|
||||||
|
FROM pg_opclass opcl, pg_proc pro
|
||||||
|
WHERE
|
||||||
|
opcamid = (SELECT oid FROM pg_am WHERE amname = 'gist')
|
||||||
|
and opcname = 'gist_ltree_ops'
|
||||||
|
and proname = 'ltree_decompress';
|
||||||
|
|
||||||
|
INSERT INTO pg_amproc (amopclaid, amprocnum, amproc)
|
||||||
|
SELECT opcl.oid, 5, pro.oid
|
||||||
|
FROM pg_opclass opcl, pg_proc pro
|
||||||
|
WHERE
|
||||||
|
opcamid = (SELECT oid FROM pg_am WHERE amname = 'gist')
|
||||||
|
and opcname = 'gist_ltree_ops'
|
||||||
|
and proname = 'ltree_penalty';
|
||||||
|
|
||||||
|
INSERT INTO pg_amproc (amopclaid, amprocnum, amproc)
|
||||||
|
SELECT opcl.oid, 6, pro.oid
|
||||||
|
FROM pg_opclass opcl, pg_proc pro
|
||||||
|
WHERE
|
||||||
|
opcamid = (SELECT oid FROM pg_am WHERE amname = 'gist')
|
||||||
|
and opcname = 'gist_ltree_ops'
|
||||||
|
and proname = 'ltree_picksplit';
|
||||||
|
|
||||||
|
INSERT INTO pg_amproc (amopclaid, amprocnum, amproc)
|
||||||
|
SELECT opcl.oid, 7, pro.oid
|
||||||
|
FROM pg_opclass opcl, pg_proc pro
|
||||||
|
WHERE
|
||||||
|
opcamid = (SELECT oid FROM pg_am WHERE amname = 'gist')
|
||||||
|
and opcname = 'gist_ltree_ops'
|
||||||
|
and proname = 'ltree_same';
|
||||||
|
|
||||||
|
-- arrays of ltree
|
||||||
|
|
||||||
|
CREATE FUNCTION _ltree_isparent(_ltree,ltree)
|
||||||
|
RETURNS bool
|
||||||
|
AS 'MODULE_PATHNAME'
|
||||||
|
LANGUAGE 'c' with (isstrict,iscachable);
|
||||||
|
|
||||||
|
CREATE FUNCTION _ltree_r_isparent(ltree,_ltree)
|
||||||
|
RETURNS bool
|
||||||
|
AS 'MODULE_PATHNAME'
|
||||||
|
LANGUAGE 'c' with (isstrict,iscachable);
|
||||||
|
|
||||||
|
CREATE FUNCTION _ltree_risparent(_ltree,ltree)
|
||||||
|
RETURNS bool
|
||||||
|
AS 'MODULE_PATHNAME'
|
||||||
|
LANGUAGE 'c' with (isstrict,iscachable);
|
||||||
|
|
||||||
|
CREATE FUNCTION _ltree_r_risparent(ltree,_ltree)
|
||||||
|
RETURNS bool
|
||||||
|
AS 'MODULE_PATHNAME'
|
||||||
|
LANGUAGE 'c' with (isstrict,iscachable);
|
||||||
|
|
||||||
|
CREATE FUNCTION _ltq_regex(_ltree,lquery)
|
||||||
|
RETURNS bool
|
||||||
|
AS 'MODULE_PATHNAME'
|
||||||
|
LANGUAGE 'c' with (isstrict,iscachable);
|
||||||
|
|
||||||
|
CREATE FUNCTION _ltq_rregex(lquery,_ltree)
|
||||||
|
RETURNS bool
|
||||||
|
AS 'MODULE_PATHNAME'
|
||||||
|
LANGUAGE 'c' with (isstrict,iscachable);
|
||||||
|
|
||||||
|
CREATE FUNCTION _ltxtq_exec(_ltree, ltxtquery)
|
||||||
|
RETURNS bool
|
||||||
|
AS 'MODULE_PATHNAME'
|
||||||
|
LANGUAGE 'c' with (isstrict, iscachable);
|
||||||
|
|
||||||
|
CREATE FUNCTION _ltxtq_rexec(ltxtquery, _ltree)
|
||||||
|
RETURNS bool
|
||||||
|
AS 'MODULE_PATHNAME'
|
||||||
|
LANGUAGE 'c' with (isstrict, iscachable);
|
||||||
|
|
||||||
|
CREATE OPERATOR @> (
|
||||||
|
LEFTARG = _ltree, RIGHTARG = ltree, PROCEDURE = _ltree_isparent,
|
||||||
|
COMMUTATOR = '<@',
|
||||||
|
RESTRICT = contsel, JOIN = contjoinsel
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE OPERATOR <@ (
|
||||||
|
LEFTARG = ltree, RIGHTARG = _ltree, PROCEDURE = _ltree_r_isparent,
|
||||||
|
COMMUTATOR = '@>',
|
||||||
|
RESTRICT = contsel, JOIN = contjoinsel
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE OPERATOR <@ (
|
||||||
|
LEFTARG = _ltree, RIGHTARG = ltree, PROCEDURE = _ltree_risparent,
|
||||||
|
COMMUTATOR = '@>',
|
||||||
|
RESTRICT = contsel, JOIN = contjoinsel
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE OPERATOR @> (
|
||||||
|
LEFTARG = ltree, RIGHTARG = _ltree, PROCEDURE = _ltree_r_risparent,
|
||||||
|
COMMUTATOR = '<@',
|
||||||
|
RESTRICT = contsel, JOIN = contjoinsel
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE OPERATOR ~ (
|
||||||
|
LEFTARG = _ltree, RIGHTARG = lquery, PROCEDURE = _ltq_regex,
|
||||||
|
COMMUTATOR = '~',
|
||||||
|
RESTRICT = contsel, JOIN = contjoinsel
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE OPERATOR ~ (
|
||||||
|
LEFTARG = lquery, RIGHTARG = _ltree, PROCEDURE = _ltq_rregex,
|
||||||
|
COMMUTATOR = '~',
|
||||||
|
RESTRICT = contsel, JOIN = contjoinsel
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE OPERATOR @ (
|
||||||
|
LEFTARG = _ltree, RIGHTARG = ltxtquery, PROCEDURE = _ltxtq_exec,
|
||||||
|
COMMUTATOR = '@',
|
||||||
|
RESTRICT = contsel, JOIN = contjoinsel
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE OPERATOR @ (
|
||||||
|
LEFTARG = ltxtquery, RIGHTARG = _ltree, PROCEDURE = _ltxtq_rexec,
|
||||||
|
COMMUTATOR = '@',
|
||||||
|
RESTRICT = contsel, JOIN = contjoinsel
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
--not indexed
|
||||||
|
CREATE OPERATOR ^@> (
|
||||||
|
LEFTARG = _ltree, RIGHTARG = ltree, PROCEDURE = _ltree_isparent,
|
||||||
|
COMMUTATOR = '^<@',
|
||||||
|
RESTRICT = contsel, JOIN = contjoinsel
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE OPERATOR ^<@ (
|
||||||
|
LEFTARG = ltree, RIGHTARG = _ltree, PROCEDURE = _ltree_r_isparent,
|
||||||
|
COMMUTATOR = '^@>',
|
||||||
|
RESTRICT = contsel, JOIN = contjoinsel
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE OPERATOR ^<@ (
|
||||||
|
LEFTARG = _ltree, RIGHTARG = ltree, PROCEDURE = _ltree_risparent,
|
||||||
|
COMMUTATOR = '^@>',
|
||||||
|
RESTRICT = contsel, JOIN = contjoinsel
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE OPERATOR ^@> (
|
||||||
|
LEFTARG = ltree, RIGHTARG = _ltree, PROCEDURE = _ltree_r_risparent,
|
||||||
|
COMMUTATOR = '^<@',
|
||||||
|
RESTRICT = contsel, JOIN = contjoinsel
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE OPERATOR ^~ (
|
||||||
|
LEFTARG = _ltree, RIGHTARG = lquery, PROCEDURE = _ltq_regex,
|
||||||
|
COMMUTATOR = '^~',
|
||||||
|
RESTRICT = contsel, JOIN = contjoinsel
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE OPERATOR ^~ (
|
||||||
|
LEFTARG = lquery, RIGHTARG = _ltree, PROCEDURE = _ltq_rregex,
|
||||||
|
COMMUTATOR = '^~',
|
||||||
|
RESTRICT = contsel, JOIN = contjoinsel
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE OPERATOR ^@ (
|
||||||
|
LEFTARG = _ltree, RIGHTARG = ltxtquery, PROCEDURE = _ltxtq_exec,
|
||||||
|
COMMUTATOR = '^@',
|
||||||
|
RESTRICT = contsel, JOIN = contjoinsel
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE OPERATOR ^@ (
|
||||||
|
LEFTARG = ltxtquery, RIGHTARG = _ltree, PROCEDURE = _ltxtq_rexec,
|
||||||
|
COMMUTATOR = '^@',
|
||||||
|
RESTRICT = contsel, JOIN = contjoinsel
|
||||||
|
);
|
||||||
|
|
||||||
|
--extractors
|
||||||
|
CREATE FUNCTION _ltree_extract_isparent(_ltree,ltree)
|
||||||
|
RETURNS ltree
|
||||||
|
AS 'MODULE_PATHNAME'
|
||||||
|
LANGUAGE 'c' with (isstrict,iscachable);
|
||||||
|
|
||||||
|
CREATE OPERATOR ?@> (
|
||||||
|
LEFTARG = _ltree, RIGHTARG = ltree, PROCEDURE = _ltree_extract_isparent
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE FUNCTION _ltree_extract_risparent(_ltree,ltree)
|
||||||
|
RETURNS ltree
|
||||||
|
AS 'MODULE_PATHNAME'
|
||||||
|
LANGUAGE 'c' with (isstrict,iscachable);
|
||||||
|
|
||||||
|
CREATE OPERATOR ?<@ (
|
||||||
|
LEFTARG = _ltree, RIGHTARG = ltree, PROCEDURE = _ltree_extract_risparent
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE FUNCTION _ltq_extract_regex(_ltree,lquery)
|
||||||
|
RETURNS ltree
|
||||||
|
AS 'MODULE_PATHNAME'
|
||||||
|
LANGUAGE 'c' with (isstrict,iscachable);
|
||||||
|
|
||||||
|
CREATE OPERATOR ?~ (
|
||||||
|
LEFTARG = _ltree, RIGHTARG = lquery, PROCEDURE = _ltq_extract_regex
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE FUNCTION _ltxtq_extract_exec(_ltree,ltxtquery)
|
||||||
|
RETURNS ltree
|
||||||
|
AS 'MODULE_PATHNAME'
|
||||||
|
LANGUAGE 'c' with (isstrict,iscachable);
|
||||||
|
|
||||||
|
CREATE OPERATOR ?@ (
|
||||||
|
LEFTARG = _ltree, RIGHTARG = ltxtquery, PROCEDURE = _ltxtq_extract_exec
|
||||||
|
);
|
||||||
|
|
||||||
|
--GiST support for ltree[]
|
||||||
|
create function _ltree_consistent(opaque,opaque,int2) returns bool as 'MODULE_PATHNAME' language 'C';
|
||||||
|
create function _ltree_compress(opaque) returns opaque as 'MODULE_PATHNAME' language 'C';
|
||||||
|
create function _ltree_penalty(opaque,opaque,opaque) returns opaque as 'MODULE_PATHNAME' language 'C' with(isstrict);
|
||||||
|
create function _ltree_picksplit(opaque, opaque) returns opaque as 'MODULE_PATHNAME' language 'C';
|
||||||
|
create function _ltree_union(bytea, opaque) returns int4 as 'MODULE_PATHNAME' language 'C';
|
||||||
|
create function _ltree_same(opaque, opaque, opaque) returns opaque as 'MODULE_PATHNAME' language 'C';
|
||||||
|
|
||||||
|
INSERT INTO pg_opclass (opcamid, opcname, opcnamespace, opcowner, opcintype, opckeytype, opcdefault)
|
||||||
|
SELECT pg_am.oid, 'gist__ltree_ops',
|
||||||
|
(SELECT oid FROM pg_namespace WHERE nspname = 'pg_catalog'),
|
||||||
|
1, -- UID of superuser is hardwired to 1 as of PG 7.3
|
||||||
|
pg_type.oid, pg_key.oid, true
|
||||||
|
FROM pg_type, pg_am, pg_type pg_key
|
||||||
|
WHERE pg_type.typname = '_ltree' and
|
||||||
|
pg_am.amname='gist' and
|
||||||
|
pg_key.typname = 'ltree_gist';
|
||||||
|
|
||||||
|
INSERT INTO pg_amop (amopclaid, amopstrategy, amopreqcheck, amopopr)
|
||||||
|
SELECT opcl.oid, 12, true, o.oid
|
||||||
|
FROM pg_opclass opcl, pg_operator o, pg_type t, pg_type tq
|
||||||
|
WHERE
|
||||||
|
opcamid = (SELECT oid FROM pg_am WHERE amname = 'gist')
|
||||||
|
and opcname = 'gist__ltree_ops'
|
||||||
|
and t.typname = '_ltree' and tq.typname = 'lquery'
|
||||||
|
and o.oprleft = t.oid and o.oprright = tq.oid
|
||||||
|
and o.oprname = '~';
|
||||||
|
|
||||||
|
INSERT INTO pg_amop (amopclaid, amopstrategy, amopreqcheck, amopopr)
|
||||||
|
SELECT opcl.oid, 13, true, o.oid
|
||||||
|
FROM pg_opclass opcl, pg_operator o, pg_type t, pg_type tq
|
||||||
|
WHERE
|
||||||
|
opcamid = (SELECT oid FROM pg_am WHERE amname = 'gist')
|
||||||
|
and opcname = 'gist__ltree_ops'
|
||||||
|
and t.typname = 'lquery' and tq.typname = '_ltree'
|
||||||
|
and o.oprleft = t.oid and o.oprright = tq.oid
|
||||||
|
and o.oprname = '~';
|
||||||
|
|
||||||
|
INSERT INTO pg_amop (amopclaid, amopstrategy, amopreqcheck, amopopr)
|
||||||
|
SELECT opcl.oid, 14, true, o.oid
|
||||||
|
FROM pg_opclass opcl, pg_operator o, pg_type t, pg_type tq
|
||||||
|
WHERE
|
||||||
|
opcamid = (SELECT oid FROM pg_am WHERE amname = 'gist')
|
||||||
|
and opcname = 'gist__ltree_ops'
|
||||||
|
and t.typname = '_ltree' and tq.typname = 'ltxtquery'
|
||||||
|
and o.oprleft = t.oid and o.oprright = tq.oid
|
||||||
|
and o.oprname = '@';
|
||||||
|
|
||||||
|
INSERT INTO pg_amop (amopclaid, amopstrategy, amopreqcheck, amopopr)
|
||||||
|
SELECT opcl.oid, 15, true, o.oid
|
||||||
|
FROM pg_opclass opcl, pg_operator o, pg_type t, pg_type tq
|
||||||
|
WHERE
|
||||||
|
opcamid = (SELECT oid FROM pg_am WHERE amname = 'gist')
|
||||||
|
and opcname = 'gist__ltree_ops'
|
||||||
|
and t.typname = 'ltxtquery' and tq.typname = '_ltree'
|
||||||
|
and o.oprleft = t.oid and o.oprright = tq.oid
|
||||||
|
and o.oprname = '@';
|
||||||
|
|
||||||
|
INSERT INTO pg_amop (amopclaid, amopstrategy, amopreqcheck, amopopr)
|
||||||
|
SELECT opcl.oid, 10, true, o.oid
|
||||||
|
FROM pg_opclass opcl, pg_operator o, pg_type t, pg_type tq
|
||||||
|
WHERE
|
||||||
|
opcamid = (SELECT oid FROM pg_am WHERE amname = 'gist')
|
||||||
|
and opcname = 'gist__ltree_ops'
|
||||||
|
and t.typname = '_ltree' and tq.typname = 'ltree'
|
||||||
|
and o.oprleft = t.oid and o.oprright = tq.oid
|
||||||
|
and o.oprname = '<@';
|
||||||
|
|
||||||
|
INSERT INTO pg_amop (amopclaid, amopstrategy, amopreqcheck, amopopr)
|
||||||
|
SELECT opcl.oid, 11, true, o.oid
|
||||||
|
FROM pg_opclass opcl, pg_operator o, pg_type t, pg_type tq
|
||||||
|
WHERE
|
||||||
|
opcamid = (SELECT oid FROM pg_am WHERE amname = 'gist')
|
||||||
|
and opcname = 'gist__ltree_ops'
|
||||||
|
and t.typname = 'ltree' and tq.typname = '_ltree'
|
||||||
|
and o.oprleft = t.oid and o.oprright = tq.oid
|
||||||
|
and o.oprname = '@>';
|
||||||
|
|
||||||
|
INSERT INTO pg_amproc (amopclaid, amprocnum, amproc)
|
||||||
|
SELECT opcl.oid, 1, pro.oid
|
||||||
|
FROM pg_opclass opcl, pg_proc pro
|
||||||
|
WHERE
|
||||||
|
opcamid = (SELECT oid FROM pg_am WHERE amname = 'gist')
|
||||||
|
and opcname = 'gist__ltree_ops'
|
||||||
|
and proname = '_ltree_consistent';
|
||||||
|
|
||||||
|
INSERT INTO pg_amproc (amopclaid, amprocnum, amproc)
|
||||||
|
SELECT opcl.oid, 2, pro.oid
|
||||||
|
FROM pg_opclass opcl, pg_proc pro
|
||||||
|
WHERE
|
||||||
|
opcamid = (SELECT oid FROM pg_am WHERE amname = 'gist')
|
||||||
|
and opcname = 'gist__ltree_ops'
|
||||||
|
and proname = '_ltree_union';
|
||||||
|
|
||||||
|
INSERT INTO pg_amproc (amopclaid, amprocnum, amproc)
|
||||||
|
SELECT opcl.oid, 3, pro.oid
|
||||||
|
FROM pg_opclass opcl, pg_proc pro
|
||||||
|
WHERE
|
||||||
|
opcamid = (SELECT oid FROM pg_am WHERE amname = 'gist')
|
||||||
|
and opcname = 'gist__ltree_ops'
|
||||||
|
and proname = '_ltree_compress';
|
||||||
|
|
||||||
|
INSERT INTO pg_amproc (amopclaid, amprocnum, amproc)
|
||||||
|
SELECT opcl.oid, 4, pro.oid
|
||||||
|
FROM pg_opclass opcl, pg_proc pro
|
||||||
|
WHERE
|
||||||
|
opcamid = (SELECT oid FROM pg_am WHERE amname = 'gist')
|
||||||
|
and opcname = 'gist__ltree_ops'
|
||||||
|
and proname = 'ltree_decompress';
|
||||||
|
|
||||||
|
INSERT INTO pg_amproc (amopclaid, amprocnum, amproc)
|
||||||
|
SELECT opcl.oid, 5, pro.oid
|
||||||
|
FROM pg_opclass opcl, pg_proc pro
|
||||||
|
WHERE
|
||||||
|
opcamid = (SELECT oid FROM pg_am WHERE amname = 'gist')
|
||||||
|
and opcname = 'gist__ltree_ops'
|
||||||
|
and proname = '_ltree_penalty';
|
||||||
|
|
||||||
|
INSERT INTO pg_amproc (amopclaid, amprocnum, amproc)
|
||||||
|
SELECT opcl.oid, 6, pro.oid
|
||||||
|
FROM pg_opclass opcl, pg_proc pro
|
||||||
|
WHERE
|
||||||
|
opcamid = (SELECT oid FROM pg_am WHERE amname = 'gist')
|
||||||
|
and opcname = 'gist__ltree_ops'
|
||||||
|
and proname = '_ltree_picksplit';
|
||||||
|
|
||||||
|
INSERT INTO pg_amproc (amopclaid, amprocnum, amproc)
|
||||||
|
SELECT opcl.oid, 7, pro.oid
|
||||||
|
FROM pg_opclass opcl, pg_proc pro
|
||||||
|
WHERE
|
||||||
|
opcamid = (SELECT oid FROM pg_am WHERE amname = 'gist')
|
||||||
|
and opcname = 'gist__ltree_ops'
|
||||||
|
and proname = '_ltree_same';
|
||||||
|
|
||||||
|
END;
|
600
contrib/ltree/ltree_gist.c
Normal file
600
contrib/ltree/ltree_gist.c
Normal file
@ -0,0 +1,600 @@
|
|||||||
|
/*
|
||||||
|
* GiST support for ltree
|
||||||
|
* Teodor Sigaev <teodor@stack.net>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "ltree.h"
|
||||||
|
#include "access/gist.h"
|
||||||
|
#include "access/rtree.h"
|
||||||
|
#include "access/nbtree.h"
|
||||||
|
|
||||||
|
#include "crc32.h"
|
||||||
|
|
||||||
|
PG_FUNCTION_INFO_V1( ltree_gist_in );
|
||||||
|
Datum ltree_gist_in(PG_FUNCTION_ARGS);
|
||||||
|
PG_FUNCTION_INFO_V1( ltree_gist_out );
|
||||||
|
Datum ltree_gist_out(PG_FUNCTION_ARGS);
|
||||||
|
|
||||||
|
Datum
|
||||||
|
ltree_gist_in(PG_FUNCTION_ARGS) {
|
||||||
|
elog(ERROR,"Unimplemented");
|
||||||
|
PG_RETURN_DATUM(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
Datum
|
||||||
|
ltree_gist_out(PG_FUNCTION_ARGS) {
|
||||||
|
elog(ERROR,"Unimplemented");
|
||||||
|
PG_RETURN_DATUM(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
PG_FUNCTION_INFO_V1( ltree_compress );
|
||||||
|
Datum ltree_compress(PG_FUNCTION_ARGS);
|
||||||
|
PG_FUNCTION_INFO_V1( ltree_decompress );
|
||||||
|
Datum ltree_decompress(PG_FUNCTION_ARGS);
|
||||||
|
PG_FUNCTION_INFO_V1( ltree_same );
|
||||||
|
Datum ltree_same(PG_FUNCTION_ARGS);
|
||||||
|
PG_FUNCTION_INFO_V1( ltree_union );
|
||||||
|
Datum ltree_union(PG_FUNCTION_ARGS);
|
||||||
|
PG_FUNCTION_INFO_V1( ltree_penalty );
|
||||||
|
Datum ltree_penalty(PG_FUNCTION_ARGS);
|
||||||
|
PG_FUNCTION_INFO_V1( ltree_picksplit );
|
||||||
|
Datum ltree_picksplit(PG_FUNCTION_ARGS);
|
||||||
|
PG_FUNCTION_INFO_V1( ltree_consistent );
|
||||||
|
Datum ltree_consistent(PG_FUNCTION_ARGS);
|
||||||
|
|
||||||
|
#define ISEQ(a,b) ( (a)->numlevel == (b)->numlevel && ltree_compare(a,b)==0 )
|
||||||
|
#define GETENTRY(vec,pos) ((ltree_gist *) DatumGetPointer(((GISTENTRY *) VARDATA(vec))[(pos)].key))
|
||||||
|
|
||||||
|
Datum
|
||||||
|
ltree_compress(PG_FUNCTION_ARGS) {
|
||||||
|
GISTENTRY *entry = (GISTENTRY *)PG_GETARG_POINTER(0);
|
||||||
|
GISTENTRY *retval = entry;
|
||||||
|
|
||||||
|
if ( entry->leafkey ) { /* ltree */
|
||||||
|
ltree_gist *key;
|
||||||
|
ltree *val = (ltree*)DatumGetPointer(PG_DETOAST_DATUM(entry->key));
|
||||||
|
int4 len = LTG_HDRSIZE + val->len;
|
||||||
|
|
||||||
|
key = (ltree_gist*)palloc( len );
|
||||||
|
key->len = len;
|
||||||
|
key->flag = LTG_ONENODE;
|
||||||
|
memcpy( (void*)LTG_NODE(key), (void*)val, val->len);
|
||||||
|
|
||||||
|
if ( PointerGetDatum(val) != entry->key )
|
||||||
|
pfree(val);
|
||||||
|
|
||||||
|
retval = (GISTENTRY*)palloc( sizeof(GISTENTRY) );
|
||||||
|
gistentryinit(*retval, PointerGetDatum(key),
|
||||||
|
entry->rel, entry->page,
|
||||||
|
entry->offset, key->len, FALSE);
|
||||||
|
}
|
||||||
|
PG_RETURN_POINTER(retval);
|
||||||
|
}
|
||||||
|
|
||||||
|
Datum
|
||||||
|
ltree_decompress(PG_FUNCTION_ARGS) {
|
||||||
|
GISTENTRY *entry = (GISTENTRY *)PG_GETARG_POINTER(0);
|
||||||
|
ltree_gist *key = (ltree_gist*)DatumGetPointer( PG_DETOAST_DATUM(entry->key) );
|
||||||
|
|
||||||
|
if ( PointerGetDatum(key) != entry->key ) {
|
||||||
|
GISTENTRY *retval = (GISTENTRY*)palloc(sizeof(GISTENTRY));
|
||||||
|
gistentryinit(*retval, PointerGetDatum(key),
|
||||||
|
entry->rel, entry->page,
|
||||||
|
entry->offset, key->len, FALSE);
|
||||||
|
PG_RETURN_POINTER(retval);
|
||||||
|
}
|
||||||
|
PG_RETURN_POINTER(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
Datum
|
||||||
|
ltree_same(PG_FUNCTION_ARGS) {
|
||||||
|
ltree_gist* a=(ltree_gist*)PG_GETARG_POINTER(0);
|
||||||
|
ltree_gist* b=(ltree_gist*)PG_GETARG_POINTER(1);
|
||||||
|
bool *result = (bool *)PG_GETARG_POINTER(2);
|
||||||
|
|
||||||
|
*result = false;
|
||||||
|
if ( LTG_ISONENODE(a) != LTG_ISONENODE(b) )
|
||||||
|
PG_RETURN_POINTER(result);
|
||||||
|
|
||||||
|
if ( LTG_ISONENODE(a) ) {
|
||||||
|
*result = ( ISEQ(LTG_NODE(a), LTG_NODE(b)) ) ? true : false;
|
||||||
|
} else {
|
||||||
|
int4 i;
|
||||||
|
BITVECP sa=LTG_SIGN(a), sb=LTG_SIGN(b);
|
||||||
|
|
||||||
|
if ( LTG_ISALLTRUE(a) != LTG_ISALLTRUE(b) )
|
||||||
|
PG_RETURN_POINTER(result);
|
||||||
|
|
||||||
|
if ( !ISEQ(LTG_LNODE(a), LTG_LNODE(b)) )
|
||||||
|
PG_RETURN_POINTER(result);
|
||||||
|
if ( !ISEQ(LTG_RNODE(a), LTG_RNODE(b)) )
|
||||||
|
PG_RETURN_POINTER(result);
|
||||||
|
|
||||||
|
*result = true;
|
||||||
|
if ( !LTG_ISALLTRUE(a) )
|
||||||
|
LOOPBYTE(
|
||||||
|
if ( sa[i] != sb[i] ) {
|
||||||
|
*result = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
PG_RETURN_POINTER(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
hashing(BITVECP sign, ltree *t) {
|
||||||
|
int tlen = t->numlevel;
|
||||||
|
ltree_level *cur = LTREE_FIRST(t);
|
||||||
|
int hash;
|
||||||
|
|
||||||
|
while(tlen > 0) {
|
||||||
|
hash = crc32_sz( cur->name, cur->len );
|
||||||
|
HASH( sign, hash );
|
||||||
|
cur = LEVEL_NEXT(cur);
|
||||||
|
tlen--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Datum
|
||||||
|
ltree_union(PG_FUNCTION_ARGS) {
|
||||||
|
bytea *entryvec = (bytea *) PG_GETARG_POINTER(0);
|
||||||
|
int *size = (int *) PG_GETARG_POINTER(1);
|
||||||
|
BITVEC base;
|
||||||
|
int4 len = (VARSIZE(entryvec) - VARHDRSZ) / sizeof(GISTENTRY);
|
||||||
|
int4 i,j;
|
||||||
|
ltree_gist *result,*cur;
|
||||||
|
ltree *left=NULL, *right=NULL, *curtree;
|
||||||
|
bool isalltrue = false;
|
||||||
|
bool isleqr;
|
||||||
|
|
||||||
|
MemSet( (void*)base, 0, sizeof(BITVEC) );
|
||||||
|
for(j=0;j<len;j++) {
|
||||||
|
cur = GETENTRY(entryvec, j);
|
||||||
|
if ( LTG_ISONENODE(cur) ) {
|
||||||
|
curtree = LTG_NODE(cur);
|
||||||
|
hashing(base,curtree);
|
||||||
|
if ( !left || ltree_compare( left, curtree ) > 0 )
|
||||||
|
left = curtree;
|
||||||
|
if ( !right || ltree_compare( right, curtree ) < 0 )
|
||||||
|
right = curtree;
|
||||||
|
} else {
|
||||||
|
if ( isalltrue || LTG_ISALLTRUE(cur) )
|
||||||
|
isalltrue = true;
|
||||||
|
else {
|
||||||
|
BITVECP sc=LTG_SIGN(cur);
|
||||||
|
LOOPBYTE(
|
||||||
|
((unsigned char*)base)[i] |= sc[i];
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
curtree = LTG_LNODE(cur);
|
||||||
|
if ( !left || ltree_compare( left, curtree ) > 0 )
|
||||||
|
left = curtree;
|
||||||
|
curtree = LTG_RNODE(cur);
|
||||||
|
if ( !right || ltree_compare( right, curtree ) < 0 )
|
||||||
|
right = curtree;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( isalltrue == false ) {
|
||||||
|
isalltrue = true;
|
||||||
|
LOOPBYTE(
|
||||||
|
if ( ((unsigned char*)base)[i] != 0xff ) {
|
||||||
|
isalltrue = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
isleqr = ( left==right || ISEQ(left,right) ) ? true : false;
|
||||||
|
*size = LTG_HDRSIZE + ( (isalltrue) ? 0 : SIGLEN ) + left->len + ( (isleqr) ? 0 : right->len );
|
||||||
|
|
||||||
|
result = (ltree_gist*)palloc( *size );
|
||||||
|
result->len = *size;
|
||||||
|
result->flag = 0;
|
||||||
|
|
||||||
|
if ( isalltrue )
|
||||||
|
result->flag |= LTG_ALLTRUE;
|
||||||
|
else
|
||||||
|
memcpy( (void*)LTG_SIGN(result), base, SIGLEN );
|
||||||
|
|
||||||
|
memcpy( (void*)LTG_LNODE(result), (void*)left, left->len );
|
||||||
|
if ( isleqr )
|
||||||
|
result->flag |= LTG_NORIGHT;
|
||||||
|
else
|
||||||
|
memcpy( (void*)LTG_RNODE(result), (void*)right, right->len );
|
||||||
|
|
||||||
|
PG_RETURN_POINTER(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
Datum
|
||||||
|
ltree_penalty(PG_FUNCTION_ARGS) {
|
||||||
|
ltree_gist *origval = (ltree_gist*)DatumGetPointer( ( (GISTENTRY *)PG_GETARG_POINTER(0) )->key );
|
||||||
|
ltree_gist *newval = (ltree_gist*)DatumGetPointer( ( (GISTENTRY *)PG_GETARG_POINTER(1) )->key );
|
||||||
|
float *penalty = (float *) PG_GETARG_POINTER(2);
|
||||||
|
int4 cmpr,cmpl;
|
||||||
|
|
||||||
|
cmpl = ltree_compare( LTG_GETLNODE(origval), LTG_GETLNODE(newval) );
|
||||||
|
cmpr = ltree_compare( LTG_GETRNODE(newval), LTG_GETRNODE(origval));
|
||||||
|
|
||||||
|
*penalty = max( cmpl, 0 ) + max( cmpr, 0 );
|
||||||
|
|
||||||
|
PG_RETURN_POINTER(penalty);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* used for sorting */
|
||||||
|
typedef struct rix {
|
||||||
|
int index;
|
||||||
|
ltree *r;
|
||||||
|
} RIX;
|
||||||
|
|
||||||
|
static int
|
||||||
|
treekey_cmp(const void *a, const void *b) {
|
||||||
|
return ltree_compare(
|
||||||
|
((RIX *) a)->r,
|
||||||
|
((RIX *) b)->r
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Datum
|
||||||
|
ltree_picksplit(PG_FUNCTION_ARGS) {
|
||||||
|
bytea *entryvec = (bytea*) PG_GETARG_POINTER(0);
|
||||||
|
GIST_SPLITVEC *v = (GIST_SPLITVEC*) PG_GETARG_POINTER(1);
|
||||||
|
OffsetNumber j;
|
||||||
|
int4 i;
|
||||||
|
RIX *array;
|
||||||
|
OffsetNumber maxoff;
|
||||||
|
int nbytes;
|
||||||
|
int size;
|
||||||
|
ltree *lu_l,*lu_r, *ru_l, *ru_r;
|
||||||
|
ltree_gist *lu, *ru;
|
||||||
|
BITVEC ls,rs;
|
||||||
|
bool lisat=false, risat=false, isleqr;
|
||||||
|
|
||||||
|
memset( (void*)ls,0,sizeof(BITVEC) );
|
||||||
|
memset( (void*)rs,0,sizeof(BITVEC) );
|
||||||
|
maxoff = ((VARSIZE(entryvec) - VARHDRSZ) / sizeof(GISTENTRY)) - 1;
|
||||||
|
nbytes = (maxoff + 2) * sizeof(OffsetNumber);
|
||||||
|
v->spl_left = (OffsetNumber *) palloc(nbytes);
|
||||||
|
v->spl_right = (OffsetNumber *) palloc(nbytes);
|
||||||
|
v->spl_nleft = 0;
|
||||||
|
v->spl_nright = 0;
|
||||||
|
array = (RIX *) palloc(sizeof(RIX) * (maxoff + 1));
|
||||||
|
|
||||||
|
/* copy the data into RIXes, and sort the RIXes */
|
||||||
|
for (j = FirstOffsetNumber; j <= maxoff; j = OffsetNumberNext(j)) {
|
||||||
|
array[j].index = j;
|
||||||
|
lu = GETENTRY( entryvec, j ); /* use as tmp val */
|
||||||
|
array[j].r = LTG_GETLNODE(lu);
|
||||||
|
}
|
||||||
|
|
||||||
|
qsort((void *) &array[FirstOffsetNumber], maxoff - FirstOffsetNumber + 1,
|
||||||
|
sizeof(RIX), treekey_cmp);
|
||||||
|
|
||||||
|
lu_l = lu_r = ru_l = ru_r = NULL;
|
||||||
|
for (j = FirstOffsetNumber; j <= maxoff; j = OffsetNumberNext(j)) {
|
||||||
|
lu = GETENTRY( entryvec, array[j].index ); /* use as tmp val */
|
||||||
|
if (j <= (maxoff - FirstOffsetNumber + 1) / 2) {
|
||||||
|
v->spl_left[v->spl_nleft] = array[j].index;
|
||||||
|
v->spl_nleft++;
|
||||||
|
if ( lu_r==NULL || ltree_compare( LTG_GETRNODE(lu), lu_r ) > 0 )
|
||||||
|
lu_r = LTG_GETRNODE(lu);
|
||||||
|
if ( LTG_ISONENODE(lu) )
|
||||||
|
hashing(ls,LTG_NODE(lu));
|
||||||
|
else {
|
||||||
|
if ( lisat || LTG_ISALLTRUE(lu) )
|
||||||
|
lisat = true;
|
||||||
|
else {
|
||||||
|
BITVECP sc=LTG_SIGN(lu);
|
||||||
|
LOOPBYTE(
|
||||||
|
((unsigned char*)ls)[i] |= sc[i];
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
v->spl_right[v->spl_nright] = array[j].index;
|
||||||
|
v->spl_nright++;
|
||||||
|
if ( ru_r==NULL || ltree_compare( LTG_GETRNODE(lu), ru_r ) > 0 )
|
||||||
|
ru_r = LTG_GETRNODE(lu);
|
||||||
|
if ( LTG_ISONENODE(lu) )
|
||||||
|
hashing(rs,LTG_NODE(lu));
|
||||||
|
else {
|
||||||
|
if ( risat || LTG_ISALLTRUE(lu) )
|
||||||
|
risat = true;
|
||||||
|
else {
|
||||||
|
BITVECP sc=LTG_SIGN(lu);
|
||||||
|
LOOPBYTE(
|
||||||
|
((unsigned char*)rs)[i] |= sc[i];
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( lisat == false ) {
|
||||||
|
lisat = true;
|
||||||
|
LOOPBYTE(
|
||||||
|
if ( ((unsigned char*)ls)[i] != 0xff ) {
|
||||||
|
lisat = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( risat == false ) {
|
||||||
|
risat = true;
|
||||||
|
LOOPBYTE(
|
||||||
|
if ( ((unsigned char*)rs)[i] != 0xff ) {
|
||||||
|
risat = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
lu_l = LTG_GETLNODE( GETENTRY( entryvec, array[FirstOffsetNumber].index ) );
|
||||||
|
isleqr = ( lu_l==lu_r || ISEQ(lu_l,lu_r) ) ? true : false;
|
||||||
|
size = LTG_HDRSIZE + ( (lisat) ? 0 : SIGLEN ) + lu_l->len + ( (isleqr) ? 0 : lu_r->len );
|
||||||
|
lu = (ltree_gist*)palloc( size );
|
||||||
|
lu->len = size;
|
||||||
|
lu->flag = 0;
|
||||||
|
if ( lisat )
|
||||||
|
lu->flag |= LTG_ALLTRUE;
|
||||||
|
else
|
||||||
|
memcpy( (void*)LTG_SIGN(lu), ls, SIGLEN );
|
||||||
|
memcpy( (void*)LTG_LNODE(lu), (void*)lu_l, lu_l->len );
|
||||||
|
if ( isleqr )
|
||||||
|
lu->flag |= LTG_NORIGHT;
|
||||||
|
else
|
||||||
|
memcpy( (void*)LTG_RNODE(lu), (void*)lu_r, lu_r->len );
|
||||||
|
|
||||||
|
|
||||||
|
ru_l = LTG_GETLNODE( GETENTRY( entryvec, array[ 1 + ((maxoff - FirstOffsetNumber + 1) / 2) ].index ) );
|
||||||
|
isleqr = ( ru_l==ru_r || ISEQ(ru_l,ru_r) ) ? true : false;
|
||||||
|
size = LTG_HDRSIZE + ( (risat) ? 0 : SIGLEN ) + ru_l->len + ( (isleqr) ? 0 : ru_r->len );
|
||||||
|
ru = (ltree_gist*)palloc( size );
|
||||||
|
ru->len = size;
|
||||||
|
ru->flag = 0;
|
||||||
|
if ( risat )
|
||||||
|
ru->flag |= LTG_ALLTRUE;
|
||||||
|
else
|
||||||
|
memcpy( (void*)LTG_SIGN(ru), rs, SIGLEN );
|
||||||
|
memcpy( (void*)LTG_LNODE(ru), (void*)ru_l, ru_l->len );
|
||||||
|
if ( isleqr )
|
||||||
|
ru->flag |= LTG_NORIGHT;
|
||||||
|
else
|
||||||
|
memcpy( (void*)LTG_RNODE(ru), (void*)ru_r, ru_r->len );
|
||||||
|
|
||||||
|
pfree(array);
|
||||||
|
v->spl_ldatum = PointerGetDatum(lu);
|
||||||
|
v->spl_rdatum = PointerGetDatum(ru);
|
||||||
|
|
||||||
|
PG_RETURN_POINTER(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
gist_isparent(ltree_gist *key, ltree *query) {
|
||||||
|
int4 numlevel = query->numlevel;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for(i=query->numlevel;i>=0;i--) {
|
||||||
|
query->numlevel=i;
|
||||||
|
if ( ltree_compare(query,LTG_GETLNODE(key)) >=0 && ltree_compare(query,LTG_GETRNODE(key)) <= 0 ) {
|
||||||
|
query->numlevel = numlevel;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
query->numlevel = numlevel;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
gist_ischild(ltree_gist *key, ltree *query) {
|
||||||
|
ltree *left = LTG_GETLNODE(key);
|
||||||
|
ltree *right = LTG_GETRNODE(key);
|
||||||
|
int4 numlevelL = left->numlevel;
|
||||||
|
int4 numlevelR = right->numlevel;
|
||||||
|
bool res = true;
|
||||||
|
|
||||||
|
if ( numlevelL > query->numlevel )
|
||||||
|
left->numlevel = query->numlevel;
|
||||||
|
|
||||||
|
if ( ltree_compare(query,left) < 0 )
|
||||||
|
res = false;
|
||||||
|
|
||||||
|
if ( numlevelR > query->numlevel )
|
||||||
|
right->numlevel = query->numlevel;
|
||||||
|
|
||||||
|
if ( res && ltree_compare(query,right) > 0 )
|
||||||
|
res = false;
|
||||||
|
|
||||||
|
left->numlevel = numlevelL;
|
||||||
|
right->numlevel = numlevelR;
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
gist_qe(ltree_gist *key, lquery* query) {
|
||||||
|
lquery_level *curq = LQUERY_FIRST(query);
|
||||||
|
BITVECP sign = LTG_SIGN(key);
|
||||||
|
int qlen = query->numlevel;
|
||||||
|
|
||||||
|
if ( LTG_ISALLTRUE(key) )
|
||||||
|
return true;
|
||||||
|
|
||||||
|
while( qlen>0 ) {
|
||||||
|
if ( curq->numvar && LQL_CANLOOKSIGN(curq) ) {
|
||||||
|
bool isexist=false;
|
||||||
|
int vlen = curq->numvar;
|
||||||
|
lquery_variant *curv = LQL_FIRST(curq);
|
||||||
|
while( vlen>0 ) {
|
||||||
|
if ( GETBIT( sign, HASHVAL( curv->val ) ) ) {
|
||||||
|
isexist=true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
curv = LVAR_NEXT(curv);
|
||||||
|
vlen--;
|
||||||
|
}
|
||||||
|
if ( !isexist )
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
curq = LQL_NEXT(curq);
|
||||||
|
qlen--;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
gist_tqcmp(ltree* t, lquery* q) {
|
||||||
|
ltree_level *al = LTREE_FIRST(t);
|
||||||
|
lquery_level *ql = LQUERY_FIRST(q);
|
||||||
|
lquery_variant *bl;
|
||||||
|
int an = t->numlevel;
|
||||||
|
int bn = q->firstgood;
|
||||||
|
int res = 0;
|
||||||
|
|
||||||
|
while( an>0 && bn>0 ) {
|
||||||
|
bl = LQL_FIRST(ql);
|
||||||
|
if ( (res = strncmp( al->name, bl->name, min(al->len, bl->len))) == 0 ) {
|
||||||
|
if ( al->len != bl->len )
|
||||||
|
return al->len - bl->len;
|
||||||
|
} else
|
||||||
|
return res;
|
||||||
|
an--; bn--;
|
||||||
|
al = LEVEL_NEXT(al);
|
||||||
|
ql = LQL_NEXT(ql);
|
||||||
|
}
|
||||||
|
|
||||||
|
return t->numlevel - q->firstgood;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
gist_between(ltree_gist *key, lquery* query) {
|
||||||
|
ltree *left = LTG_GETLNODE(key);
|
||||||
|
ltree *right = LTG_GETRNODE(key);
|
||||||
|
int4 numlevelL = left->numlevel;
|
||||||
|
int4 numlevelR = right->numlevel;
|
||||||
|
bool res = true;
|
||||||
|
|
||||||
|
if ( query->firstgood == 0 )
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if ( numlevelL > query->firstgood )
|
||||||
|
left->numlevel = query->firstgood;
|
||||||
|
|
||||||
|
if ( gist_tqcmp(left,query) > 0 )
|
||||||
|
res = false;
|
||||||
|
|
||||||
|
if ( numlevelR > query->firstgood )
|
||||||
|
right->numlevel = query->firstgood;
|
||||||
|
|
||||||
|
if ( res && gist_tqcmp(right,query) < 0 )
|
||||||
|
res = false;
|
||||||
|
|
||||||
|
left->numlevel = numlevelL;
|
||||||
|
right->numlevel = numlevelR;
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
checkcondition_bit(void *checkval, ITEM* val ) {
|
||||||
|
return ( FLG_CANLOOKSIGN(val->flag) ) ? GETBIT( checkval, HASHVAL( val->val ) ) : true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
gist_qtxt(ltree_gist *key, ltxtquery* query) {
|
||||||
|
if ( LTG_ISALLTRUE(key) )
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return execute(
|
||||||
|
GETQUERY(query),
|
||||||
|
(void*)LTG_SIGN(key), false,
|
||||||
|
checkcondition_bit
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Datum
|
||||||
|
ltree_consistent(PG_FUNCTION_ARGS) {
|
||||||
|
GISTENTRY *entry = (GISTENTRY*)PG_GETARG_POINTER(0);
|
||||||
|
char *query = (char*)DatumGetPointer( PG_DETOAST_DATUM(PG_GETARG_DATUM(1)) );
|
||||||
|
ltree_gist *key = (ltree_gist*)DatumGetPointer( entry->key );
|
||||||
|
StrategyNumber strategy = (StrategyNumber) PG_GETARG_UINT16(2);
|
||||||
|
bool res = false;
|
||||||
|
|
||||||
|
#ifndef assert_enabled
|
||||||
|
#define assert_enabled 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
switch( strategy ) {
|
||||||
|
case BTLessStrategyNumber:
|
||||||
|
res = ( GIST_LEAF( entry ) ) ?
|
||||||
|
( ltree_compare((ltree*)query,LTG_NODE(key)) > 0 )
|
||||||
|
:
|
||||||
|
( ltree_compare((ltree*)query,LTG_GETLNODE(key)) >= 0 );
|
||||||
|
break;
|
||||||
|
case BTLessEqualStrategyNumber:
|
||||||
|
res = ( ltree_compare((ltree*)query,LTG_GETLNODE(key)) >= 0 );
|
||||||
|
break;
|
||||||
|
case BTEqualStrategyNumber:
|
||||||
|
if ( GIST_LEAF( entry ) )
|
||||||
|
res = ( ltree_compare((ltree*)query,LTG_NODE(key)) == 0 );
|
||||||
|
else
|
||||||
|
res = (
|
||||||
|
ltree_compare((ltree*)query,LTG_GETLNODE(key)) >= 0
|
||||||
|
&&
|
||||||
|
ltree_compare((ltree*)query,LTG_GETRNODE(key)) <= 0
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case BTGreaterEqualStrategyNumber:
|
||||||
|
res = ( ltree_compare((ltree*)query,LTG_GETRNODE(key)) <= 0 );
|
||||||
|
break;
|
||||||
|
case BTGreaterStrategyNumber:
|
||||||
|
res = ( GIST_LEAF( entry ) ) ?
|
||||||
|
( ltree_compare((ltree*)query,LTG_GETRNODE(key)) < 0 )
|
||||||
|
:
|
||||||
|
( ltree_compare((ltree*)query,LTG_GETRNODE(key)) <= 0 );
|
||||||
|
break;
|
||||||
|
case 10:
|
||||||
|
res = ( GIST_LEAF( entry ) ) ?
|
||||||
|
inner_isparent( (ltree*)query, LTG_NODE(key) )
|
||||||
|
:
|
||||||
|
gist_isparent( key, (ltree*)query);
|
||||||
|
break;
|
||||||
|
case 11:
|
||||||
|
res = ( GIST_LEAF( entry ) ) ?
|
||||||
|
inner_isparent( LTG_NODE(key), (ltree*)query)
|
||||||
|
:
|
||||||
|
gist_ischild( key, (ltree*)query);
|
||||||
|
break;
|
||||||
|
case 12:
|
||||||
|
case 13:
|
||||||
|
if ( GIST_LEAF( entry ) )
|
||||||
|
res = DatumGetBool( DirectFunctionCall2( ltq_regex,
|
||||||
|
PointerGetDatum( LTG_NODE(key) ),
|
||||||
|
PointerGetDatum( (lquery*)query )
|
||||||
|
) );
|
||||||
|
else
|
||||||
|
res = ( gist_qe(key, (lquery*)query) && gist_between(key, (lquery*)query) );
|
||||||
|
break;
|
||||||
|
case 14:
|
||||||
|
case 15:
|
||||||
|
if ( GIST_LEAF( entry ) )
|
||||||
|
res = DatumGetBool( DirectFunctionCall2( ltxtq_exec,
|
||||||
|
PointerGetDatum( LTG_NODE(key) ),
|
||||||
|
PointerGetDatum( (lquery*)query )
|
||||||
|
) );
|
||||||
|
else
|
||||||
|
res = gist_qtxt(key, (ltxtquery*)query);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
elog(ERROR,"Unknown StrategyNumber: %d", strategy);
|
||||||
|
}
|
||||||
|
PG_RETURN_BOOL(res);
|
||||||
|
}
|
||||||
|
|
433
contrib/ltree/ltree_io.c
Normal file
433
contrib/ltree/ltree_io.c
Normal file
@ -0,0 +1,433 @@
|
|||||||
|
/*
|
||||||
|
* in/out function for ltree and lquery
|
||||||
|
* Teodor Sigaev <teodor@stack.net>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "ltree.h"
|
||||||
|
#include <ctype.h>
|
||||||
|
#include "crc32.h"
|
||||||
|
|
||||||
|
PG_FUNCTION_INFO_V1(ltree_in);
|
||||||
|
Datum ltree_in(PG_FUNCTION_ARGS);
|
||||||
|
PG_FUNCTION_INFO_V1(ltree_out);
|
||||||
|
Datum ltree_out(PG_FUNCTION_ARGS);
|
||||||
|
|
||||||
|
PG_FUNCTION_INFO_V1(lquery_in);
|
||||||
|
Datum lquery_in(PG_FUNCTION_ARGS);
|
||||||
|
PG_FUNCTION_INFO_V1(lquery_out);
|
||||||
|
Datum lquery_out(PG_FUNCTION_ARGS);
|
||||||
|
|
||||||
|
|
||||||
|
#define UNCHAR elog(ERROR,"Syntax error in position %d near '%c'", ptr-buf, *ptr)
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char* start;
|
||||||
|
int len;
|
||||||
|
int flag;
|
||||||
|
} nodeitem;
|
||||||
|
|
||||||
|
#define LTPRS_WAITNAME 0
|
||||||
|
#define LTPRS_WAITDELIM 1
|
||||||
|
|
||||||
|
Datum
|
||||||
|
ltree_in(PG_FUNCTION_ARGS) {
|
||||||
|
char *buf = (char *) PG_GETARG_POINTER(0);
|
||||||
|
char *ptr;
|
||||||
|
nodeitem *list, *lptr;
|
||||||
|
int num=0, totallen = 0;
|
||||||
|
int state = LTPRS_WAITNAME;
|
||||||
|
ltree *result;
|
||||||
|
ltree_level *curlevel;
|
||||||
|
|
||||||
|
ptr=buf;
|
||||||
|
while( *ptr ) {
|
||||||
|
if ( *ptr == '.' )
|
||||||
|
num++;
|
||||||
|
ptr++;
|
||||||
|
}
|
||||||
|
|
||||||
|
list = lptr = (nodeitem*) palloc( sizeof(nodeitem)*(num+1) );
|
||||||
|
ptr=buf;
|
||||||
|
while( *ptr ) {
|
||||||
|
if ( state == LTPRS_WAITNAME ) {
|
||||||
|
if ( ISALNUM(*ptr) ) {
|
||||||
|
lptr->start = ptr;
|
||||||
|
state = LTPRS_WAITDELIM;
|
||||||
|
} else
|
||||||
|
UNCHAR;
|
||||||
|
} else if ( state == LTPRS_WAITDELIM ) {
|
||||||
|
if ( *ptr == '.' ) {
|
||||||
|
lptr->len = ptr - lptr->start;
|
||||||
|
if ( lptr->len > 255 )
|
||||||
|
elog(ERROR,"Name of level is too long (%d, must be < 256) in position %d",
|
||||||
|
lptr->len, lptr->start - buf);
|
||||||
|
totallen += lptr->len + LEVEL_HDRSIZE;
|
||||||
|
lptr++;
|
||||||
|
state = LTPRS_WAITNAME;
|
||||||
|
} else if ( !ISALNUM(*ptr) )
|
||||||
|
UNCHAR;
|
||||||
|
} else
|
||||||
|
elog(ERROR,"Inner error in parser");
|
||||||
|
ptr++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( state == LTPRS_WAITDELIM ) {
|
||||||
|
lptr->len = ptr - lptr->start;
|
||||||
|
if ( lptr->len > 255 )
|
||||||
|
elog(ERROR,"Name of level is too long (%d, must be < 256) in position %d",
|
||||||
|
lptr->len, lptr->start - buf);
|
||||||
|
totallen += lptr->len + LEVEL_HDRSIZE;
|
||||||
|
lptr++;
|
||||||
|
} else if ( ! (state == LTPRS_WAITNAME && lptr == list) )
|
||||||
|
elog(ERROR,"Unexpected end of line");
|
||||||
|
|
||||||
|
result = (ltree*)palloc( LTREE_HDRSIZE + totallen );
|
||||||
|
result->len = LTREE_HDRSIZE + totallen;
|
||||||
|
result->numlevel = lptr-list;
|
||||||
|
curlevel = LTREE_FIRST(result);
|
||||||
|
lptr=list;
|
||||||
|
while( lptr-list < result->numlevel ) {
|
||||||
|
curlevel->len = (uint8) lptr->len;
|
||||||
|
memcpy( curlevel->name, lptr->start, lptr->len);
|
||||||
|
curlevel = LEVEL_NEXT(curlevel);
|
||||||
|
lptr++;
|
||||||
|
}
|
||||||
|
|
||||||
|
pfree(list);
|
||||||
|
|
||||||
|
PG_RETURN_POINTER(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
Datum
|
||||||
|
ltree_out(PG_FUNCTION_ARGS) {
|
||||||
|
ltree *in = PG_GETARG_LTREE(0);
|
||||||
|
char *buf,*ptr;
|
||||||
|
int i;
|
||||||
|
ltree_level *curlevel;
|
||||||
|
|
||||||
|
ptr = buf = (char*)palloc( in->len );
|
||||||
|
curlevel = LTREE_FIRST(in);
|
||||||
|
for(i=0;i<in->numlevel;i++) {
|
||||||
|
if ( i!=0 ) {
|
||||||
|
*ptr = '.';
|
||||||
|
ptr++;
|
||||||
|
}
|
||||||
|
memcpy( ptr, curlevel->name, curlevel->len );
|
||||||
|
ptr+=curlevel->len;
|
||||||
|
curlevel = LEVEL_NEXT(curlevel);
|
||||||
|
}
|
||||||
|
|
||||||
|
*ptr='\0';
|
||||||
|
PG_FREE_IF_COPY(in,0);
|
||||||
|
|
||||||
|
PG_RETURN_POINTER(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
#define LQPRS_WAITLEVEL 0
|
||||||
|
#define LQPRS_WAITDELIM 1
|
||||||
|
#define LQPRS_WAITOPEN 2
|
||||||
|
#define LQPRS_WAITFNUM 3
|
||||||
|
#define LQPRS_WAITSNUM 4
|
||||||
|
#define LQPRS_WAITND 5
|
||||||
|
#define LQPRS_WAITCLOSE 6
|
||||||
|
#define LQPRS_WAITEND 7
|
||||||
|
#define LQPRS_WAITVAR 8
|
||||||
|
|
||||||
|
|
||||||
|
#define GETVAR(x) ( *((nodeitem**)LQL_FIRST(x)) )
|
||||||
|
|
||||||
|
Datum
|
||||||
|
lquery_in(PG_FUNCTION_ARGS) {
|
||||||
|
char *buf = (char *) PG_GETARG_POINTER(0);
|
||||||
|
char *ptr;
|
||||||
|
int num=0, totallen = 0, numOR=0;
|
||||||
|
int state = LQPRS_WAITLEVEL;
|
||||||
|
lquery *result;
|
||||||
|
nodeitem *lptr=NULL;
|
||||||
|
lquery_level *cur,*curqlevel, *tmpql;
|
||||||
|
lquery_variant *lrptr=NULL;
|
||||||
|
bool hasnot=false;
|
||||||
|
bool wasbad=false;
|
||||||
|
|
||||||
|
ptr=buf;
|
||||||
|
while( *ptr ) {
|
||||||
|
if ( *ptr == '.' )
|
||||||
|
num++;
|
||||||
|
else if ( *ptr == '|' )
|
||||||
|
numOR++;
|
||||||
|
ptr++;
|
||||||
|
}
|
||||||
|
|
||||||
|
num++;
|
||||||
|
curqlevel = tmpql = (lquery_level*) palloc( ( LQL_HDRSIZE+sizeof(nodeitem*) )*(num) );
|
||||||
|
memset((void*)tmpql,0, ( LQL_HDRSIZE+sizeof(nodeitem*) )*(num) );
|
||||||
|
ptr=buf;
|
||||||
|
while( *ptr ) {
|
||||||
|
if ( state==LQPRS_WAITLEVEL ) {
|
||||||
|
if ( ISALNUM(*ptr) ) {
|
||||||
|
GETVAR(curqlevel) = lptr = (nodeitem*)palloc( sizeof(nodeitem)*(numOR+1) );
|
||||||
|
memset((void*)GETVAR(curqlevel), 0,sizeof(nodeitem)*(numOR+1) );
|
||||||
|
lptr->start = ptr;
|
||||||
|
state = LQPRS_WAITDELIM;
|
||||||
|
curqlevel->numvar = 1;
|
||||||
|
} else if ( *ptr == '!' ) {
|
||||||
|
GETVAR(curqlevel) = lptr = (nodeitem*)palloc( sizeof(nodeitem)*(numOR+1) );
|
||||||
|
memset((void*)GETVAR(curqlevel), 0,sizeof(nodeitem)*(numOR+1) );
|
||||||
|
lptr->start = ptr+1;
|
||||||
|
state = LQPRS_WAITDELIM;
|
||||||
|
curqlevel->numvar = 1;
|
||||||
|
curqlevel->flag |= LQL_NOT;
|
||||||
|
hasnot=true;
|
||||||
|
} else if ( *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 ) {
|
||||||
|
if ( *ptr == '@' ) {
|
||||||
|
if ( lptr->start == ptr )
|
||||||
|
UNCHAR;
|
||||||
|
lptr->flag |= LVAR_INCASE;
|
||||||
|
curqlevel->flag |= LVAR_INCASE;
|
||||||
|
} else if ( *ptr == '*' ) {
|
||||||
|
if ( lptr->start == ptr )
|
||||||
|
UNCHAR;
|
||||||
|
lptr->flag |= LVAR_ANYEND;
|
||||||
|
curqlevel->flag |= LVAR_ANYEND;
|
||||||
|
} else if ( *ptr == '%' ) {
|
||||||
|
if ( lptr->start == ptr )
|
||||||
|
UNCHAR;
|
||||||
|
lptr->flag |= LVAR_SUBLEXEM;
|
||||||
|
curqlevel->flag |= LVAR_SUBLEXEM;
|
||||||
|
} else if ( *ptr == '|' ) {
|
||||||
|
lptr->len = ptr - lptr->start -
|
||||||
|
( ( lptr->flag & LVAR_SUBLEXEM ) ? 1 : 0 ) -
|
||||||
|
( ( lptr->flag & LVAR_INCASE ) ? 1 : 0 ) -
|
||||||
|
( ( lptr->flag & LVAR_ANYEND ) ? 1 : 0 );
|
||||||
|
if ( lptr->len > 255 )
|
||||||
|
elog(ERROR,"Name of level is too long (%d, must be < 256) in position %d",
|
||||||
|
lptr->len, lptr->start - buf);
|
||||||
|
state = LQPRS_WAITVAR;
|
||||||
|
} else if ( *ptr == '.' ) {
|
||||||
|
lptr->len = ptr - lptr->start -
|
||||||
|
( ( lptr->flag & LVAR_SUBLEXEM ) ? 1 : 0 ) -
|
||||||
|
( ( lptr->flag & LVAR_INCASE ) ? 1 : 0 ) -
|
||||||
|
( ( lptr->flag & LVAR_ANYEND ) ? 1 : 0 );
|
||||||
|
if ( lptr->len > 255 )
|
||||||
|
elog(ERROR,"Name of level is too long (%d, must be < 256) in position %d",
|
||||||
|
lptr->len, lptr->start - buf);
|
||||||
|
state = LQPRS_WAITLEVEL;
|
||||||
|
curqlevel++;
|
||||||
|
} else if ( ISALNUM(*ptr) ) {
|
||||||
|
if ( lptr->flag )
|
||||||
|
UNCHAR;
|
||||||
|
} else
|
||||||
|
UNCHAR;
|
||||||
|
} else if ( state == LQPRS_WAITOPEN ) {
|
||||||
|
if ( *ptr == '{' ) {
|
||||||
|
state = LQPRS_WAITFNUM;
|
||||||
|
} else if ( *ptr == '.' ) {
|
||||||
|
curqlevel->low=0;
|
||||||
|
curqlevel->high=0xffff;
|
||||||
|
curqlevel++;
|
||||||
|
state = LQPRS_WAITLEVEL;
|
||||||
|
} else
|
||||||
|
UNCHAR;
|
||||||
|
} else if ( state == LQPRS_WAITFNUM ) {
|
||||||
|
if ( *ptr == ',' ) {
|
||||||
|
state = LQPRS_WAITSNUM;
|
||||||
|
} else if ( isdigit(*ptr) ) {
|
||||||
|
curqlevel->low = atoi( ptr );
|
||||||
|
state = LQPRS_WAITND;
|
||||||
|
} else
|
||||||
|
UNCHAR;
|
||||||
|
} else if ( state == LQPRS_WAITSNUM ) {
|
||||||
|
if ( isdigit(*ptr) ) {
|
||||||
|
curqlevel->high = atoi( ptr );
|
||||||
|
state = LQPRS_WAITCLOSE;
|
||||||
|
} else if ( *ptr == '}' ) {
|
||||||
|
curqlevel->high = 0xffff;
|
||||||
|
state = LQPRS_WAITEND;
|
||||||
|
} else
|
||||||
|
UNCHAR;
|
||||||
|
} else if ( state == LQPRS_WAITCLOSE ) {
|
||||||
|
if ( *ptr == '}' )
|
||||||
|
state = LQPRS_WAITEND;
|
||||||
|
else if ( !isdigit(*ptr) )
|
||||||
|
UNCHAR;
|
||||||
|
} else if ( state == LQPRS_WAITND ) {
|
||||||
|
if ( *ptr == '}' ) {
|
||||||
|
curqlevel->high = curqlevel->low;
|
||||||
|
state = LQPRS_WAITEND;
|
||||||
|
} else if ( *ptr == ',' )
|
||||||
|
state = LQPRS_WAITSNUM;
|
||||||
|
else if ( !isdigit(*ptr) )
|
||||||
|
UNCHAR;
|
||||||
|
} else if ( state == LQPRS_WAITEND ) {
|
||||||
|
if ( *ptr == '.' ) {
|
||||||
|
state = LQPRS_WAITLEVEL;
|
||||||
|
curqlevel++;
|
||||||
|
} else
|
||||||
|
UNCHAR;
|
||||||
|
} else
|
||||||
|
elog(ERROR,"Inner error in parser");
|
||||||
|
ptr++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( state==LQPRS_WAITDELIM ) {
|
||||||
|
if ( lptr->start == ptr )
|
||||||
|
elog(ERROR,"Unexpected end of line");
|
||||||
|
lptr->len = ptr - lptr->start -
|
||||||
|
( ( lptr->flag & LVAR_SUBLEXEM ) ? 1 : 0 ) -
|
||||||
|
( ( lptr->flag & LVAR_INCASE ) ? 1 : 0 ) -
|
||||||
|
( ( lptr->flag & LVAR_ANYEND ) ? 1 : 0 );
|
||||||
|
if ( lptr->len==0 )
|
||||||
|
elog(ERROR,"Unexpected end of line");
|
||||||
|
if ( lptr->len > 255 )
|
||||||
|
elog(ERROR,"Name of level is too long (%d, must be < 256) in position %d",
|
||||||
|
lptr->len, lptr->start - buf);
|
||||||
|
} else if ( state == LQPRS_WAITOPEN ) {
|
||||||
|
curqlevel->high = 0xffff;
|
||||||
|
} else if ( state != LQPRS_WAITEND )
|
||||||
|
elog(ERROR,"Unexpected end of line");
|
||||||
|
|
||||||
|
curqlevel = tmpql;
|
||||||
|
totallen = LQUERY_HDRSIZE;
|
||||||
|
while( curqlevel-tmpql < num ) {
|
||||||
|
totallen += LQL_HDRSIZE;
|
||||||
|
if ( curqlevel->numvar ) {
|
||||||
|
lptr = GETVAR(curqlevel);
|
||||||
|
while( lptr-GETVAR(curqlevel) < curqlevel->numvar ) {
|
||||||
|
totallen += LVAR_HDRSIZE + lptr->len;
|
||||||
|
lptr++;
|
||||||
|
}
|
||||||
|
} else if ( curqlevel->low > curqlevel->high )
|
||||||
|
elog(ERROR,"Low limit(%d) is greater than upper(%d)",curqlevel->low,curqlevel->high );
|
||||||
|
curqlevel++;
|
||||||
|
}
|
||||||
|
|
||||||
|
result = (lquery*)palloc( totallen );
|
||||||
|
result->len = totallen;
|
||||||
|
result->numlevel = num;
|
||||||
|
result->firstgood = 0;
|
||||||
|
result->flag=0;
|
||||||
|
if ( hasnot )
|
||||||
|
result->flag |= LQUERY_HASNOT;
|
||||||
|
cur = LQUERY_FIRST(result);
|
||||||
|
curqlevel = tmpql;
|
||||||
|
while( curqlevel-tmpql < num ) {
|
||||||
|
memcpy(cur,curqlevel,LQL_HDRSIZE);
|
||||||
|
cur->totallen=LQL_HDRSIZE;
|
||||||
|
if ( curqlevel->numvar ) {
|
||||||
|
lrptr = LQL_FIRST(cur);
|
||||||
|
lptr = GETVAR(curqlevel);
|
||||||
|
while( lptr-GETVAR(curqlevel) < curqlevel->numvar ) {
|
||||||
|
cur->totallen += LVAR_HDRSIZE + lptr->len;
|
||||||
|
lrptr->len = lptr->len;
|
||||||
|
lrptr->flag = lptr->flag;
|
||||||
|
lrptr->val = crc32_sz((uint8 *) lptr->start, lptr->len);
|
||||||
|
memcpy( lrptr->name, lptr->start, lptr->len);
|
||||||
|
lptr++;
|
||||||
|
lrptr = LVAR_NEXT( lrptr );
|
||||||
|
}
|
||||||
|
pfree( GETVAR(curqlevel) );
|
||||||
|
if ( cur->numvar > 1 || cur->flag != 0 )
|
||||||
|
wasbad=true;
|
||||||
|
else if ( wasbad==false )
|
||||||
|
(result->firstgood)++;
|
||||||
|
} else
|
||||||
|
wasbad=true;
|
||||||
|
curqlevel++;
|
||||||
|
cur = LQL_NEXT(cur);
|
||||||
|
}
|
||||||
|
|
||||||
|
pfree(tmpql);
|
||||||
|
PG_RETURN_POINTER(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
Datum
|
||||||
|
lquery_out(PG_FUNCTION_ARGS) {
|
||||||
|
lquery *in = PG_GETARG_LQUERY(0);
|
||||||
|
char *buf,*ptr;
|
||||||
|
int i,j,totallen=0;
|
||||||
|
lquery_level *curqlevel;
|
||||||
|
lquery_variant *curtlevel;
|
||||||
|
|
||||||
|
curqlevel = LQUERY_FIRST(in);
|
||||||
|
for(i=0;i<in->numlevel;i++) {
|
||||||
|
if ( curqlevel->numvar )
|
||||||
|
totallen = (curqlevel->numvar*4) + 1 + curqlevel->totallen;
|
||||||
|
else
|
||||||
|
totallen = 2*11 + 4;
|
||||||
|
totallen++;
|
||||||
|
curqlevel = LQL_NEXT(curqlevel);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ptr = buf = (char*)palloc( totallen );
|
||||||
|
curqlevel = LQUERY_FIRST(in);
|
||||||
|
for(i=0;i<in->numlevel;i++) {
|
||||||
|
if ( i!=0 ) {
|
||||||
|
*ptr = '.';
|
||||||
|
ptr++;
|
||||||
|
}
|
||||||
|
if ( curqlevel->numvar ) {
|
||||||
|
if ( curqlevel->flag & LQL_NOT ) {
|
||||||
|
*ptr = '!';
|
||||||
|
ptr++;
|
||||||
|
}
|
||||||
|
curtlevel = LQL_FIRST(curqlevel);
|
||||||
|
for(j=0;j<curqlevel->numvar;j++) {
|
||||||
|
if ( j!=0 ) {
|
||||||
|
*ptr = '|';
|
||||||
|
ptr++;
|
||||||
|
}
|
||||||
|
memcpy( ptr, curtlevel->name, curtlevel->len );
|
||||||
|
ptr+=curtlevel->len;
|
||||||
|
if ( (curtlevel->flag & LVAR_SUBLEXEM) ) {
|
||||||
|
*ptr = '%';
|
||||||
|
ptr++;
|
||||||
|
}
|
||||||
|
if ( (curtlevel->flag & LVAR_INCASE) ) {
|
||||||
|
*ptr = '@';
|
||||||
|
ptr++;
|
||||||
|
}
|
||||||
|
if ( (curtlevel->flag & LVAR_ANYEND) ) {
|
||||||
|
*ptr = '*';
|
||||||
|
ptr++;
|
||||||
|
}
|
||||||
|
curtlevel = LVAR_NEXT(curtlevel);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if ( curqlevel->low == curqlevel->high ) {
|
||||||
|
sprintf(ptr,"*{%d}",curqlevel->low);
|
||||||
|
} else if ( curqlevel->low == 0 ) {
|
||||||
|
if ( curqlevel->high == 0xffff ) {
|
||||||
|
*ptr='*';
|
||||||
|
*(ptr+1)='\0';
|
||||||
|
} else
|
||||||
|
sprintf(ptr,"*{,%d}",curqlevel->high);
|
||||||
|
} else if ( curqlevel->high == 0xffff ) {
|
||||||
|
sprintf(ptr,"*{%d,}",curqlevel->low);
|
||||||
|
} else
|
||||||
|
sprintf(ptr,"*{%d,%d}", curqlevel->low, curqlevel->high);
|
||||||
|
ptr = strchr(ptr,'\0');
|
||||||
|
}
|
||||||
|
|
||||||
|
curqlevel = LQL_NEXT(curqlevel);
|
||||||
|
}
|
||||||
|
|
||||||
|
*ptr='\0';
|
||||||
|
PG_FREE_IF_COPY(in,0);
|
||||||
|
|
||||||
|
PG_RETURN_POINTER(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
310
contrib/ltree/ltree_op.c
Normal file
310
contrib/ltree/ltree_op.c
Normal file
@ -0,0 +1,310 @@
|
|||||||
|
/*
|
||||||
|
* op function for ltree
|
||||||
|
* Teodor Sigaev <teodor@stack.net>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "ltree.h"
|
||||||
|
#include <ctype.h>
|
||||||
|
|
||||||
|
/* compare functions */
|
||||||
|
PG_FUNCTION_INFO_V1(ltree_cmp);
|
||||||
|
PG_FUNCTION_INFO_V1(ltree_lt);
|
||||||
|
PG_FUNCTION_INFO_V1(ltree_le);
|
||||||
|
PG_FUNCTION_INFO_V1(ltree_eq);
|
||||||
|
PG_FUNCTION_INFO_V1(ltree_ne);
|
||||||
|
PG_FUNCTION_INFO_V1(ltree_ge);
|
||||||
|
PG_FUNCTION_INFO_V1(ltree_gt);
|
||||||
|
PG_FUNCTION_INFO_V1(nlevel);
|
||||||
|
PG_FUNCTION_INFO_V1(ltree_isparent);
|
||||||
|
PG_FUNCTION_INFO_V1(ltree_risparent);
|
||||||
|
PG_FUNCTION_INFO_V1(subltree);
|
||||||
|
PG_FUNCTION_INFO_V1(subpath);
|
||||||
|
PG_FUNCTION_INFO_V1(ltree_addltree);
|
||||||
|
PG_FUNCTION_INFO_V1(ltree_addtext);
|
||||||
|
PG_FUNCTION_INFO_V1(ltree_textadd);
|
||||||
|
Datum ltree_cmp(PG_FUNCTION_ARGS);
|
||||||
|
Datum ltree_lt(PG_FUNCTION_ARGS);
|
||||||
|
Datum ltree_le(PG_FUNCTION_ARGS);
|
||||||
|
Datum ltree_eq(PG_FUNCTION_ARGS);
|
||||||
|
Datum ltree_ne(PG_FUNCTION_ARGS);
|
||||||
|
Datum ltree_ge(PG_FUNCTION_ARGS);
|
||||||
|
Datum ltree_gt(PG_FUNCTION_ARGS);
|
||||||
|
Datum nlevel(PG_FUNCTION_ARGS);
|
||||||
|
Datum subltree(PG_FUNCTION_ARGS);
|
||||||
|
Datum subpath(PG_FUNCTION_ARGS);
|
||||||
|
Datum ltree_addltree(PG_FUNCTION_ARGS);
|
||||||
|
Datum ltree_addtext(PG_FUNCTION_ARGS);
|
||||||
|
Datum ltree_textadd(PG_FUNCTION_ARGS);
|
||||||
|
|
||||||
|
int
|
||||||
|
ltree_compare(const ltree *a, const ltree *b) {
|
||||||
|
ltree_level *al = LTREE_FIRST(a);
|
||||||
|
ltree_level *bl = LTREE_FIRST(b);
|
||||||
|
int an = a->numlevel;
|
||||||
|
int bn = b->numlevel;
|
||||||
|
int res = 0;
|
||||||
|
|
||||||
|
while( an>0 && bn>0 ) {
|
||||||
|
if ( (res = strncmp( al->name, bl->name, min(al->len, bl->len))) == 0 ) {
|
||||||
|
if ( al->len != bl->len )
|
||||||
|
return (al->len - bl->len)*10*(an+1);
|
||||||
|
} else
|
||||||
|
return res*10*(an+1);
|
||||||
|
|
||||||
|
an--; bn--;
|
||||||
|
al = LEVEL_NEXT(al);
|
||||||
|
bl = LEVEL_NEXT(bl);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (a->numlevel - b->numlevel)*10*(an+1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#define RUNCMP \
|
||||||
|
ltree *a = PG_GETARG_LTREE(0); \
|
||||||
|
ltree *b = PG_GETARG_LTREE(1); \
|
||||||
|
int res = ltree_compare(a,b); \
|
||||||
|
PG_FREE_IF_COPY(a,0); \
|
||||||
|
PG_FREE_IF_COPY(b,1); \
|
||||||
|
|
||||||
|
Datum
|
||||||
|
ltree_cmp(PG_FUNCTION_ARGS) {
|
||||||
|
RUNCMP
|
||||||
|
PG_RETURN_INT32(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
Datum
|
||||||
|
ltree_lt(PG_FUNCTION_ARGS) {
|
||||||
|
RUNCMP
|
||||||
|
PG_RETURN_BOOL( (res<0) ? true : false );
|
||||||
|
}
|
||||||
|
|
||||||
|
Datum
|
||||||
|
ltree_le(PG_FUNCTION_ARGS) {
|
||||||
|
RUNCMP
|
||||||
|
PG_RETURN_BOOL( (res<=0) ? true : false );
|
||||||
|
}
|
||||||
|
|
||||||
|
Datum
|
||||||
|
ltree_eq(PG_FUNCTION_ARGS) {
|
||||||
|
RUNCMP
|
||||||
|
PG_RETURN_BOOL( (res==0) ? true : false );
|
||||||
|
}
|
||||||
|
|
||||||
|
Datum
|
||||||
|
ltree_ge(PG_FUNCTION_ARGS) {
|
||||||
|
RUNCMP
|
||||||
|
PG_RETURN_BOOL( (res>=0) ? true : false );
|
||||||
|
}
|
||||||
|
|
||||||
|
Datum
|
||||||
|
ltree_gt(PG_FUNCTION_ARGS) {
|
||||||
|
RUNCMP
|
||||||
|
PG_RETURN_BOOL( (res>0) ? true : false );
|
||||||
|
}
|
||||||
|
|
||||||
|
Datum
|
||||||
|
ltree_ne(PG_FUNCTION_ARGS) {
|
||||||
|
RUNCMP
|
||||||
|
PG_RETURN_BOOL( (res!=0) ? true : false );
|
||||||
|
}
|
||||||
|
|
||||||
|
Datum
|
||||||
|
nlevel(PG_FUNCTION_ARGS) {
|
||||||
|
ltree *a = PG_GETARG_LTREE(0);
|
||||||
|
int res = a->numlevel;
|
||||||
|
PG_FREE_IF_COPY(a,0);
|
||||||
|
PG_RETURN_INT32(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
inner_isparent(const ltree *c, const ltree *p) {
|
||||||
|
ltree_level *cl = LTREE_FIRST(c);
|
||||||
|
ltree_level *pl = LTREE_FIRST(p);
|
||||||
|
int pn = p->numlevel;
|
||||||
|
|
||||||
|
if ( pn > c->numlevel )
|
||||||
|
return false;
|
||||||
|
|
||||||
|
while( pn>0 ) {
|
||||||
|
if ( cl->len != pl->len )
|
||||||
|
return false;
|
||||||
|
if ( strncmp( cl->name, pl->name, cl->len ) )
|
||||||
|
return false;
|
||||||
|
|
||||||
|
pn--;
|
||||||
|
cl = LEVEL_NEXT(cl);
|
||||||
|
pl = LEVEL_NEXT(pl);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Datum
|
||||||
|
ltree_isparent(PG_FUNCTION_ARGS) {
|
||||||
|
ltree *c = PG_GETARG_LTREE(1);
|
||||||
|
ltree *p = PG_GETARG_LTREE(0);
|
||||||
|
bool res = inner_isparent(c,p);
|
||||||
|
PG_FREE_IF_COPY(c,1);
|
||||||
|
PG_FREE_IF_COPY(p,0);
|
||||||
|
PG_RETURN_BOOL( res );
|
||||||
|
}
|
||||||
|
|
||||||
|
Datum
|
||||||
|
ltree_risparent(PG_FUNCTION_ARGS) {
|
||||||
|
ltree *c = PG_GETARG_LTREE(0);
|
||||||
|
ltree *p = PG_GETARG_LTREE(1);
|
||||||
|
bool res = inner_isparent(c,p);
|
||||||
|
PG_FREE_IF_COPY(c,0);
|
||||||
|
PG_FREE_IF_COPY(p,1);
|
||||||
|
PG_RETURN_BOOL( res );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static ltree*
|
||||||
|
inner_subltree(ltree *t, int4 startpos, int4 endpos) {
|
||||||
|
char *start=NULL,*end=NULL;
|
||||||
|
ltree_level *ptr = LTREE_FIRST(t);
|
||||||
|
ltree *res;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
if ( startpos <0 || endpos <0 || startpos>=t->numlevel || startpos >= endpos )
|
||||||
|
elog(ERROR,"Wrong positions");
|
||||||
|
|
||||||
|
if ( endpos > t->numlevel )
|
||||||
|
endpos = t->numlevel;
|
||||||
|
|
||||||
|
for(i=0;i<endpos ;i++) {
|
||||||
|
if ( i==startpos )
|
||||||
|
start = (char*)ptr;
|
||||||
|
if ( i==endpos-1 ) {
|
||||||
|
end = (char*)LEVEL_NEXT(ptr);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
ptr = LEVEL_NEXT(ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
res=(ltree*)palloc( LTREE_HDRSIZE + (end-start) );
|
||||||
|
res->len = LTREE_HDRSIZE + (end-start);
|
||||||
|
res->numlevel = endpos-startpos;
|
||||||
|
|
||||||
|
memcpy( LTREE_FIRST(res), start, end-start);
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
Datum
|
||||||
|
subltree(PG_FUNCTION_ARGS) {
|
||||||
|
ltree *t = PG_GETARG_LTREE(0);
|
||||||
|
ltree *res = inner_subltree(t,PG_GETARG_INT32(1),PG_GETARG_INT32(2));
|
||||||
|
|
||||||
|
PG_FREE_IF_COPY(t,0);
|
||||||
|
PG_RETURN_POINTER(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
Datum
|
||||||
|
subpath(PG_FUNCTION_ARGS) {
|
||||||
|
ltree *t = PG_GETARG_LTREE(0);
|
||||||
|
int4 start = PG_GETARG_INT32(1);
|
||||||
|
int4 len = ( fcinfo->nargs==3 ) ? PG_GETARG_INT32(2) : 0;
|
||||||
|
int4 end;
|
||||||
|
ltree *res;
|
||||||
|
|
||||||
|
end = start+len;
|
||||||
|
|
||||||
|
if ( start < 0 ) {
|
||||||
|
start = t->numlevel + start;
|
||||||
|
end = start+len;
|
||||||
|
}
|
||||||
|
if ( start < 0 ) { /* start > t->numlevel */
|
||||||
|
start = t->numlevel + start;
|
||||||
|
end = start+len;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( len < 0 )
|
||||||
|
end = t->numlevel + len;
|
||||||
|
else if ( len == 0 )
|
||||||
|
end = 0xffff;
|
||||||
|
|
||||||
|
res = inner_subltree(t,start,end);
|
||||||
|
|
||||||
|
PG_FREE_IF_COPY(t,0);
|
||||||
|
PG_RETURN_POINTER(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ltree*
|
||||||
|
ltree_concat( ltree *a, ltree *b) {
|
||||||
|
ltree *r;
|
||||||
|
r=(ltree*)palloc( a->len + b->len - LTREE_HDRSIZE);
|
||||||
|
r->len = a->len + b->len - LTREE_HDRSIZE;
|
||||||
|
r->numlevel = a->numlevel + b->numlevel;
|
||||||
|
|
||||||
|
memcpy( LTREE_FIRST(r), LTREE_FIRST(a), a->len - LTREE_HDRSIZE);
|
||||||
|
memcpy( ((char*)LTREE_FIRST(r))+ a->len - LTREE_HDRSIZE, LTREE_FIRST(b), b->len -
|
||||||
|
LTREE_HDRSIZE);
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
Datum
|
||||||
|
ltree_addltree(PG_FUNCTION_ARGS) {
|
||||||
|
ltree *a = PG_GETARG_LTREE(0);
|
||||||
|
ltree *b = PG_GETARG_LTREE(1);
|
||||||
|
ltree *r;
|
||||||
|
|
||||||
|
r = ltree_concat(a, b);
|
||||||
|
PG_FREE_IF_COPY(a,0);
|
||||||
|
PG_FREE_IF_COPY(b,1);
|
||||||
|
PG_RETURN_POINTER(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
Datum
|
||||||
|
ltree_addtext(PG_FUNCTION_ARGS) {
|
||||||
|
ltree *a = PG_GETARG_LTREE(0);
|
||||||
|
text *b = PG_GETARG_TEXT_P(1);
|
||||||
|
char *s;
|
||||||
|
ltree *r,*tmp;
|
||||||
|
|
||||||
|
s = (char*)palloc( VARSIZE(b) - VARHDRSZ+1 );
|
||||||
|
memcpy(s, VARDATA(b), VARSIZE(b) - VARHDRSZ );
|
||||||
|
s[VARSIZE(b) - VARHDRSZ] = '\0';
|
||||||
|
|
||||||
|
tmp = (ltree*)DatumGetPointer( DirectFunctionCall1(
|
||||||
|
ltree_in,
|
||||||
|
PointerGetDatum(s)
|
||||||
|
) );
|
||||||
|
|
||||||
|
pfree(s);
|
||||||
|
|
||||||
|
r = ltree_concat(a,tmp);
|
||||||
|
|
||||||
|
pfree( tmp );
|
||||||
|
|
||||||
|
PG_FREE_IF_COPY(a,0);
|
||||||
|
PG_FREE_IF_COPY(b,1);
|
||||||
|
PG_RETURN_POINTER(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
Datum
|
||||||
|
ltree_textadd(PG_FUNCTION_ARGS) {
|
||||||
|
ltree *a = PG_GETARG_LTREE(1);
|
||||||
|
text *b = PG_GETARG_TEXT_P(0);
|
||||||
|
char *s;
|
||||||
|
ltree *r,*tmp;
|
||||||
|
|
||||||
|
s = (char*)palloc( VARSIZE(b) - VARHDRSZ + 1 );
|
||||||
|
memcpy(s, VARDATA(b), VARSIZE(b) - VARHDRSZ );
|
||||||
|
s[VARSIZE(b) - VARHDRSZ] = '\0';
|
||||||
|
|
||||||
|
tmp = (ltree*)DatumGetPointer( DirectFunctionCall1(
|
||||||
|
ltree_in,
|
||||||
|
PointerGetDatum(s)
|
||||||
|
) );
|
||||||
|
|
||||||
|
pfree(s);
|
||||||
|
|
||||||
|
r = ltree_concat(tmp,a);
|
||||||
|
|
||||||
|
pfree( tmp );
|
||||||
|
|
||||||
|
PG_FREE_IF_COPY(a,1);
|
||||||
|
PG_FREE_IF_COPY(b,0);
|
||||||
|
PG_RETURN_POINTER(r);
|
||||||
|
}
|
16
contrib/ltree/ltreetest.sql
Normal file
16
contrib/ltree/ltreetest.sql
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
create table test ( path ltree);
|
||||||
|
insert into test values ('Top');
|
||||||
|
insert into test values ('Top.Science');
|
||||||
|
insert into test values ('Top.Science.Astronomy');
|
||||||
|
insert into test values ('Top.Science.Astronomy.Astrophysics');
|
||||||
|
insert into test values ('Top.Science.Astronomy.Cosmology');
|
||||||
|
insert into test values ('Top.Hobbies');
|
||||||
|
insert into test values ('Top.Hobbies.Amateurs_Astronomy');
|
||||||
|
insert into test values ('Top.Collections');
|
||||||
|
insert into test values ('Top.Collections.Pictures');
|
||||||
|
insert into test values ('Top.Collections.Pictures.Astronomy');
|
||||||
|
insert into test values ('Top.Collections.Pictures.Astronomy.Stars');
|
||||||
|
insert into test values ('Top.Collections.Pictures.Astronomy.Galaxies');
|
||||||
|
insert into test values ('Top.Collections.Pictures.Astronomy.Astronauts');
|
||||||
|
create index path_gist_idx on test using gist(path);
|
||||||
|
create index path_idx on test using btree(path);
|
484
contrib/ltree/ltxtquery_io.c
Normal file
484
contrib/ltree/ltxtquery_io.c
Normal file
@ -0,0 +1,484 @@
|
|||||||
|
/*
|
||||||
|
* txtquery io
|
||||||
|
* Teodor Sigaev <teodor@stack.net>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "ltree.h"
|
||||||
|
#include <ctype.h>
|
||||||
|
#include "crc32.h"
|
||||||
|
|
||||||
|
PG_FUNCTION_INFO_V1(ltxtq_in);
|
||||||
|
Datum ltxtq_in(PG_FUNCTION_ARGS);
|
||||||
|
PG_FUNCTION_INFO_V1(ltxtq_out);
|
||||||
|
Datum ltxtq_out(PG_FUNCTION_ARGS);
|
||||||
|
|
||||||
|
|
||||||
|
/* parser's states */
|
||||||
|
#define WAITOPERAND 1
|
||||||
|
#define INOPERAND 2
|
||||||
|
#define WAITOPERATOR 3
|
||||||
|
|
||||||
|
/*
|
||||||
|
* node of query tree, also used
|
||||||
|
* for storing polish notation in parser
|
||||||
|
*/
|
||||||
|
typedef struct NODE {
|
||||||
|
int4 type;
|
||||||
|
int4 val;
|
||||||
|
int2 distance;
|
||||||
|
int2 length;
|
||||||
|
uint16 flag;
|
||||||
|
struct NODE *next;
|
||||||
|
} NODE;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char *buf;
|
||||||
|
int4 state;
|
||||||
|
int4 count;
|
||||||
|
/* reverse polish notation in list (for temprorary usage) */
|
||||||
|
NODE *str;
|
||||||
|
/* number in str */
|
||||||
|
int4 num;
|
||||||
|
|
||||||
|
/* user-friendly operand */
|
||||||
|
int4 lenop;
|
||||||
|
int4 sumlen;
|
||||||
|
char *op;
|
||||||
|
char *curop;
|
||||||
|
} QPRS_STATE;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* get token from query string
|
||||||
|
*/
|
||||||
|
static int4
|
||||||
|
gettoken_query(QPRS_STATE * state, int4 *val, int4 *lenval, char **strval, uint16 *flag)
|
||||||
|
{
|
||||||
|
while (1)
|
||||||
|
{
|
||||||
|
switch (state->state)
|
||||||
|
{
|
||||||
|
case WAITOPERAND:
|
||||||
|
if (*(state->buf) == '!')
|
||||||
|
{
|
||||||
|
(state->buf)++;
|
||||||
|
*val = (int4) '!';
|
||||||
|
return OPR;
|
||||||
|
}
|
||||||
|
else if (*(state->buf) == '(')
|
||||||
|
{
|
||||||
|
state->count++;
|
||||||
|
(state->buf)++;
|
||||||
|
return OPEN;
|
||||||
|
}
|
||||||
|
else if ( ISALNUM(*(state->buf)) )
|
||||||
|
{
|
||||||
|
state->state = INOPERAND;
|
||||||
|
*strval = state->buf;
|
||||||
|
*lenval = 1;
|
||||||
|
*flag = 0;
|
||||||
|
} else if ( !isspace(*(state->buf)) )
|
||||||
|
elog(ERROR,"Operand syntax error");
|
||||||
|
break;
|
||||||
|
case INOPERAND:
|
||||||
|
if ( ISALNUM(*(state->buf)) ) {
|
||||||
|
if ( *flag )
|
||||||
|
elog(ERROR,"Modificators syntax error");
|
||||||
|
(*lenval)++;
|
||||||
|
} else if ( *(state->buf) == '%' ) {
|
||||||
|
*flag |= LVAR_SUBLEXEM;
|
||||||
|
} else if ( *(state->buf) == '@' ) {
|
||||||
|
*flag |= LVAR_INCASE;
|
||||||
|
} else if ( *(state->buf) == '*' ) {
|
||||||
|
*flag |= LVAR_ANYEND;
|
||||||
|
} else {
|
||||||
|
state->state = WAITOPERATOR;
|
||||||
|
return VAL;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case WAITOPERATOR:
|
||||||
|
if (*(state->buf) == '&' || *(state->buf) == '|')
|
||||||
|
{
|
||||||
|
state->state = WAITOPERAND;
|
||||||
|
*val = (int4) *(state->buf);
|
||||||
|
(state->buf)++;
|
||||||
|
return OPR;
|
||||||
|
}
|
||||||
|
else if (*(state->buf) == ')')
|
||||||
|
{
|
||||||
|
(state->buf)++;
|
||||||
|
state->count--;
|
||||||
|
return (state->count < 0) ? ERR : CLOSE;
|
||||||
|
}
|
||||||
|
else if (*(state->buf) == '\0')
|
||||||
|
return (state->count) ? ERR : END;
|
||||||
|
else if (*(state->buf) != ' ')
|
||||||
|
return ERR;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return ERR;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
(state->buf)++;
|
||||||
|
}
|
||||||
|
return END;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* push new one in polish notation reverse view
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
pushquery(QPRS_STATE * state, int4 type, int4 val, int4 distance, int4 lenval, uint16 flag)
|
||||||
|
{
|
||||||
|
NODE *tmp = (NODE *) palloc(sizeof(NODE));
|
||||||
|
|
||||||
|
tmp->type = type;
|
||||||
|
tmp->val = val;
|
||||||
|
tmp->flag = flag;
|
||||||
|
if (distance > 0xffff)
|
||||||
|
elog(ERROR, "Value is too big");
|
||||||
|
if (lenval > 0xff)
|
||||||
|
elog(ERROR, "Operand is too long");
|
||||||
|
tmp->distance = distance;
|
||||||
|
tmp->length = lenval;
|
||||||
|
tmp->next = state->str;
|
||||||
|
state->str = tmp;
|
||||||
|
state->num++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This function is used for query_txt parsing
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
pushval_asis(QPRS_STATE * state, int type, char *strval, int lenval, uint16 flag)
|
||||||
|
{
|
||||||
|
if (lenval > 0xffff)
|
||||||
|
elog(ERROR, "Word is too long");
|
||||||
|
|
||||||
|
pushquery(state, type, crc32_sz((uint8 *) strval, lenval),
|
||||||
|
state->curop - state->op, lenval, flag);
|
||||||
|
|
||||||
|
while (state->curop - state->op + lenval + 1 >= state->lenop)
|
||||||
|
{
|
||||||
|
int4 tmp = state->curop - state->op;
|
||||||
|
|
||||||
|
state->lenop *= 2;
|
||||||
|
state->op = (char *) repalloc((void *) state->op, state->lenop);
|
||||||
|
state->curop = state->op + tmp;
|
||||||
|
}
|
||||||
|
memcpy((void *) state->curop, (void *) strval, lenval);
|
||||||
|
state->curop += lenval;
|
||||||
|
*(state->curop) = '\0';
|
||||||
|
state->curop++;
|
||||||
|
state->sumlen += lenval + 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define STACKDEPTH 32
|
||||||
|
/*
|
||||||
|
* make polish notaion of query
|
||||||
|
*/
|
||||||
|
static int4
|
||||||
|
makepol(QPRS_STATE * state)
|
||||||
|
{
|
||||||
|
int4 val,
|
||||||
|
type;
|
||||||
|
int4 lenval;
|
||||||
|
char *strval;
|
||||||
|
int4 stack[STACKDEPTH];
|
||||||
|
int4 lenstack = 0;
|
||||||
|
uint16 flag;
|
||||||
|
|
||||||
|
while ((type = gettoken_query(state, &val, &lenval, &strval,&flag)) != END) {
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case VAL:
|
||||||
|
pushval_asis(state, VAL, strval, lenval,flag);
|
||||||
|
while (lenstack && (stack[lenstack - 1] == (int4) '&' ||
|
||||||
|
stack[lenstack - 1] == (int4) '!'))
|
||||||
|
{
|
||||||
|
lenstack--;
|
||||||
|
pushquery(state, OPR, stack[lenstack], 0, 0, 0);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case OPR:
|
||||||
|
if (lenstack && val == (int4) '|')
|
||||||
|
pushquery(state, OPR, val, 0, 0, 0);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (lenstack == STACKDEPTH)
|
||||||
|
elog(ERROR, "Stack too short");
|
||||||
|
stack[lenstack] = val;
|
||||||
|
lenstack++;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case OPEN:
|
||||||
|
if (makepol(state) == ERR)
|
||||||
|
return ERR;
|
||||||
|
if (lenstack && (stack[lenstack - 1] == (int4) '&' ||
|
||||||
|
stack[lenstack - 1] == (int4) '!'))
|
||||||
|
{
|
||||||
|
lenstack--;
|
||||||
|
pushquery(state, OPR, stack[lenstack], 0, 0, 0);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case CLOSE:
|
||||||
|
while (lenstack)
|
||||||
|
{
|
||||||
|
lenstack--;
|
||||||
|
pushquery(state, OPR, stack[lenstack], 0, 0, 0);
|
||||||
|
};
|
||||||
|
return END;
|
||||||
|
break;
|
||||||
|
case ERR:
|
||||||
|
default:
|
||||||
|
elog(ERROR, "Syntax error");
|
||||||
|
return ERR;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (lenstack) {
|
||||||
|
lenstack--;
|
||||||
|
pushquery(state, OPR, stack[lenstack], 0, 0, 0);
|
||||||
|
};
|
||||||
|
return END;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
findoprnd(ITEM * ptr, int4 *pos)
|
||||||
|
{
|
||||||
|
if (ptr[*pos].type == VAL || ptr[*pos].type == VALTRUE)
|
||||||
|
{
|
||||||
|
ptr[*pos].left = 0;
|
||||||
|
(*pos)++;
|
||||||
|
}
|
||||||
|
else if (ptr[*pos].val == (int4) '!')
|
||||||
|
{
|
||||||
|
ptr[*pos].left = 1;
|
||||||
|
(*pos)++;
|
||||||
|
findoprnd(ptr, pos);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ITEM *curitem = &ptr[*pos];
|
||||||
|
int4 tmp = *pos;
|
||||||
|
|
||||||
|
(*pos)++;
|
||||||
|
findoprnd(ptr, pos);
|
||||||
|
curitem->left = *pos - tmp;
|
||||||
|
findoprnd(ptr, pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* input
|
||||||
|
*/
|
||||||
|
static ltxtquery *
|
||||||
|
queryin(char *buf)
|
||||||
|
{
|
||||||
|
QPRS_STATE state;
|
||||||
|
int4 i;
|
||||||
|
ltxtquery *query;
|
||||||
|
int4 commonlen;
|
||||||
|
ITEM *ptr;
|
||||||
|
NODE *tmp;
|
||||||
|
int4 pos = 0;
|
||||||
|
|
||||||
|
#ifdef BS_DEBUG
|
||||||
|
char pbuf[16384],
|
||||||
|
*cur;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* init state */
|
||||||
|
state.buf = buf;
|
||||||
|
state.state = WAITOPERAND;
|
||||||
|
state.count = 0;
|
||||||
|
state.num = 0;
|
||||||
|
state.str = NULL;
|
||||||
|
|
||||||
|
/* init list of operand */
|
||||||
|
state.sumlen = 0;
|
||||||
|
state.lenop = 64;
|
||||||
|
state.curop = state.op = (char *) palloc(state.lenop);
|
||||||
|
*(state.curop) = '\0';
|
||||||
|
|
||||||
|
/* parse query & make polish notation (postfix, but in reverse order) */
|
||||||
|
makepol(&state);
|
||||||
|
if (!state.num)
|
||||||
|
elog(ERROR, "Empty query");
|
||||||
|
/* make finish struct */
|
||||||
|
commonlen = COMPUTESIZE(state.num, state.sumlen);
|
||||||
|
query = (ltxtquery *) palloc(commonlen);
|
||||||
|
query->len = commonlen;
|
||||||
|
query->size = state.num;
|
||||||
|
ptr = GETQUERY(query);
|
||||||
|
|
||||||
|
/* set item in polish notation */
|
||||||
|
for (i = 0; i < state.num; i++)
|
||||||
|
{
|
||||||
|
ptr[i].type = state.str->type;
|
||||||
|
ptr[i].val = state.str->val;
|
||||||
|
ptr[i].distance = state.str->distance;
|
||||||
|
ptr[i].length = state.str->length;
|
||||||
|
ptr[i].flag = state.str->flag;
|
||||||
|
tmp = state.str->next;
|
||||||
|
pfree(state.str);
|
||||||
|
state.str = tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* set user friendly-operand view */
|
||||||
|
memcpy((void *) GETOPERAND(query), (void *) state.op, state.sumlen);
|
||||||
|
pfree(state.op);
|
||||||
|
|
||||||
|
/* set left operand's position for every operator */
|
||||||
|
pos = 0;
|
||||||
|
findoprnd(ptr, &pos);
|
||||||
|
|
||||||
|
return query;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* in without morphology
|
||||||
|
*/
|
||||||
|
Datum
|
||||||
|
ltxtq_in(PG_FUNCTION_ARGS)
|
||||||
|
{
|
||||||
|
PG_RETURN_POINTER(queryin((char *) PG_GETARG_POINTER(0)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* out function
|
||||||
|
*/
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
ITEM *curpol;
|
||||||
|
char *buf;
|
||||||
|
char *cur;
|
||||||
|
char *op;
|
||||||
|
int4 buflen;
|
||||||
|
} INFIX;
|
||||||
|
|
||||||
|
#define RESIZEBUF(inf,addsize) \
|
||||||
|
while( ( inf->cur - inf->buf ) + addsize + 1 >= inf->buflen ) \
|
||||||
|
{ \
|
||||||
|
int4 len = inf->cur - inf->buf; \
|
||||||
|
inf->buflen *= 2; \
|
||||||
|
inf->buf = (char*) repalloc( (void*)inf->buf, inf->buflen ); \
|
||||||
|
inf->cur = inf->buf + len; \
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* recursive walk on tree and print it in
|
||||||
|
* infix (human-readable) view
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
infix(INFIX * in, bool first)
|
||||||
|
{
|
||||||
|
if (in->curpol->type == VAL)
|
||||||
|
{
|
||||||
|
char *op = in->op + in->curpol->distance;
|
||||||
|
|
||||||
|
RESIZEBUF(in, in->curpol->length * 2 + 5);
|
||||||
|
while (*op) {
|
||||||
|
*(in->cur) = *op;
|
||||||
|
op++;
|
||||||
|
in->cur++;
|
||||||
|
}
|
||||||
|
if ( in->curpol->flag & LVAR_SUBLEXEM ) {
|
||||||
|
*(in->cur) = '%';
|
||||||
|
in->cur++;
|
||||||
|
}
|
||||||
|
if ( in->curpol->flag & LVAR_INCASE ) {
|
||||||
|
*(in->cur) = '@';
|
||||||
|
in->cur++;
|
||||||
|
}
|
||||||
|
if ( in->curpol->flag & LVAR_ANYEND ) {
|
||||||
|
*(in->cur) = '*';
|
||||||
|
in->cur++;
|
||||||
|
}
|
||||||
|
*(in->cur) = '\0';
|
||||||
|
in->curpol++;
|
||||||
|
}
|
||||||
|
else if (in->curpol->val == (int4) '!')
|
||||||
|
{
|
||||||
|
bool isopr = false;
|
||||||
|
|
||||||
|
RESIZEBUF(in, 1);
|
||||||
|
*(in->cur) = '!';
|
||||||
|
in->cur++;
|
||||||
|
*(in->cur) = '\0';
|
||||||
|
in->curpol++;
|
||||||
|
if (in->curpol->type == OPR)
|
||||||
|
{
|
||||||
|
isopr = true;
|
||||||
|
RESIZEBUF(in, 2);
|
||||||
|
sprintf(in->cur, "( ");
|
||||||
|
in->cur = strchr(in->cur, '\0');
|
||||||
|
}
|
||||||
|
infix(in, isopr);
|
||||||
|
if (isopr)
|
||||||
|
{
|
||||||
|
RESIZEBUF(in, 2);
|
||||||
|
sprintf(in->cur, " )");
|
||||||
|
in->cur = strchr(in->cur, '\0');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int4 op = in->curpol->val;
|
||||||
|
INFIX nrm;
|
||||||
|
|
||||||
|
in->curpol++;
|
||||||
|
if (op == (int4) '|' && !first)
|
||||||
|
{
|
||||||
|
RESIZEBUF(in, 2);
|
||||||
|
sprintf(in->cur, "( ");
|
||||||
|
in->cur = strchr(in->cur, '\0');
|
||||||
|
}
|
||||||
|
|
||||||
|
nrm.curpol = in->curpol;
|
||||||
|
nrm.op = in->op;
|
||||||
|
nrm.buflen = 16;
|
||||||
|
nrm.cur = nrm.buf = (char *) palloc(sizeof(char) * nrm.buflen);
|
||||||
|
|
||||||
|
/* get right operand */
|
||||||
|
infix(&nrm, false);
|
||||||
|
|
||||||
|
/* get & print left operand */
|
||||||
|
in->curpol = nrm.curpol;
|
||||||
|
infix(in, false);
|
||||||
|
|
||||||
|
/* print operator & right operand */
|
||||||
|
RESIZEBUF(in, 3 + (nrm.cur - nrm.buf));
|
||||||
|
sprintf(in->cur, " %c %s", op, nrm.buf);
|
||||||
|
in->cur = strchr(in->cur, '\0');
|
||||||
|
pfree(nrm.buf);
|
||||||
|
|
||||||
|
if (op == (int4) '|' && !first)
|
||||||
|
{
|
||||||
|
RESIZEBUF(in, 2);
|
||||||
|
sprintf(in->cur, " )");
|
||||||
|
in->cur = strchr(in->cur, '\0');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Datum
|
||||||
|
ltxtq_out(PG_FUNCTION_ARGS)
|
||||||
|
{
|
||||||
|
ltxtquery *query = PG_GETARG_LTXTQUERY(0);
|
||||||
|
INFIX nrm;
|
||||||
|
|
||||||
|
if (query->size == 0)
|
||||||
|
elog(ERROR, "Empty");
|
||||||
|
nrm.curpol = GETQUERY(query);
|
||||||
|
nrm.buflen = 32;
|
||||||
|
nrm.cur = nrm.buf = (char *) palloc(sizeof(char) * nrm.buflen);
|
||||||
|
*(nrm.cur) = '\0';
|
||||||
|
nrm.op = GETOPERAND(query);
|
||||||
|
infix(&nrm, true);
|
||||||
|
|
||||||
|
PG_FREE_IF_COPY(query, 0);
|
||||||
|
PG_RETURN_POINTER(nrm.buf);
|
||||||
|
}
|
||||||
|
|
99
contrib/ltree/ltxtquery_op.c
Normal file
99
contrib/ltree/ltxtquery_op.c
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
/*
|
||||||
|
* txtquery operations with ltree
|
||||||
|
* Teodor Sigaev <teodor@stack.net>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "ltree.h"
|
||||||
|
#include <ctype.h>
|
||||||
|
|
||||||
|
PG_FUNCTION_INFO_V1(ltxtq_exec);
|
||||||
|
PG_FUNCTION_INFO_V1(ltxtq_rexec);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* check for boolean condition
|
||||||
|
*/
|
||||||
|
bool
|
||||||
|
execute(ITEM * curitem, void *checkval, bool calcnot, bool (*chkcond) (void *checkval, ITEM * val)) {
|
||||||
|
if (curitem->type == VAL)
|
||||||
|
return (*chkcond) (checkval, curitem);
|
||||||
|
else if (curitem->val == (int4) '!') {
|
||||||
|
return (calcnot) ?
|
||||||
|
((execute(curitem + 1, checkval, calcnot, chkcond)) ? false : true)
|
||||||
|
: true;
|
||||||
|
} else if (curitem->val == (int4) '&') {
|
||||||
|
if (execute(curitem + curitem->left, checkval, calcnot, chkcond))
|
||||||
|
return execute(curitem + 1, checkval, calcnot, chkcond);
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
} else { /* |-operator */
|
||||||
|
if (execute(curitem + curitem->left, checkval, calcnot, chkcond))
|
||||||
|
return true;
|
||||||
|
else
|
||||||
|
return execute(curitem + 1, checkval, calcnot, chkcond);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
ltree *node;
|
||||||
|
char *operand;
|
||||||
|
} CHKVAL;
|
||||||
|
|
||||||
|
static bool
|
||||||
|
checkcondition_str(void* checkval, ITEM * val) {
|
||||||
|
ltree_level *level = LTREE_FIRST( ((CHKVAL*)checkval)->node );
|
||||||
|
int tlen = ((CHKVAL*)checkval)->node->numlevel;
|
||||||
|
char *op = ((CHKVAL*)checkval)->operand + val->distance;
|
||||||
|
int (*cmpptr)(const char *,const char *,size_t);
|
||||||
|
|
||||||
|
cmpptr = ( val->flag & LVAR_INCASE ) ? strncasecmp : strncmp;
|
||||||
|
while( tlen > 0 ) {
|
||||||
|
if ( val->flag & LVAR_SUBLEXEM ) {
|
||||||
|
if ( compare_subnode(level, op, val->length, cmpptr, (val->flag & LVAR_ANYEND) ) )
|
||||||
|
return true;
|
||||||
|
} else if (
|
||||||
|
(
|
||||||
|
val->length == level->len ||
|
||||||
|
( level->len > val->length && (val->flag & LVAR_ANYEND) )
|
||||||
|
) &&
|
||||||
|
(*cmpptr)( op, level->name, val->length) == 0 )
|
||||||
|
return true;
|
||||||
|
|
||||||
|
tlen--;
|
||||||
|
level = LEVEL_NEXT(level);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Datum
|
||||||
|
ltxtq_exec(PG_FUNCTION_ARGS) {
|
||||||
|
ltree *val = PG_GETARG_LTREE(0);
|
||||||
|
ltxtquery *query = PG_GETARG_LTXTQUERY(1);
|
||||||
|
CHKVAL chkval;
|
||||||
|
bool result;
|
||||||
|
|
||||||
|
chkval.node = val;
|
||||||
|
chkval.operand = GETOPERAND(query);
|
||||||
|
|
||||||
|
result = execute(
|
||||||
|
GETQUERY(query),
|
||||||
|
&chkval,
|
||||||
|
true,
|
||||||
|
checkcondition_str
|
||||||
|
);
|
||||||
|
|
||||||
|
PG_FREE_IF_COPY(val, 0);
|
||||||
|
PG_FREE_IF_COPY(query, 1);
|
||||||
|
PG_RETURN_BOOL(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
Datum
|
||||||
|
ltxtq_rexec(PG_FUNCTION_ARGS) {
|
||||||
|
PG_RETURN_DATUM( DirectFunctionCall2( ltxtq_exec,
|
||||||
|
PG_GETARG_DATUM(1),
|
||||||
|
PG_GETARG_DATUM(0)
|
||||||
|
) );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
71
contrib/ltree/patch.72
Normal file
71
contrib/ltree/patch.72
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
*** ltree.sql.in Tue Jul 23 18:49:12 2002
|
||||||
|
--- ltree.sql.in.72 Wed Jul 17 18:59:08 2002
|
||||||
|
***************
|
||||||
|
*** 177,188 ****
|
||||||
|
|
||||||
|
|
||||||
|
-- B-tree support
|
||||||
|
! INSERT INTO pg_opclass (opcamid, opcname, opcnamespace, opcowner, opcintype, opcdefault, opckeytype)
|
||||||
|
VALUES (
|
||||||
|
(SELECT oid FROM pg_am WHERE amname = 'btree'),
|
||||||
|
'ltree_ops',
|
||||||
|
- (SELECT oid FROM pg_namespace WHERE nspname = 'pg_catalog'),
|
||||||
|
- 1, -- UID of superuser is hardwired to 1 as of PG 7.3
|
||||||
|
(SELECT oid FROM pg_type WHERE typname = 'ltree'),
|
||||||
|
true,
|
||||||
|
0);
|
||||||
|
--- 177,186 ----
|
||||||
|
|
||||||
|
|
||||||
|
-- B-tree support
|
||||||
|
! INSERT INTO pg_opclass (opcamid, opcname, opcintype, opcdefault, opckeytype)
|
||||||
|
VALUES (
|
||||||
|
(SELECT oid FROM pg_am WHERE amname = 'btree'),
|
||||||
|
'ltree_ops',
|
||||||
|
(SELECT oid FROM pg_type WHERE typname = 'ltree'),
|
||||||
|
true,
|
||||||
|
0);
|
||||||
|
***************
|
||||||
|
*** 376,386 ****
|
||||||
|
create function ltree_union(bytea, opaque) returns int4 as 'MODULE_PATHNAME' language 'C';
|
||||||
|
create function ltree_same(opaque, opaque, opaque) returns opaque as 'MODULE_PATHNAME' language 'C';
|
||||||
|
|
||||||
|
! INSERT INTO pg_opclass (opcamid, opcname, opcnamespace, opcowner, opcintype, opckeytype, opcdefault)
|
||||||
|
! SELECT pg_am.oid, 'gist_ltree_ops',
|
||||||
|
! (SELECT oid FROM pg_namespace WHERE nspname = 'pg_catalog'),
|
||||||
|
! 1, -- UID of superuser is hardwired to 1 as of PG 7.3
|
||||||
|
! pg_type.oid, pg_key.oid, true
|
||||||
|
FROM pg_type, pg_am, pg_type pg_key
|
||||||
|
WHERE pg_type.typname = 'ltree' and
|
||||||
|
pg_am.amname='gist' and
|
||||||
|
--- 374,381 ----
|
||||||
|
create function ltree_union(bytea, opaque) returns int4 as 'MODULE_PATHNAME' language 'C';
|
||||||
|
create function ltree_same(opaque, opaque, opaque) returns opaque as 'MODULE_PATHNAME' language 'C';
|
||||||
|
|
||||||
|
! INSERT INTO pg_opclass (opcamid, opcname, opcintype, opckeytype, opcdefault)
|
||||||
|
! SELECT pg_am.oid, 'gist_ltree_ops', pg_type.oid, pg_key.oid, true
|
||||||
|
FROM pg_type, pg_am, pg_type pg_key
|
||||||
|
WHERE pg_type.typname = 'ltree' and
|
||||||
|
pg_am.amname='gist' and
|
||||||
|
***************
|
||||||
|
*** 720,730 ****
|
||||||
|
create function _ltree_union(bytea, opaque) returns int4 as 'MODULE_PATHNAME' language 'C';
|
||||||
|
create function _ltree_same(opaque, opaque, opaque) returns opaque as 'MODULE_PATHNAME' language 'C';
|
||||||
|
|
||||||
|
! INSERT INTO pg_opclass (opcamid, opcname, opcnamespace, opcowner, opcintype, opckeytype, opcdefault)
|
||||||
|
! SELECT pg_am.oid, 'gist__ltree_ops',
|
||||||
|
! (SELECT oid FROM pg_namespace WHERE nspname = 'pg_catalog'),
|
||||||
|
! 1, -- UID of superuser is hardwired to 1 as of PG 7.3
|
||||||
|
! pg_type.oid, pg_key.oid, true
|
||||||
|
FROM pg_type, pg_am, pg_type pg_key
|
||||||
|
WHERE pg_type.typname = '_ltree' and
|
||||||
|
pg_am.amname='gist' and
|
||||||
|
--- 715,722 ----
|
||||||
|
create function _ltree_union(bytea, opaque) returns int4 as 'MODULE_PATHNAME' language 'C';
|
||||||
|
create function _ltree_same(opaque, opaque, opaque) returns opaque as 'MODULE_PATHNAME' language 'C';
|
||||||
|
|
||||||
|
! INSERT INTO pg_opclass (opcamid, opcname, opcintype, opckeytype, opcdefault)
|
||||||
|
! SELECT pg_am.oid, 'gist__ltree_ops', pg_type.oid, pg_key.oid, true
|
||||||
|
FROM pg_type, pg_am, pg_type pg_key
|
||||||
|
WHERE pg_type.typname = '_ltree' and
|
||||||
|
pg_am.amname='gist' and
|
238
contrib/ltree/sql/ltree.sql
Normal file
238
contrib/ltree/sql/ltree.sql
Normal file
@ -0,0 +1,238 @@
|
|||||||
|
\set ECHO none
|
||||||
|
\i ltree.sql
|
||||||
|
\set ECHO all
|
||||||
|
|
||||||
|
select ''::ltree;
|
||||||
|
select '1'::ltree;
|
||||||
|
select '1.2'::ltree;
|
||||||
|
select '1.2._3'::ltree;
|
||||||
|
|
||||||
|
select subltree('Top.Child1.Child2',1,2);
|
||||||
|
select subpath('Top.Child1.Child2',1,2);
|
||||||
|
select subpath('Top.Child1.Child2',-1,1);
|
||||||
|
select subpath('Top.Child1.Child2',0,-2);
|
||||||
|
select subpath('Top.Child1.Child2',0,-1);
|
||||||
|
select subpath('Top.Child1.Child2',0,0);
|
||||||
|
select subpath('Top.Child1.Child2',1,0);
|
||||||
|
select subpath('Top.Child1.Child2',0);
|
||||||
|
select subpath('Top.Child1.Child2',1);
|
||||||
|
|
||||||
|
select 'Top.Child1.Child2'::ltree || 'Child3'::text;
|
||||||
|
select 'Top.Child1.Child2'::ltree || 'Child3'::ltree;
|
||||||
|
select 'Top_0'::ltree || 'Top.Child1.Child2'::ltree;
|
||||||
|
select 'Top.Child1.Child2'::ltree || ''::ltree;
|
||||||
|
select ''::ltree || 'Top.Child1.Child2'::ltree;
|
||||||
|
|
||||||
|
select '1'::lquery;
|
||||||
|
select '4|3|2'::lquery;
|
||||||
|
select '1.2'::lquery;
|
||||||
|
select '1.4|3|2'::lquery;
|
||||||
|
select '1.0'::lquery;
|
||||||
|
select '4|3|2.0'::lquery;
|
||||||
|
select '1.2.0'::lquery;
|
||||||
|
select '1.4|3|2.0'::lquery;
|
||||||
|
select '1.*'::lquery;
|
||||||
|
select '4|3|2.*'::lquery;
|
||||||
|
select '1.2.*'::lquery;
|
||||||
|
select '1.4|3|2.*'::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.*.4|3|2.0'::lquery;
|
||||||
|
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 'qwerty%@*.tu'::lquery;
|
||||||
|
|
||||||
|
select nlevel('1.2.3.4');
|
||||||
|
select '1.2'::ltree < '2.2'::ltree;
|
||||||
|
select '1.2'::ltree <= '2.2'::ltree;
|
||||||
|
select '2.2'::ltree = '2.2'::ltree;
|
||||||
|
select '3.2'::ltree >= '2.2'::ltree;
|
||||||
|
select '3.2'::ltree > '2.2'::ltree;
|
||||||
|
|
||||||
|
select '1.2.3'::ltree @> '1.2.3.4'::ltree;
|
||||||
|
select '1.2.3.4'::ltree @> '1.2.3.4'::ltree;
|
||||||
|
select '1.2.3.4.5'::ltree @> '1.2.3.4'::ltree;
|
||||||
|
select '1.3.3'::ltree @> '1.2.3.4'::ltree;
|
||||||
|
|
||||||
|
select 'a.b.c.d.e'::ltree ~ 'a.b.c.d.e';
|
||||||
|
select 'a.b.c.d.e'::ltree ~ 'A.b.c.d.e';
|
||||||
|
select 'a.b.c.d.e'::ltree ~ 'A@.b.c.d.e';
|
||||||
|
select 'aa.b.c.d.e'::ltree ~ 'A@.b.c.d.e';
|
||||||
|
select 'aa.b.c.d.e'::ltree ~ 'A*.b.c.d.e';
|
||||||
|
select 'aa.b.c.d.e'::ltree ~ 'A*@.b.c.d.e';
|
||||||
|
select 'aa.b.c.d.e'::ltree ~ 'A*@|g.b.c.d.e';
|
||||||
|
select 'g.b.c.d.e'::ltree ~ 'A*@|g.b.c.d.e';
|
||||||
|
select 'a.b.c.d.e'::ltree ~ 'a.b.c.d.e';
|
||||||
|
select 'a.b.c.d.e'::ltree ~ 'a.*.e';
|
||||||
|
select 'a.b.c.d.e'::ltree ~ 'a.*{3}.e';
|
||||||
|
select 'a.b.c.d.e'::ltree ~ 'a.*{2}.e';
|
||||||
|
select 'a.b.c.d.e'::ltree ~ 'a.*{4}.e';
|
||||||
|
select 'a.b.c.d.e'::ltree ~ 'a.*{,4}.e';
|
||||||
|
select 'a.b.c.d.e'::ltree ~ 'a.*{2,}.e';
|
||||||
|
select 'a.b.c.d.e'::ltree ~ 'a.*{2,4}.e';
|
||||||
|
select 'a.b.c.d.e'::ltree ~ 'a.*{2,3}.e';
|
||||||
|
select 'a.b.c.d.e'::ltree ~ 'a.*{2,3}';
|
||||||
|
select 'a.b.c.d.e'::ltree ~ 'a.*{2,4}';
|
||||||
|
select 'a.b.c.d.e'::ltree ~ 'a.*{2,5}';
|
||||||
|
select 'a.b.c.d.e'::ltree ~ '*{2,3}.e';
|
||||||
|
select 'a.b.c.d.e'::ltree ~ '*{2,4}.e';
|
||||||
|
select 'a.b.c.d.e'::ltree ~ '*{2,5}.e';
|
||||||
|
select 'a.b.c.d.e'::ltree ~ '*.e';
|
||||||
|
select 'a.b.c.d.e'::ltree ~ '*.e.*';
|
||||||
|
select 'a.b.c.d.e'::ltree ~ '*.d.*';
|
||||||
|
select 'a.b.c.d.e'::ltree ~ '*.a.*.d.*';
|
||||||
|
select 'a.b.c.d.e'::ltree ~ '*.!d.*';
|
||||||
|
select 'a.b.c.d.e'::ltree ~ '*.!d';
|
||||||
|
select 'a.b.c.d.e'::ltree ~ '!d.*';
|
||||||
|
select 'a.b.c.d.e'::ltree ~ '!a.*';
|
||||||
|
select 'a.b.c.d.e'::ltree ~ '*.!e';
|
||||||
|
select 'a.b.c.d.e'::ltree ~ '*.!e.*';
|
||||||
|
select 'a.b.c.d.e'::ltree ~ 'a.*.!e';
|
||||||
|
select 'a.b.c.d.e'::ltree ~ 'a.*.!d';
|
||||||
|
select 'a.b.c.d.e'::ltree ~ 'a.*.!d.*';
|
||||||
|
select 'a.b.c.d.e'::ltree ~ 'a.*.!f.*';
|
||||||
|
select 'a.b.c.d.e'::ltree ~ '*.a.*.!f.*';
|
||||||
|
select 'a.b.c.d.e'::ltree ~ '*.a.*.!d.*';
|
||||||
|
select 'a.b.c.d.e'::ltree ~ '*.a.!d.*';
|
||||||
|
select 'a.b.c.d.e'::ltree ~ '*.a.!d';
|
||||||
|
select 'a.b.c.d.e'::ltree ~ 'a.!d.*';
|
||||||
|
select 'a.b.c.d.e'::ltree ~ '*.a.*.!d.*';
|
||||||
|
select 'a.b.c.d.e'::ltree ~ '*.!b.*';
|
||||||
|
select 'a.b.c.d.e'::ltree ~ '*.!b.c.*';
|
||||||
|
select 'a.b.c.d.e'::ltree ~ '*.!b.*.c.*';
|
||||||
|
select 'a.b.c.d.e'::ltree ~ '!b.*.c.*';
|
||||||
|
select 'a.b.c.d.e'::ltree ~ '!b.b.*';
|
||||||
|
select 'a.b.c.d.e'::ltree ~ '!b.*.e';
|
||||||
|
select 'a.b.c.d.e'::ltree ~ '!b.!c.*.e';
|
||||||
|
select 'a.b.c.d.e'::ltree ~ '!b.*.!c.*.e';
|
||||||
|
select 'a.b.c.d.e'::ltree ~ '*{2}.!b.*.!c.*.e';
|
||||||
|
select 'a.b.c.d.e'::ltree ~ '*{1}.!b.*.!c.*.e';
|
||||||
|
select 'a.b.c.d.e'::ltree ~ '*{1}.!b.*{1}.!c.*.e';
|
||||||
|
select 'a.b.c.d.e'::ltree ~ 'a.!b.*{1}.!c.*.e';
|
||||||
|
select 'a.b.c.d.e'::ltree ~ '!b.*{1}.!c.*.e';
|
||||||
|
select 'a.b.c.d.e'::ltree ~ '*.!b.*{1}.!c.*.e';
|
||||||
|
select 'a.b.c.d.e'::ltree ~ '*.!b.*.!c.*.e';
|
||||||
|
select 'a.b.c.d.e'::ltree ~ '!b.!c.*';
|
||||||
|
select 'a.b.c.d.e'::ltree ~ '!b.*.!c.*';
|
||||||
|
select 'a.b.c.d.e'::ltree ~ '*{2}.!b.*.!c.*';
|
||||||
|
select 'a.b.c.d.e'::ltree ~ '*{1}.!b.*.!c.*';
|
||||||
|
select 'a.b.c.d.e'::ltree ~ '*{1}.!b.*{1}.!c.*';
|
||||||
|
select 'a.b.c.d.e'::ltree ~ 'a.!b.*{1}.!c.*';
|
||||||
|
select 'a.b.c.d.e'::ltree ~ '!b.*{1}.!c.*';
|
||||||
|
select 'a.b.c.d.e'::ltree ~ '*.!b.*{1}.!c.*';
|
||||||
|
select 'a.b.c.d.e'::ltree ~ '*.!b.*.!c.*';
|
||||||
|
|
||||||
|
select 'QWER_TY'::ltree ~ 'q%@*';
|
||||||
|
select 'QWER_TY'::ltree ~ 'Q_t%@*';
|
||||||
|
select 'QWER_GY'::ltree ~ 'q_t%@*';
|
||||||
|
|
||||||
|
--ltxtquery
|
||||||
|
select '!tree & aWdf@*'::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;
|
||||||
|
select 'tree.awdfg'::ltree @ 'tree | aWdf@*'::ltxtquery;
|
||||||
|
select 'tree.awdfg'::ltree @ 'tree & aWdf@*'::ltxtquery;
|
||||||
|
select 'tree.awdfg'::ltree @ 'tree & aWdf@'::ltxtquery;
|
||||||
|
select 'tree.awdfg'::ltree @ 'tree & aWdf*'::ltxtquery;
|
||||||
|
select 'tree.awdfg'::ltree @ 'tree & aWdf'::ltxtquery;
|
||||||
|
select 'tree.awdfg'::ltree @ 'tree & awdf*'::ltxtquery;
|
||||||
|
select 'tree.awdfg'::ltree @ 'tree & aWdfg@'::ltxtquery;
|
||||||
|
select 'tree.awdfg_qwerty'::ltree @ 'tree & aw_qw%*'::ltxtquery;
|
||||||
|
select 'tree.awdfg_qwerty'::ltree @ 'tree & aw_rw%*'::ltxtquery;
|
||||||
|
|
||||||
|
--arrays
|
||||||
|
|
||||||
|
select '{1.2.3}'::ltree[] @> '1.2.3.4';
|
||||||
|
select '{1.2.3.4}'::ltree[] @> '1.2.3.4';
|
||||||
|
select '{1.2.3.4.5}'::ltree[] @> '1.2.3.4';
|
||||||
|
select '{1.3.3}'::ltree[] @> '1.2.3.4';
|
||||||
|
select '{5.67.8, 1.2.3}'::ltree[] @> '1.2.3.4';
|
||||||
|
select '{5.67.8, 1.2.3.4}'::ltree[] @> '1.2.3.4';
|
||||||
|
select '{5.67.8, 1.2.3.4.5}'::ltree[] @> '1.2.3.4';
|
||||||
|
select '{5.67.8, 1.3.3}'::ltree[] @> '1.2.3.4';
|
||||||
|
select '{1.2.3, 7.12.asd}'::ltree[] @> '1.2.3.4';
|
||||||
|
select '{1.2.3.4, 7.12.asd}'::ltree[] @> '1.2.3.4';
|
||||||
|
select '{1.2.3.4.5, 7.12.asd}'::ltree[] @> '1.2.3.4';
|
||||||
|
select '{1.3.3, 7.12.asd}'::ltree[] @> '1.2.3.4';
|
||||||
|
select '{ltree.asd, tree.awdfg}'::ltree[] @ 'tree & aWdfg@'::ltxtquery;
|
||||||
|
select '{j.k.l.m, g.b.c.d.e}'::ltree[] ~ 'A*@|g.b.c.d.e';
|
||||||
|
|
||||||
|
--exractors
|
||||||
|
select ('{3456,1.2.3.34}'::ltree[] ?@> '1.2.3.4') is null;
|
||||||
|
select '{3456,1.2.3}'::ltree[] ?@> '1.2.3.4';
|
||||||
|
select '{3456,1.2.3.4}'::ltree[] ?<@ '1.2.3';
|
||||||
|
select ('{3456,1.2.3.4}'::ltree[] ?<@ '1.2.5') is null;
|
||||||
|
select '{ltree.asd, tree.awdfg}'::ltree[] ?@ 'tree & aWdfg@'::ltxtquery;
|
||||||
|
select '{j.k.l.m, g.b.c.d.e}'::ltree[] ?~ 'A*@|g.b.c.d.e';
|
||||||
|
|
||||||
|
create table ltreetest (t ltree);
|
||||||
|
\copy ltreetest from 'data/ltree.data'
|
||||||
|
|
||||||
|
select * from ltreetest where t < '12.3' order by t asc;
|
||||||
|
select * from ltreetest where t <= '12.3' order by t asc;
|
||||||
|
select * from ltreetest where t = '12.3' order by t asc;
|
||||||
|
select * from ltreetest where t >= '12.3' order by t asc;
|
||||||
|
select * from ltreetest where t > '12.3' order by t asc;
|
||||||
|
select * from ltreetest where t @> '1.1.1' order by t asc;
|
||||||
|
select * from ltreetest where t <@ '1.1.1' order by t asc;
|
||||||
|
select * from ltreetest where t ~ '1.1.1.*' order by t asc;
|
||||||
|
select * from ltreetest where t ~ '*.1' order by t asc;
|
||||||
|
select * from ltreetest where t ~ '23.*.1' order by t asc;
|
||||||
|
select * from ltreetest where t ~ '23.*{1}.1' order by t asc;
|
||||||
|
select * from ltreetest where t @ '23 & 1' order by t asc;
|
||||||
|
|
||||||
|
create unique index tstidx on ltreetest (t);
|
||||||
|
set enable_seqscan=off;
|
||||||
|
|
||||||
|
select * from ltreetest where t < '12.3' order by t asc;
|
||||||
|
select * from ltreetest where t <= '12.3' order by t asc;
|
||||||
|
select * from ltreetest where t = '12.3' order by t asc;
|
||||||
|
select * from ltreetest where t >= '12.3' order by t asc;
|
||||||
|
select * from ltreetest where t > '12.3' order by t asc;
|
||||||
|
|
||||||
|
drop index tstidx;
|
||||||
|
create index tstidx on ltreetest using gist (t);
|
||||||
|
set enable_seqscan=off;
|
||||||
|
|
||||||
|
select * from ltreetest where t < '12.3' order by t asc;
|
||||||
|
select * from ltreetest where t <= '12.3' order by t asc;
|
||||||
|
select * from ltreetest where t = '12.3' order by t asc;
|
||||||
|
select * from ltreetest where t >= '12.3' order by t asc;
|
||||||
|
select * from ltreetest where t > '12.3' order by t asc;
|
||||||
|
select * from ltreetest where t @> '1.1.1' order by t asc;
|
||||||
|
select * from ltreetest where t <@ '1.1.1' order by t asc;
|
||||||
|
select * from ltreetest where t ~ '1.1.1.*' order by t asc;
|
||||||
|
select * from ltreetest where t ~ '*.1' order by t asc;
|
||||||
|
select * from ltreetest where t ~ '23.*.1' order by t asc;
|
||||||
|
select * from ltreetest where t ~ '23.*{1}.1' order by t asc;
|
||||||
|
select * from ltreetest where t @ '23 & 1' order by t asc;
|
||||||
|
|
||||||
|
create table _ltreetest (t ltree[]);
|
||||||
|
\copy _ltreetest from 'data/_ltree.data'
|
||||||
|
|
||||||
|
select count(*) from _ltreetest where t @> '1.1.1' ;
|
||||||
|
select count(*) from _ltreetest where t <@ '1.1.1' ;
|
||||||
|
select count(*) from _ltreetest where t ~ '1.1.1.*' ;
|
||||||
|
select count(*) from _ltreetest where t ~ '*.1' ;
|
||||||
|
select count(*) from _ltreetest where t ~ '23.*.1' ;
|
||||||
|
select count(*) from _ltreetest where t ~ '23.*{1}.1' ;
|
||||||
|
select count(*) from _ltreetest where t @ '23 & 1' ;
|
||||||
|
|
||||||
|
create index _tstidx on _ltreetest using gist (t);
|
||||||
|
set enable_seqscan=off;
|
||||||
|
|
||||||
|
select count(*) from _ltreetest where t @> '1.1.1' ;
|
||||||
|
select count(*) from _ltreetest where t <@ '1.1.1' ;
|
||||||
|
select count(*) from _ltreetest where t ~ '1.1.1.*' ;
|
||||||
|
select count(*) from _ltreetest where t ~ '*.1' ;
|
||||||
|
select count(*) from _ltreetest where t ~ '23.*.1' ;
|
||||||
|
select count(*) from _ltreetest where t ~ '23.*{1}.1' ;
|
||||||
|
select count(*) from _ltreetest where t @ '23 & 1' ;
|
||||||
|
|
Loading…
x
Reference in New Issue
Block a user