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
|
||||
top_builddir = ..
|
||||
@ -20,6 +20,7 @@ WANTED_DIRS = \
|
||||
intarray \
|
||||
isbn_issn \
|
||||
lo \
|
||||
ltree \
|
||||
miscutil \
|
||||
noupdate \
|
||||
oid2name \
|
||||
|
@ -95,6 +95,10 @@ lo -
|
||||
Large Object maintenance
|
||||
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 API translation library
|
||||
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