trgm-regexp-0.9-heikki-1.patch
text/x-diff
Filename: trgm-regexp-0.9-heikki-1.patch
Type: text/x-diff
Part: 0
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: unified
| File | + | − |
|---|---|---|
| contrib/pg_trgm/expected/pg_trgm.out | 42 | 1 |
| contrib/pg_trgm/Makefile | 1 | 1 |
| contrib/pg_trgm/pg_trgm--1.0.sql | 3 | 1 |
| contrib/pg_trgm/sql/pg_trgm.sql | 9 | 1 |
| contrib/pg_trgm/trgm_gin.c | 40 | 2 |
| contrib/pg_trgm/trgm.h | 9 | 1 |
| contrib/pg_trgm/trgm_op.c | 7 | 15 |
| contrib/pg_trgm/trgm_regexp.c | 1797 | 0 |
| doc/src/sgml/pgtrgm.sgml | 21 | 3 |
| src/backend/utils/adt/regexp.c | 1 | 1 |
| src/include/regex/regex.h | 1 | 0 |
diff --git a/contrib/pg_trgm/Makefile b/contrib/pg_trgm/Makefile
index 64fd69f..8033733 100644
--- a/contrib/pg_trgm/Makefile
+++ b/contrib/pg_trgm/Makefile
@@ -1,7 +1,7 @@
# contrib/pg_trgm/Makefile
MODULE_big = pg_trgm
-OBJS = trgm_op.o trgm_gist.o trgm_gin.o
+OBJS = trgm_op.o trgm_gist.o trgm_gin.o trgm_regexp.o
EXTENSION = pg_trgm
DATA = pg_trgm--1.0.sql pg_trgm--unpackaged--1.0.sql
diff --git a/contrib/pg_trgm/expected/pg_trgm.out b/contrib/pg_trgm/expected/pg_trgm.out
index 81d0ca8..ee0131f 100644
--- a/contrib/pg_trgm/expected/pg_trgm.out
+++ b/contrib/pg_trgm/expected/pg_trgm.out
@@ -54,7 +54,7 @@ select similarity('wow',' WOW ');
(1 row)
CREATE TABLE test_trgm(t text);
-\copy test_trgm from 'data/trgm.data
+\copy test_trgm from 'data/trgm.data'
select t,similarity(t,'qwertyu0988') as sml from test_trgm where t % 'qwertyu0988' order by sml desc, t;
t | sml
-------------+----------
@@ -3515,6 +3515,47 @@ select * from test2 where t ilike 'qua%';
quark
(1 row)
+select * from test2 where t ~ '[abc]{3}';
+ t
+--------
+ abcdef
+(1 row)
+
+select * from test2 where t ~ 'a[bc]+d';
+ t
+--------
+ abcdef
+(1 row)
+
+select * from test2 where t ~* 'DEF';
+ t
+--------
+ abcdef
+(1 row)
+
+select * from test2 where t ~ 'dEf';
+ t
+---
+(0 rows)
+
+select * from test2 where t ~* '^q';
+ t
+-------
+ quark
+(1 row)
+
+select * from test2 where t ~* '[abc]{3}[def]{3}';
+ t
+--------
+ abcdef
+(1 row)
+
+select * from test2 where t ~ 'q.*rk$';
+ t
+-------
+ quark
+(1 row)
+
drop index test2_idx_gin;
create index test2_idx_gist on test2 using gist (t gist_trgm_ops);
set enable_seqscan=off;
diff --git a/contrib/pg_trgm/pg_trgm--1.0.sql b/contrib/pg_trgm/pg_trgm--1.0.sql
index 8067bd6..ca9bcaa 100644
--- a/contrib/pg_trgm/pg_trgm--1.0.sql
+++ b/contrib/pg_trgm/pg_trgm--1.0.sql
@@ -163,4 +163,6 @@ AS
ALTER OPERATOR FAMILY gin_trgm_ops USING gin ADD
OPERATOR 3 pg_catalog.~~ (text, text),
- OPERATOR 4 pg_catalog.~~* (text, text);
+ OPERATOR 4 pg_catalog.~~* (text, text),
+ OPERATOR 5 pg_catalog.~ (text, text),
+ OPERATOR 6 pg_catalog.~* (text, text);
diff --git a/contrib/pg_trgm/sql/pg_trgm.sql b/contrib/pg_trgm/sql/pg_trgm.sql
index 81ab1e7..7d8a151 100644
--- a/contrib/pg_trgm/sql/pg_trgm.sql
+++ b/contrib/pg_trgm/sql/pg_trgm.sql
@@ -13,7 +13,7 @@ select similarity('wow',' WOW ');
CREATE TABLE test_trgm(t text);
-\copy test_trgm from 'data/trgm.data
+\copy test_trgm from 'data/trgm.data'
select t,similarity(t,'qwertyu0988') as sml from test_trgm where t % 'qwertyu0988' order by sml desc, t;
select t,similarity(t,'gwertyu0988') as sml from test_trgm where t % 'gwertyu0988' order by sml desc, t;
@@ -52,6 +52,14 @@ select * from test2 where t like '%bcd%';
select * from test2 where t like E'%\\bcd%';
select * from test2 where t ilike '%BCD%';
select * from test2 where t ilike 'qua%';
+
+select * from test2 where t ~ '[abc]{3}';
+select * from test2 where t ~ 'a[bc]+d';
+select * from test2 where t ~* 'DEF';
+select * from test2 where t ~ 'dEf';
+select * from test2 where t ~* '^q';
+select * from test2 where t ~* '[abc]{3}[def]{3}';
+select * from test2 where t ~ 'q.*rk$';
drop index test2_idx_gin;
create index test2_idx_gist on test2 using gist (t gist_trgm_ops);
set enable_seqscan=off;
diff --git a/contrib/pg_trgm/trgm.h b/contrib/pg_trgm/trgm.h
index 067f29d..b3ce65b 100644
--- a/contrib/pg_trgm/trgm.h
+++ b/contrib/pg_trgm/trgm.h
@@ -7,7 +7,6 @@
#include "access/gist.h"
#include "access/itup.h"
#include "storage/bufpage.h"
-#include "utils/builtins.h"
/* options */
#define LPADDING 2
@@ -28,6 +27,8 @@
#define DistanceStrategyNumber 2
#define LikeStrategyNumber 3
#define ILikeStrategyNumber 4
+#define RegExpStrategyNumber 5
+#define RegExpStrategyNumberICase 6
typedef char trgm[3];
@@ -46,8 +47,10 @@ uint32 trgm2int(trgm *ptr);
#ifdef KEEPONLYALNUM
#define ISPRINTABLECHAR(a) ( isascii( *(unsigned char*)(a) ) && (isalnum( *(unsigned char*)(a) ) || *(unsigned char*)(a)==' ') )
+#define ISWORDCHR(c) (t_isalpha(c) || t_isdigit(c))
#else
#define ISPRINTABLECHAR(a) ( isascii( *(unsigned char*)(a) ) && isprint( *(unsigned char*)(a) ) )
+#define ISWORDCHR(c) (!t_isspace(c))
#endif
#define ISPRINTABLETRGM(t) ( ISPRINTABLECHAR( ((char*)(t)) ) && ISPRINTABLECHAR( ((char*)(t))+1 ) && ISPRINTABLECHAR( ((char*)(t))+2 ) )
@@ -99,11 +102,16 @@ typedef char *BITVECP;
#define GETARR(x) ( (trgm*)( (char*)x+TRGMHDRSIZE ) )
#define ARRNELEM(x) ( ( VARSIZE(x) - TRGMHDRSIZE )/sizeof(trgm) )
+typedef struct PackedGraph PackedGraph;
+
extern float4 trgm_limit;
TRGM *generate_trgm(char *str, int slen);
TRGM *generate_wildcard_trgm(const char *str, int slen);
float4 cnt_sml(TRGM *trg1, TRGM *trg2);
bool trgm_contained_by(TRGM *trg1, TRGM *trg2);
+void cnt_trigram(trgm *trgmptr, char *str, int bytelen);
+TRGM *createTrgmCNFA(text *text_re, MemoryContext context, PackedGraph **paths);
+bool trigramsMatchGraph(PackedGraph *graph, bool *check);
#endif /* __TRGM_H__ */
diff --git a/contrib/pg_trgm/trgm_gin.c b/contrib/pg_trgm/trgm_gin.c
index 114fb78..b519901 100644
--- a/contrib/pg_trgm/trgm_gin.c
+++ b/contrib/pg_trgm/trgm_gin.c
@@ -80,7 +80,7 @@ gin_extract_query_trgm(PG_FUNCTION_ARGS)
StrategyNumber strategy = PG_GETARG_UINT16(2);
/* bool **pmatch = (bool **) PG_GETARG_POINTER(3); */
- /* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */
+ Pointer **extra_data = (Pointer **) PG_GETARG_POINTER(4);
/* bool **nullFlags = (bool **) PG_GETARG_POINTER(5); */
int32 *searchMode = (int32 *) PG_GETARG_POINTER(6);
Datum *entries = NULL;
@@ -88,6 +88,7 @@ gin_extract_query_trgm(PG_FUNCTION_ARGS)
int32 trglen;
trgm *ptr;
int32 i;
+ PackedGraph *graph;
switch (strategy)
{
@@ -107,6 +108,32 @@ gin_extract_query_trgm(PG_FUNCTION_ARGS)
*/
trg = generate_wildcard_trgm(VARDATA(val), VARSIZE(val) - VARHDRSZ);
break;
+ case RegExpStrategyNumberICase:
+#ifndef IGNORECASE
+ elog(ERROR, "cannot handle ~* with case-sensitive trigrams");
+#endif
+ /* FALL THRU */
+ case RegExpStrategyNumber:
+ trg = createTrgmCNFA(val, fcinfo->flinfo->fn_mcxt, &graph);
+ if (trg && ARRNELEM(trg) > 0)
+ {
+ /*
+ * Successful regex processing: store CNFA-like graph as an
+ * extra_data.
+ */
+ *extra_data = (Pointer *) palloc0(sizeof(Pointer) *
+ ARRNELEM(trg));
+ for (i = 0; i < ARRNELEM(trg); i++)
+ (*extra_data)[i] = (Pointer) graph;
+ }
+ else
+ {
+ /* No result: have to do full index scan. */
+ *nentries = 0;
+ *searchMode = GIN_SEARCH_MODE_ALL;
+ PG_RETURN_POINTER(entries);
+ }
+ break;
default:
elog(ERROR, "unrecognized strategy number: %d", strategy);
trg = NULL; /* keep compiler quiet */
@@ -147,7 +174,7 @@ gin_trgm_consistent(PG_FUNCTION_ARGS)
/* text *query = PG_GETARG_TEXT_P(2); */
int32 nkeys = PG_GETARG_INT32(3);
- /* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */
+ Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4);
bool *recheck = (bool *) PG_GETARG_POINTER(5);
bool res;
int32 i,
@@ -189,6 +216,17 @@ gin_trgm_consistent(PG_FUNCTION_ARGS)
}
}
break;
+ case RegExpStrategyNumber:
+ case RegExpStrategyNumberICase:
+ if (nkeys < 1)
+ {
+ /* Regex processing gave no result: do full index scan */
+ res = true;
+ break;
+ }
+ res = trigramsMatchGraph((PackedGraph *) extra_data[0], check);
+
+ break;
default:
elog(ERROR, "unrecognized strategy number: %d", strategy);
res = false; /* keep compiler quiet */
diff --git a/contrib/pg_trgm/trgm_op.c b/contrib/pg_trgm/trgm_op.c
index 87dffd1..71aa938 100644
--- a/contrib/pg_trgm/trgm_op.c
+++ b/contrib/pg_trgm/trgm_op.c
@@ -77,12 +77,6 @@ unique_array(trgm *a, int len)
return curend + 1 - a;
}
-#ifdef KEEPONLYALNUM
-#define iswordchr(c) (t_isalpha(c) || t_isdigit(c))
-#else
-#define iswordchr(c) (!t_isspace(c))
-#endif
-
/*
* Finds first word in string, returns pointer to the word,
* endword points to the character after word
@@ -92,7 +86,7 @@ find_word(char *str, int lenstr, char **endword, int *charlen)
{
char *beginword = str;
- while (beginword - str < lenstr && !iswordchr(beginword))
+ while (beginword - str < lenstr && !ISWORDCHR(beginword))
beginword += pg_mblen(beginword);
if (beginword - str >= lenstr)
@@ -100,7 +94,7 @@ find_word(char *str, int lenstr, char **endword, int *charlen)
*endword = beginword;
*charlen = 0;
- while (*endword - str < lenstr && iswordchr(*endword))
+ while (*endword - str < lenstr && ISWORDCHR(*endword))
{
*endword += pg_mblen(*endword);
(*charlen)++;
@@ -109,8 +103,7 @@ find_word(char *str, int lenstr, char **endword, int *charlen)
return beginword;
}
-#ifdef USE_WIDE_UPPER_LOWER
-static void
+void
cnt_trigram(trgm *tptr, char *str, int bytelen)
{
if (bytelen == 3)
@@ -131,7 +124,6 @@ cnt_trigram(trgm *tptr, char *str, int bytelen)
CPTRGM(tptr, &crc);
}
}
-#endif
/*
* Adds trigrams from words (already padded).
@@ -287,7 +279,7 @@ get_wildcard_part(const char *str, int lenstr,
{
if (in_escape)
{
- if (iswordchr(beginword))
+ if (ISWORDCHR(beginword))
break;
in_escape = false;
in_leading_wildcard_meta = false;
@@ -298,7 +290,7 @@ get_wildcard_part(const char *str, int lenstr,
in_escape = true;
else if (ISWILDCARDCHAR(beginword))
in_leading_wildcard_meta = true;
- else if (iswordchr(beginword))
+ else if (ISWORDCHR(beginword))
break;
else
in_leading_wildcard_meta = false;
@@ -341,7 +333,7 @@ get_wildcard_part(const char *str, int lenstr,
clen = pg_mblen(endword);
if (in_escape)
{
- if (iswordchr(endword))
+ if (ISWORDCHR(endword))
{
memcpy(s, endword, clen);
(*charlen)++;
@@ -369,7 +361,7 @@ get_wildcard_part(const char *str, int lenstr,
in_trailing_wildcard_meta = true;
break;
}
- else if (iswordchr(endword))
+ else if (ISWORDCHR(endword))
{
memcpy(s, endword, clen);
(*charlen)++;
diff --git a/contrib/pg_trgm/trgm_regexp.c b/contrib/pg_trgm/trgm_regexp.c
new file mode 100644
index 0000000..cfcabbb
--- /dev/null
+++ b/contrib/pg_trgm/trgm_regexp.c
@@ -0,0 +1,1797 @@
+/*
+ * contrib/pg_trgm/trgm_regexp.c - regular expression matching using trigrams
+ *
+ * The general idea of index support for a regular expression (regex) search
+ * is to transform the regex to a logical expression on trigrams. For example:
+ *
+ * (ab|cd)efg => ((abe & bef) | (cde & def)) & efg
+ *
+ * If a string matches the regex, then it must match the logical expression of
+ * trigrams. The opposite is not necessary true, however: a string that matches
+ * the logical expression might not match the original regex. Such false
+ * positives are removed during recheck.
+ *
+ * The algorithm to convert a regex to a logical expression is based on
+ * analysis of an automaton that corresponds to regex. The algorithm consists
+ * of four stages:
+ *
+ * 1) Compile the regexp to CNFA form. This is handled by the PostgreSQL
+ * regexp library, but we have peek into the data structures it produces.
+ *
+ * 2) Transform the original CNFA into an automaton-like graph, where arcs
+ * are labeled with trigrams that must be present in order to move from
+ * state to another via the arc. The trigrams used in this stage consist
+ * of colors, not characters, like the original CNFA.
+ *
+ * 3) Expand the color trigrams into regular trigrams consisting of
+ * characters. If too many distinct trigrams are produced, trigrams are
+ * eliminated and the graph is simplified until it's simple enough.
+ *
+ * 4) Finally, the resulting graph is packed into a PackedGraph struct, and
+ * returned to the caller.
+ *
+ *
+ * 1) Compile the regexp to CNFA form
+ * ----------------------------------
+ * The automaton returned by the regexp compiler is a graph where vertices
+ * are "states" and arcs are labeled with colors. Each color represents
+ * a group of characters, so that all characters assigned to the same color
+ * are interchangeable, as far as matching the regexp is concerned. There are
+ * two special states: "initial" and "final". There can be multiple outgoing
+ * arcs from a state labeled with the same color, which makes the automaton
+ * non-deterministic, because it can be in many states simultaneously.
+ *
+ * 2) Transform the original CNFA into an automaton-like graph
+ * -----------------------------------------------------------
+ * In the 2nd stage, the automaton is transformed into a graph that resembles
+ * the original CNFA. Each state in the transformed graph represents a state
+ * from the original CNFA, with an "enter key". The enter key consists of the
+ * last two characters (colors, to be precise) read before entering the state.
+ * There can be one or more states in the transformed graph for each state in
+ * the original CNFA, depending on what characters can precede it. Each arc
+ * is labelled with a trigram that must be present in the string to match.
+ *
+ * So the transformed graph resembles the original CNFA, but the arcs are
+ * labeled with trigrams instead of individual characters, and there are
+ * more states. It is a lossy representation of the original CNFA: any string
+ * that matches the original regexp must match the transformed graph, but the
+ * reverse is not true.
+ *
+ * When building the graph, if the number of states or arcs exceed pre-defined
+ * limits, we give up and simply mark any states not yet processed as final
+ * states. Roughly speaking, that means that we make use of some portion from
+ * the beginning of the regexp.
+ *
+ * 3) Expand the color trigrams into regular trigrams
+ * --------------------------------------------------
+ * The trigrams in the transformed graph are "color trigrams", consisting
+ * of three consecutive colors that must be present in the string. But for
+ * search, we need regular trigrams consisting of characters. In the 3rd
+ * stage, the color trigrams are expanded into regular trigrams. Since each
+ * color can represent many characters, the total number of regular trigrams
+ * after expansion could be very large. Because searching an index with
+ * thousands of trigrams would be slow, and would likely produce so many
+ * "false positives" that you would have to traverse a large fraction of the
+ * index, the graph is simplified further in a lossy fashion by removing
+ * color trigrams until the number of trigrams after expansion is below
+ * MAX_TRGM_COUNT threshold. When a color trigram is removed, the states
+ * connected by any arcs labelled with that trigram are merged.
+ *
+ * 4) Pack the graph into a compact representation
+ * -----------------------------------------------
+ * The 2nd and 3rd stages might have eliminated or merged many of the states
+ * and trigrams created earlier, so in this final stage, the graph is
+ * compacted and packed into a simpler struct that contains only the
+ * information needed to evaluate it.
+ *
+ *
+ * OLD COMMENTS:
+ * States of the graph produced in the first stage are marked with "keys". Key
+ * is a pair of a "prefix" and a state of the original automaton. "Prefix" is
+ * pair of colors of last two read characters. So, knowing the prefix is enough
+ * to know what is a color trigram when we read new character with some
+ * particular color. However, we can know single color of prefix or don't know
+ * any color of it. Each state of resulting graph have an "enter key" (with that
+ * key we've entered this state) and a set of keys which are reachable without
+ * reading any predictable color trigram. The algorithm of processing each state
+ * of resulting graph are so:
+ * 1) Add all keys which achievable without reading of any predictable color
+ * trigram.
+ * 2) Add outgoing arcs labeled with trigrams.
+ * Step 2 leads to creation of new states. We use breadth-first algorithm for
+ * processing them.
+ *
+ * Consider this on example of regex ab[cd]. This regex are transformed into
+ * following CNFA (for simplicity of example we don't use colors):
+ *
+ * 4#
+ * c/
+ * a b /
+ * 1* --- 2 ---- 3
+ * \
+ * d\
+ * 5#
+ *
+ * We use * to mark initial state and # to mark final state. It's not depicted,
+ * but states 1, 4, 5 have self-referencing arcs for all possible characters,
+ * because pattern can match to any part of string.
+ *
+ * As the result of first stage we will have following graph:
+ *
+ * abc abd
+ * 2# <---- 1* ----> 3#
+ *
+ * Let us consider the sequence of algorithm work for this graph producing.
+ * We will designate state key as (prefix.s, prefix.ambiguity, nstate).
+ * 1) Create state 1 with enter key (" ", true, 1).
+ * 2) Add key (" a", true, 2) to state 1.
+ * 3) Add key ("ab", false, 3) to state 1.
+ * 4) Add arc from output state 1 to new state 2 with enter key
+ * ("bc", false, 4).
+ * 5) Mark state 2 final because state 4 of source CNFA is marked as final.
+ * 6) Add arc from output state 1 to new state 3 with enter key
+ * ("bd", false, 5).
+ * 7) Mark state 3 final because state 4 of source CNFA is marked as final.
+ *
+ * At the second stage we select color trigrams to expand into simple trigrams.
+ * We're removing color trigrams starting from the most wide. When removing
+ * color trigram we have to merge states connected by corresponding arcs.
+ * It's necessary to not merge initial and final states, because our graph
+ * becomes useless in this case.
+ */
+#include "postgres.h"
+
+#include "trgm.h"
+
+#include "catalog/pg_collation.h"
+#include "fmgr.h"
+#include "miscadmin.h"
+#include "mb/pg_wchar.h"
+#include "nodes/pg_list.h"
+#include "regex/regex.h"
+#undef INFINITY /* avoid conflict of INFINITY definition */
+#include "regex/regguts.h"
+#include "tsearch/ts_locale.h"
+#include "utils/hsearch.h"
+
+/*
+ * Uncomment to print intermediate stages, for exploring and debugging the
+ * algorithm implementation. This produces three graphs in Graphviz .dot
+ * format, in /tmp.
+ */
+#define TRGM_REGEXP_DEBUG
+
+/*---
+ * Following group of parameters are used in order to limit our computations.
+ * Otherwise regex processing could be too slow and memory-consuming.
+ *
+ * MAX_RESULT_STATES - How many states we allow in result CNFA-like graph
+ * MAX_RESULT_ARCS - How many arcs we allow in result CNFA-like graph
+ * MAX_TRGM_COUNT - How many simple trigrams we allow to extract
+ */
+#define MAX_RESULT_STATES 128
+#define MAX_RESULT_ARCS 1024
+#define MAX_TRGM_COUNT 256
+
+/* Virtual color for representation in prefixes and color trigrams. */
+#define EMPTY_COLOR ((color) -1)
+#define UNKNOWN_COLOR ((color) -2)
+
+/*
+ * Widechar trigram datatype for holding trigram before possible conversion into
+ * CRC32
+ */
+typedef color ColorTrgm[3];
+
+/*
+ * Maximum length of multibyte encoding character is 4. So, we can hold it in
+ * 32 bit integer for handling simplicity.
+ */
+typedef uint32 mb_char;
+
+/*----
+ * Attributes of CNFA colors:
+ *
+ * expandable - flag indicates we potentially can expand this
+ * color into distinct characters.
+ * containNonAlpha - flag indicates if color might contain
+ * non-alphanumeric characters (which aren't
+ * extracted into trigrams)
+ * alphaCharsCount - count of characters in color
+ * alphaCharsCountAllocated - allocated size of alphaChars array
+ * alphaChars - array of alphanumeric characters of this color
+ * (which are extracted into trigrams)
+ *
+ * When expandable is false, all other attributes doesn't matter we just think
+ * this color as always unknown character.
+ */
+typedef struct
+{
+ bool expandable;
+ bool containNonAlpha;
+ int alphaCharsCount;
+ int alphaCharsCountAllocated;
+ mb_char *alphaChars;
+} ColorInfo;
+
+/*
+ * Prefix is information about colors of last two read characters when coming
+ * into specific CNFA state. These colors could have special values
+ * UNKNOWN_COLOR and EMPTY_COLOR. UNKNOWN_COLOR means that we read some
+ * character of unexpandable color. EMPTY_COLOR means that we read
+ * non-alphanumeric character.
+ */
+typedef struct
+{
+ color s[2];
+} TrgmPrefix;
+
+/*
+ * "Key" of resulting state: pair of prefix and source CNFA state.
+ */
+typedef struct
+{
+ TrgmPrefix prefix;
+ int nstate;
+} TrgmStateKey;
+
+/*---
+ * State of resulting graph.
+ *
+ * enterKey - a key with which we can enter this state
+ * keys - all keys achievable without reading any predictable trigram
+ * arcs - outgoing arcs of this state
+ * parent - parent state if this state has been merged
+ * children - children states if this state has been merged
+ * fin - flag indicated this state is final
+ * init - flag indicated this state is initial
+ * queued - flag indicated this state is queued in CNFA-like graph
+ * construction
+ * number - number of this state (use at the package stage)
+ */
+typedef struct TrgmState
+{
+ TrgmStateKey enterKey;
+ List *arcs;
+ bool fin;
+ bool init;
+
+ /* (stage 2) */
+ List *keys;
+ bool queued;
+
+ struct TrgmState *parent;
+ List *children;
+ int number;
+} TrgmState;
+
+/*
+ * Arc in the transformed graph. Arc is labeled with trigram.
+ */
+typedef struct
+{
+ TrgmState *target;
+ ColorTrgm trgm;
+} TrgmArc;
+
+/*
+ * Information about arc of specific color trigram: contain pointers to the
+ * source and target states. (stage 3)
+ */
+typedef struct
+{
+ TrgmState *source;
+ TrgmState *target;
+} ArcInfo;
+
+/*---
+ * Information about color trigram: (stage 3)
+ *
+ * trgm - trigram itself
+ * number - number of this trigram (used in the package stage)
+ * count - number of simple trigrams fitting into this color trigram
+ * expanded - flag indicates this color trigram is expanded into simple trigrams
+ * arcs - list of all arcs labeled with this color trigram.
+ */
+typedef struct
+{
+ ColorTrgm trgm;
+ int number;
+ int count;
+ bool expanded;
+ List *arcs;
+} ColorTrgmInfo;
+
+/*---
+ * Data structure representing all the data we need during regex processing.
+ * Initially we set "cnfa" to cnfa of regex, write color information info
+ * "colorInfo" and set "overflowed" flag to false. And the stage of trigram
+ * CFNA-like graph creation "states", "initState" and "arcsCount" are filled.
+ * "owerflowed" flag could be set in case of overflow. Then we collect array
+ * of all present color trigrams to "colorTrgms" and "colorTrgmsCount" and
+ * expand them into "trg" and "totalTrgmCount".
+ *
+ * cnfa - source CFNA of regex
+ * colorInfo - processed information of regex colors
+ * ncolors - number of colors in colorInfo
+ * states - hash of states of resulting graph
+ * initState - pointer to initial state of resulting graph
+ * arcsCount - total number of arcs of resulting graph (for resource
+ * limiting)
+ * colorTrgms - array of all color trigrams presented in graph
+ * colorTrgmsCount - count of that color trigrams
+ * totalTrgmCount - total count of extracted simple trigrams
+ * queue - queue for CFNA-like graph construction
+ * overflowed - if set, we have exceeded resource limit for transformation
+ */
+typedef struct
+{
+ /*
+ * Source CNFA of the regexp, and color information extracted from it
+ * (stage 1)
+ */
+ struct cnfa *cnfa;
+ ColorInfo *colorInfo;
+ int ncolors;
+
+ /* Transformed graph (stage 2) */
+ HTAB *states;
+ TrgmState *initState;
+ int arcsCount;
+ List *queue;
+ bool overflowed;
+
+ /* Information about distinct color trigrams in the graph (stage 3) */
+ ColorTrgmInfo *colorTrgms;
+
+ int colorTrgmsCount;
+ int totalTrgmCount;
+} TrgmCNFA;
+
+
+/*
+ * Final, compact representation of CNFA-like graph.
+ */
+typedef struct
+{
+ int targetState;
+ int colorTrgm;
+} PackedArc;
+
+typedef struct
+{
+ int arcsCount;
+ PackedArc *arcs;
+} PackedState;
+
+struct PackedGraph
+{
+ /*
+ * colorTrigramsCount and colorTrigramsGroups contain information
+ * about how trigrams are grouped into color trigrams. "colorTrigramsCount"
+ * represents total count of color trigrams and "colorTrigramGroups" contain
+ * number of simple trigrams in each color trigram.
+ */
+ int colorTrigramsCount;
+ int *colorTrigramGroups;
+
+ /*
+ * "states" points to definition of "statesCount" states. 0 state is
+ * always initial state and 1 state is always final state. Each state's
+ * "arcs" points to "arcsCount" description of arcs. Each arc describe by
+ * number of color trigram and number of target state (both are zero-based).
+ */
+ int statesCount;
+ PackedState *states;
+
+ /* Temporary work space for trigramsMatchGraph() */
+ bool *colorTrigramsActive;
+ bool *statesActive;
+};
+
+
+/* prototypes for private functions */
+static bool activateState(PackedGraph *graph, int stateno);
+static ColorInfo *getColorInfo(regex_t *regex, int *ncolors);
+static TrgmState *getState(TrgmCNFA *trgmCNFA, TrgmStateKey *key);
+static void transformGraph(TrgmCNFA *trgmCNFA);
+static bool selectColorTrigrams(TrgmCNFA *trgmCNFA);
+static TRGM *expandColorTrigrams(TrgmCNFA *trgmCNFA);
+static PackedGraph *packGraph(TrgmCNFA *trgmCNFA, MemoryContext context);
+
+#ifdef TRGM_REGEXP_DEBUG
+static void printSourceCNFA(struct cnfa *cnfa, ColorInfo *colors, int ncolors);
+static void printTrgmCNFA(TrgmCNFA *trgmCNFA);
+static void printPackedGraph(PackedGraph *packedGraph, TRGM *trigrams);
+#endif
+
+
+/*
+ * Main entry point to process a regular expression. Returns a packed graph
+ * representation of the regular expression, or NULL if the regular expression
+ * was too complex.
+ */
+TRGM *
+createTrgmCNFA(text *text_re, MemoryContext context, PackedGraph **graph)
+{
+ struct guts *g;
+ TrgmCNFA trgmCNFA;
+ regex_t *regex;
+ TRGM *trg;
+
+ /*
+ * Stage 1: Compile the regexp into a CNFA, using the PostgreSQL regexp
+ * library.
+ */
+#ifdef IGNORECASE
+ regex = RE_compile_and_cache(text_re, REG_ADVANCED | REG_ICASE, DEFAULT_COLLATION_OID);
+#else
+ regex = RE_compile_and_cache(text_re, REG_ADVANCED, DEFAULT_COLLATION_OID);
+#endif
+ g = (struct guts *) regex->re_guts;
+ trgmCNFA.cnfa = &g->search;
+
+ /* Collect color information from the CNFA */
+ trgmCNFA.colorInfo = getColorInfo(regex, &trgmCNFA.ncolors);
+
+#ifdef TRGM_REGEXP_DEBUG
+ printSourceCNFA(&g->search, trgmCNFA.colorInfo, trgmCNFA.ncolors);
+#endif
+
+ /*
+ * Stage 2: Create a transformed graph from the source CNFA.
+ */
+ transformGraph(&trgmCNFA);
+
+#ifdef TRGM_REGEXP_DEBUG
+ printTrgmCNFA(&trgmCNFA);
+#endif
+
+ if (trgmCNFA.initState->fin)
+ return NULL;
+
+ /*
+ * Stage 3: Select color trigrams to expand.
+ */
+ if (!selectColorTrigrams(&trgmCNFA))
+ return NULL;
+
+ /*
+ * Stage 4: Expand color trigrams and pack graph into final representation.
+ */
+ trg = expandColorTrigrams(&trgmCNFA);
+
+ *graph = packGraph(&trgmCNFA, context);
+
+#ifdef TRGM_REGEXP_DEBUG
+ printPackedGraph(*graph, trg);
+#endif
+
+ return trg;
+}
+
+/*
+ * Main entry point for evaluating a graph.
+ */
+bool
+trigramsMatchGraph(PackedGraph *graph, bool *check)
+{
+ int i,
+ j,
+ k;
+
+ /*
+ * Reset temporary working areas.
+ */
+ memset(graph->colorTrigramsActive, 0,
+ sizeof(bool) * graph->colorTrigramsCount);
+ memset(graph->statesActive, 0, sizeof(bool) * graph->statesCount);
+
+ /* Check which color trigrams were matched. */
+ j = 0;
+ for (i = 0; i < graph->colorTrigramsCount; i++)
+ {
+ int cnt = graph->colorTrigramGroups[i];
+ for (k = j; k < j + cnt; k++)
+ {
+ if (check[k])
+ {
+ /*
+ * Found one matched trigram in the group. Can skip the rest
+ * of them and go to the next group.
+ */
+ graph->colorTrigramsActive[i] = true;
+ break;
+ }
+ }
+ j = j + cnt;
+ }
+
+ /* Recursively evaluate graph, starting from initial state */
+ return activateState(graph, 0);
+}
+
+/*
+ * Recursive function for graph state activation. Returns true if state
+ * activation leads to activation of final state.
+ */
+static bool
+activateState(PackedGraph *graph, int stateno)
+{
+ PackedState *state = &graph->states[stateno];
+ int cnt = state->arcsCount;
+ int i;
+
+ graph->statesActive[stateno] = true;
+
+ /* Loop over arcs */
+ for (i = 0; i < cnt; i++)
+ {
+ PackedArc *arc = (PackedArc *) &state->arcs[i];
+ /*
+ * If corresponding color trigram is present then activate the
+ * corresponding state.
+ */
+ if (graph->colorTrigramsActive[arc->colorTrgm])
+ {
+ if (arc->targetState == 1)
+ return true; /* reached final state */
+ if (!graph->statesActive[arc->targetState])
+ {
+ if (activateState(graph, arc->targetState))
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+/*---------------------
+ * Subroutines for pre-processing the color map.
+ *---------------------
+ */
+
+/*
+ * Convert pg_wchar to multibyte character.
+ */
+static mb_char
+convertPgWchar(pg_wchar c)
+{
+ /*
+ * "s" has enough of space for a multibyte character of 4 bytes, and
+ * a zero-byte at the end.
+ */
+ char s[5];
+ char *lowerCased;
+ mb_char result;
+
+ if (c == 0)
+ return 0;
+
+ MemSet(s, 0, sizeof(s));
+ pg_wchar2mb_with_len(&c, s, 1);
+
+ /* Convert to lowercase if needed */
+#ifdef IGNORECASE
+ lowerCased = lowerstr(s);
+ if (strncmp(lowerCased, s, 4))
+ return 0;
+#else
+ lowerCased = s;
+#endif
+ strncpy((char *) &result, lowerCased, 4);
+ return result;
+}
+
+/*
+ * Add new character into color information
+ */
+static void
+addCharToColor(ColorInfo *colorInfo, pg_wchar c)
+{
+ if (!colorInfo->alphaChars)
+ {
+ colorInfo->alphaCharsCountAllocated = 16;
+ colorInfo->alphaChars = (pg_wchar *) palloc(sizeof(pg_wchar) *
+ colorInfo->alphaCharsCountAllocated);
+ }
+ if (colorInfo->alphaCharsCount >= colorInfo->alphaCharsCountAllocated)
+ {
+ colorInfo->alphaCharsCountAllocated *= 2;
+ colorInfo->alphaChars = (pg_wchar *) repalloc(colorInfo->alphaChars,
+ sizeof(pg_wchar) * colorInfo->alphaCharsCountAllocated);
+ }
+ colorInfo->alphaChars[colorInfo->alphaCharsCount++] = c;
+}
+
+/*
+ * Recursive function which scans colormap and updates color attributes in
+ * ColorInfo structure. Colormap is a tree which maps character to colors.
+ * The tree contains 4 levels. Each level corresponding to byte of character.
+ * Non-leaf nodes of tree contain pointers to descending tree nodes for each
+ * byte value. Leaf nodes of tree contain color numbers for each byte value.
+ * Potentially colormap could be very large, but most part of the colormap
+ * points to zero colors. That's tree nodes which corresponds to only zero
+ * color can be reused.
+ */
+static void
+scanColorMap(union tree tree[NBYTS], union tree *t, ColorInfo *colorInfos,
+ int level, pg_wchar p)
+{
+ int i;
+
+ check_stack_depth();
+
+ if (level < NBYTS - 1)
+ {
+ /* non-leaf node */
+ for (i = 0; i < BYTTAB; i++)
+ {
+ /*
+ * This condition checks if all underlying levels express zero
+ * color. Zero color uses multiple links to same tree node. So,
+ * avoid scanning it because it's expensive.
+ */
+ if (t->tptr[i] == &tree[level + 1])
+ continue;
+ /* Recursive scanning of next level color table */
+ scanColorMap(tree, t->tptr[i], colorInfos, level + 1, (p << 8) | i);
+ }
+ }
+ else
+ {
+ /* leaf node */
+ p <<= 8;
+ for (i = 0; i < BYTTAB; i++)
+ {
+ ColorInfo *colorInfo = &colorInfos[t->tcolor[i]];
+ pg_wchar c;
+
+ if (!colorInfo->expandable)
+ continue;
+
+ /* Convert to multibyte character */
+ c = convertPgWchar(p | i);
+
+ if (!c)
+ continue;
+
+ /* Update color attributes according to next character */
+ if (ISWORDCHR((char *)&c))
+ addCharToColor(colorInfo, c);
+ else
+ colorInfo->containNonAlpha = true;
+ }
+ }
+}
+
+/*
+ * Fill ColorInfo structure for each color by scanning colormap.
+ */
+static ColorInfo *
+getColorInfo(regex_t *regex, int *ncolors)
+{
+ struct guts *g;
+ struct colormap *cm;
+ ColorInfo *result;
+ int colorsCount;
+ int i;
+
+ g = (struct guts *) regex->re_guts;
+ cm = &g->cmap;
+ *ncolors = colorsCount = cm->max + 1;
+
+ result = (ColorInfo *) palloc0(colorsCount * sizeof(ColorInfo));
+
+ /*
+ * Zero color is a default color which contains all characters that aren't
+ * in explicitly expressed classes. Mark that we can expect everything
+ * from it.
+ */
+ result[0].expandable = false;
+ for (i = 1; i < colorsCount; i++)
+ result[i].expandable = true;
+
+ scanColorMap(cm->tree, &cm->tree[0], result, 0, 0);
+
+ return result;
+}
+
+/*---------------------
+ * Subroutines for transforming original CNFA graph into a color trigram
+ * graph.
+ *---------------------
+ */
+
+/*
+ * Check if prefix1 "contains" prefix2. "contains" mean that any exact prefix
+ * (which no ambiguity) which satisfy to prefix2 also satisfy to prefix1.
+ */
+static bool
+prefixContains(TrgmPrefix *prefix1, TrgmPrefix *prefix2)
+{
+ if (prefix1->s[1] == UNKNOWN_COLOR)
+ {
+ /* Fully ambiguous prefix contains everything */
+ return true;
+ }
+ else if (prefix1->s[0] == UNKNOWN_COLOR)
+ {
+ /*
+ * Prefix with only first unknown color contains every prefix with same
+ * second color.
+ */
+ if (prefix1->s[1] == prefix2->s[1])
+ return true;
+ else
+ return false;
+ }
+ else
+ {
+ /* Exact prefix contains only exactly same prefix */
+ if (prefix1->s[0] == prefix2->s[0] && prefix1->s[1] == prefix2->s[1])
+ return true;
+ else
+ return false;
+ }
+}
+
+/*
+ * Add all keys that can be reached without reading any color trigram to
+ * state of CNFA-like graph on color trigrams.
+ */
+static void
+addKeys(TrgmCNFA *trgmCNFA, TrgmState *state, TrgmStateKey *key)
+{
+ struct carc *s;
+ TrgmStateKey destKey;
+ ListCell *cell, *prev, *next;
+ TrgmStateKey *keyCopy;
+
+ MemSet(&destKey, 0, sizeof(TrgmStateKey));
+
+ /* Adjust list of keys with new one */
+ prev = NULL;
+ cell = list_head(state->keys);
+ while (cell)
+ {
+ TrgmStateKey *existingKey = (TrgmStateKey *) lfirst(cell);
+ next = lnext(cell);
+ if (existingKey->nstate == key->nstate)
+ {
+ if (prefixContains(&existingKey->prefix, &key->prefix))
+ {
+ /* This old key already covers the new key. Nothing to do */
+ return;
+ }
+ if (prefixContains(&key->prefix, &existingKey->prefix))
+ {
+ /*
+ * The new key covers this old key. Remove the old key, it's
+ * no longer needed once we add this key to the list.
+ */
+ state->keys = list_delete_cell(state->keys, cell, prev);
+ }
+ else
+ prev = cell;
+ }
+ else
+ prev = cell;
+ cell = next;
+ }
+
+ keyCopy = (TrgmStateKey *) palloc(sizeof(TrgmStateKey));
+ memcpy(keyCopy, key, sizeof(TrgmStateKey));
+ state->keys = lappend(state->keys, keyCopy);
+
+ /* Mark final state */
+ if (key->nstate == trgmCNFA->cnfa->post)
+ {
+ state->fin = true;
+ return;
+ }
+
+ /*
+ * Loop through all outgoing arcs from the corresponding state in the
+ * original CNFA.
+ */
+ s = trgmCNFA->cnfa->states[key->nstate];
+ while (s->co != COLORLESS)
+ {
+ ColorInfo *colorInfo;
+
+ if (s->co == trgmCNFA->cnfa->bos[1])
+ {
+ /* Start of line (^) */
+ destKey.nstate = s->to;
+
+ /* Mark prefix as start of new color trigram */
+ destKey.prefix.s[0] = EMPTY_COLOR;
+ destKey.prefix.s[1] = EMPTY_COLOR;
+
+ /* Add key to this state */
+ addKeys(trgmCNFA, state, &destKey);
+ if (state->fin)
+ return;
+ }
+ else if (s->co == trgmCNFA->cnfa->eos[1])
+ {
+ /* End of string ($) */
+ if (key->prefix.s[0] == UNKNOWN_COLOR ||
+ key->prefix.s[1] == EMPTY_COLOR)
+ {
+ destKey.nstate = s->to;
+
+ /*
+ * Let's think prefix to become ambiguous (in order to prevent
+ * latter fiddling around with keys).
+ */
+ destKey.prefix.s[1] = UNKNOWN_COLOR;
+ destKey.prefix.s[0] = UNKNOWN_COLOR;
+
+ /* Prefix is ambiguous, add key to the same state */
+ addKeys(trgmCNFA, state, &destKey);
+ if (state->fin)
+ return;
+ }
+ }
+ else
+ {
+ /* Regular color */
+ colorInfo = &trgmCNFA->colorInfo[s->co];
+
+ if (colorInfo->expandable)
+ {
+ if (colorInfo->containNonAlpha &&
+ (key->prefix.s[0] == UNKNOWN_COLOR ||
+ key->prefix.s[1] == EMPTY_COLOR))
+ {
+ /*
+ * When color contain non-alphanumeric character we should
+ * add empty key with empty prefix.
+ */
+ destKey.prefix.s[0] = EMPTY_COLOR;
+ destKey.prefix.s[1] = EMPTY_COLOR;
+ destKey.nstate = s->to;
+ addKeys(trgmCNFA, state, &destKey);
+ if (state->fin)
+ return;
+ }
+
+ if (colorInfo->alphaCharsCount > 0 &&
+ key->prefix.s[0] == UNKNOWN_COLOR)
+ {
+ /* Add corresponding key when prefix was unknown */
+ destKey.prefix.s[0] = key->prefix.s[1];
+ destKey.prefix.s[1] = s->co;
+ destKey.nstate = s->to;
+ addKeys(trgmCNFA, state, &destKey);
+ if (state->fin)
+ return;
+ }
+ }
+ else
+ {
+ /*
+ * Unexpandable color. Add corresponding key to this state.
+ */
+ destKey.nstate = s->to;
+ destKey.prefix.s[0] = UNKNOWN_COLOR;
+ destKey.prefix.s[1] = UNKNOWN_COLOR;
+ addKeys(trgmCNFA, state, &destKey);
+ if (state->fin)
+ return;
+ }
+ }
+ s++;
+ }
+}
+
+/*
+ * Add outgoing arc from state if needed.
+ */
+static void
+addArc(TrgmCNFA *trgmCNFA, TrgmState *state, TrgmStateKey *key,
+ TrgmStateKey *destKey, color co)
+{
+ TrgmArc *arc;
+ ListCell *cell2;
+
+ /* Check whether we actually can add the arc */
+ if (key->prefix.s[0] == UNKNOWN_COLOR ||
+ (key->prefix.s[1] == EMPTY_COLOR && co == EMPTY_COLOR))
+ return;
+
+ /* If we have the same key here, we don't need to add new arc */
+ foreach(cell2, state->keys)
+ {
+ TrgmStateKey *key2 = (TrgmStateKey *) lfirst(cell2);
+ if (key2->nstate == destKey->nstate &&
+ prefixContains(&key2->prefix, &destKey->prefix))
+ {
+ return;
+ }
+ }
+
+ /* Not found, add new arc */
+ arc = (TrgmArc *) palloc(sizeof(TrgmArc));
+ arc->target = getState(trgmCNFA, destKey);
+ arc->trgm[0] = key->prefix.s[0];
+ arc->trgm[1] = key->prefix.s[1];
+ arc->trgm[2] = co;
+ state->arcs = lappend(state->arcs, arc);
+ trgmCNFA->arcsCount++;
+}
+
+/*
+ * Add outgoing arcs from given state.
+ */
+static void
+addArcs(TrgmCNFA *trgmCNFA, TrgmState *state)
+{
+ struct carc *s;
+ TrgmStateKey destKey;
+ ListCell *cell;
+
+ MemSet(&destKey, 0, sizeof(TrgmStateKey));
+
+ /*
+ * Iterate over keys associated with this output state.
+ */
+ foreach(cell, state->keys)
+ {
+ TrgmStateKey *key = (TrgmStateKey *) lfirst(cell);
+ s = trgmCNFA->cnfa->states[key->nstate];
+ while (s->co != COLORLESS)
+ {
+ ColorInfo *colorInfo;
+ if (s->co == trgmCNFA->cnfa->bos[1])
+ {
+ /* Should be already handled by addKeys. */
+ }
+ else if (s->co == trgmCNFA->cnfa->eos[1])
+ {
+ /* End of the string ($) */
+ destKey.nstate = s->to;
+
+ /* Assume prefix to become ambiguous after end of the string */
+ destKey.prefix.s[1] = UNKNOWN_COLOR;
+ destKey.prefix.s[0] = UNKNOWN_COLOR;
+
+ addArc(trgmCNFA, state, key, &destKey, EMPTY_COLOR);
+ }
+ else
+ {
+ /* Regular color: try to add outgoing arcs */
+ colorInfo = &trgmCNFA->colorInfo[s->co];
+
+ if (colorInfo->expandable)
+ {
+ if (colorInfo->containNonAlpha)
+ {
+ destKey.prefix.s[0] = EMPTY_COLOR;
+ destKey.prefix.s[1] = EMPTY_COLOR;
+ destKey.nstate = s->to;
+ addArc(trgmCNFA, state, key, &destKey, EMPTY_COLOR);
+ }
+
+ if (colorInfo->alphaCharsCount > 0)
+ {
+ destKey.prefix.s[0] = key->prefix.s[1];
+ destKey.prefix.s[1] = s->co;
+ destKey.nstate = s->to;
+ addArc(trgmCNFA, state, key, &destKey, s->co);
+ }
+ }
+ }
+ s++;
+ }
+ }
+}
+
+/*
+ * Get state of trigram CNFA-like graph of given enter key and queue it for
+ * processing if needed.
+ */
+static TrgmState *
+getState(TrgmCNFA *trgmCNFA, TrgmStateKey *key)
+{
+ bool found;
+ TrgmState *state;
+
+ state = hash_search(trgmCNFA->states, key, HASH_ENTER, &found);
+ if (!found)
+ {
+ /* New state: initialize and queue it */
+ state->arcs = NIL;
+ state->keys = NIL;
+ state->init = false;
+ state->fin = false;
+ state->children = NIL;
+ state->parent = NULL;
+ state->number = -1;
+
+ state->queued = true;
+ trgmCNFA->queue = lappend(trgmCNFA->queue, state);
+ }
+ return state;
+}
+
+/*
+ * Process the state: add keys and then add outgoing arcs.
+ */
+static void
+processState(TrgmCNFA *trgmCNFA, TrgmState *state)
+{
+ addKeys(trgmCNFA, state, &state->enterKey);
+ /*
+ * Add outgoing arc only if state isn't final (we have no interest in arc
+ * if we already match)
+ */
+ if (!state->fin)
+ addArcs(trgmCNFA, state);
+
+ state->queued = false;
+}
+
+/*
+ * Process queue of CFNA-like graph states until all the states are processed.
+ * This algorithm may be stopped due to resources limitation. In this case we
+ * assume every unprocessed branch to immediately finish with matching (this
+ * can give us more false positives but no false negatives) by marking all
+ * unprocessed states as final.
+ */
+static void
+transformGraph(TrgmCNFA *trgmCNFA)
+{
+ HASHCTL hashCtl;
+ TrgmStateKey initkey;
+ TrgmState *initstate;
+
+ trgmCNFA->queue = NIL;
+ trgmCNFA->arcsCount = 0;
+ trgmCNFA->overflowed = false;
+
+ /* Initialize hash of states */
+ hashCtl.keysize = sizeof(TrgmStateKey);
+ hashCtl.entrysize = sizeof(TrgmState);
+ hashCtl.hcxt = CurrentMemoryContext;
+ hashCtl.hash = tag_hash;
+ hashCtl.match = memcmp;
+ trgmCNFA->states =
+ hash_create("Trigram CNFA",
+ 1024,
+ &hashCtl,
+ HASH_ELEM | HASH_CONTEXT | HASH_FUNCTION | HASH_COMPARE);
+
+ /* Create initial state */
+ MemSet(&initkey, 0, sizeof(TrgmStateKey));
+ initkey.prefix.s[0] = UNKNOWN_COLOR;
+ initkey.prefix.s[1] = UNKNOWN_COLOR;
+ initkey.nstate = trgmCNFA->cnfa->pre;
+
+ initstate = getState(trgmCNFA, &initkey);
+ initstate->init = true;
+ trgmCNFA->initState = initstate;
+
+ /*
+ * Recursively build the transformed graph by processing queue of
+ * states (Breadth-first search).
+ */
+ while (trgmCNFA->queue != NIL)
+ {
+ TrgmState *state = (TrgmState *) linitial(trgmCNFA->queue);
+
+ state->queued = false;
+ trgmCNFA->queue = list_delete_first(trgmCNFA->queue);
+
+ /*
+ * If we overflow then just mark state as final. Otherwise do actual
+ * processing.
+ */
+ if (trgmCNFA->overflowed)
+ state->fin = true;
+ else
+ processState(trgmCNFA, state);
+
+ /* Did we overflow? */
+ if (trgmCNFA->arcsCount > MAX_RESULT_ARCS ||
+ hash_get_num_entries(trgmCNFA->states) > MAX_RESULT_STATES)
+ {
+ trgmCNFA->overflowed = true;
+ }
+ }
+}
+
+
+/*---------------------
+ * Subroutines for expanding color trigrams into regular trigrams.
+ *---------------------
+ */
+
+/*
+ * Compare function for sorting of color trigrams by their colors.
+ */
+static int
+colorTrgmInfoCmp(const void *p1, const void *p2)
+{
+ const ColorTrgmInfo *c1 = (const ColorTrgmInfo *)p1;
+ const ColorTrgmInfo *c2 = (const ColorTrgmInfo *)p2;
+ return memcmp(c1->trgm, c2->trgm, sizeof(ColorTrgm));
+}
+
+/*
+ * Compare function for sorting color trigrams by descending of their simple
+ * trigrams counts.
+ */
+static int
+colorTrgmInfoCountCmp(const void *p1, const void *p2)
+{
+ const ColorTrgmInfo *c1 = (const ColorTrgmInfo *)p1;
+ const ColorTrgmInfo *c2 = (const ColorTrgmInfo *)p2;
+ if (c1->count < c2->count)
+ return 1;
+ else if (c1->count == c2->count)
+ return 0;
+ else
+ return -1;
+}
+
+/*
+ * Convert trigram into trgm datatype.
+ */
+static void
+fillTrgm(trgm *ptrgm, mb_char s[3])
+{
+ char text[14],
+ *p;
+ int i;
+
+ /* Write multibyte string into "text". */
+ p = text;
+ for (i = 0; i < 3; i++)
+ {
+ int len;
+ if (s[i] != 0)
+ {
+ len = strnlen((char *) &s[i], 4);
+ memcpy(p, &s[i], len);
+ p += len;
+ }
+ else
+ {
+ *p++ = ' ';
+ }
+ }
+ *p = 0;
+
+ /* Extract trigrams from "text" */
+ cnt_trigram(ptrgm, text, p - text);
+}
+
+/*
+ * Merge states of graph.
+ */
+static void
+mergeStates(TrgmState *state1, TrgmState *state2)
+{
+ ListCell *cell;
+
+ Assert(!state1->parent);
+ Assert(!state2->parent);
+ Assert(state1 != state2);
+
+ foreach(cell, state2->children)
+ {
+ TrgmState *state = (TrgmState *) lfirst(cell);
+ state->parent = state1;
+ }
+ state1->init = state1->init || state2->init;
+ state1->fin = state1->fin || state2->fin;
+ state2->parent = state1;
+ state1->children = list_concat(state1->children, state2->children);
+ state1->children = lappend(state1->children, state2);
+ state2->children = NIL;
+}
+
+/*
+ * Get vector of all color trigrams in graph and select which of them to expand
+ * into simple trigrams.
+ */
+static bool
+selectColorTrigrams(TrgmCNFA *trgmCNFA)
+{
+ HASH_SEQ_STATUS scan_status;
+ int arcsCount = trgmCNFA->arcsCount,
+ i;
+ TrgmState *state;
+ ColorTrgmInfo *p1, *p2, *colorTrgms;
+ int totalTrgmCount, number;
+
+ /* Collect color trigrams from all arcs */
+ colorTrgms = (ColorTrgmInfo *) palloc(sizeof(ColorTrgmInfo) * arcsCount);
+ i = 0;
+ hash_seq_init(&scan_status, trgmCNFA->states);
+ while ((state = (TrgmState *) hash_seq_search(&scan_status)) != NULL)
+ {
+ ListCell *cell;
+ foreach(cell, state->arcs)
+ {
+ TrgmArc *arc = (TrgmArc *) lfirst(cell);
+ ArcInfo *arcInfo = (ArcInfo *) palloc(sizeof(ArcInfo));
+ arcInfo->source = state;
+ arcInfo->target = arc->target;
+ colorTrgms[i].arcs = list_make1(arcInfo);
+ colorTrgms[i].expanded = true;
+ memcpy(&colorTrgms[i].trgm, &arc->trgm, sizeof(ColorTrgm));
+ i++;
+ }
+ }
+ Assert(i == arcsCount);
+
+ /* Remove duplicates and merge arcs lists */
+ if (arcsCount >= 2)
+ {
+ qsort(colorTrgms, arcsCount, sizeof(ColorTrgmInfo), colorTrgmInfoCmp);
+ p1 = colorTrgms + 1;
+ p2 = colorTrgms;
+ while (p1 - colorTrgms < arcsCount)
+ {
+ if (memcmp(p1->trgm, p2->trgm, sizeof(ColorTrgm)) != 0)
+ {
+ p2++;
+ *p2 = *p1;
+ }
+ else
+ {
+ p2->arcs = list_concat(p2->arcs, p1->arcs);
+ }
+ p1++;
+ }
+ trgmCNFA->colorTrgmsCount = p2 + 1 - colorTrgms;
+ }
+ else
+ {
+ trgmCNFA->colorTrgmsCount = arcsCount;
+ }
+
+ /* Count number of simple trigrams in each color trigram */
+ totalTrgmCount = 0;
+ for (i = 0; i < trgmCNFA->colorTrgmsCount; i++)
+ {
+ int j, count = 1;
+ ColorTrgmInfo *trgmInfo = &colorTrgms[i];
+ for (j = 0; j < 3; j++)
+ {
+ /* FIXME: count could overflow */
+ if (trgmInfo->trgm[j] != EMPTY_COLOR)
+ count *= trgmCNFA->colorInfo[trgmInfo->trgm[j]].alphaCharsCount;
+ }
+ trgmInfo->count = count;
+ totalTrgmCount += count;
+ }
+
+ /* Sort color trigrams by descending of their simple trigram counts */
+ qsort(colorTrgms, trgmCNFA->colorTrgmsCount, sizeof(ColorTrgmInfo),
+ colorTrgmInfoCountCmp);
+ /*
+ * Remove color trigrams from the graph until total number of simple
+ * trigrams is below MAX_TRGM_COUNT. We start removing from largest color
+ * trigrams which are most promising for reduce total number of simple
+ * trigrams. When removing color trigram we have to merge states connected
+ * by corresponding arcs. It's necessary to not merge initial and final
+ * states, because our graph becomes useless in this case.
+ */
+ for (i = 0;
+ (i < trgmCNFA->colorTrgmsCount) && (totalTrgmCount > MAX_TRGM_COUNT);
+ i++)
+ {
+ ColorTrgmInfo *trgmInfo = &colorTrgms[i];
+ bool canRemove = true;
+ ListCell *cell;
+
+ /*
+ * Does any arc of with this color trigram connect initial and final
+ * states?
+ */
+ foreach(cell, trgmInfo->arcs)
+ {
+ ArcInfo *arcInfo = (ArcInfo *) lfirst(cell);
+ TrgmState *source = arcInfo->source, *target = arcInfo->target;
+ while (source->parent)
+ source = source->parent;
+ while (target->parent)
+ target = target->parent;
+ if ((source->init || target->init) && (source->fin || target->fin))
+ {
+ canRemove = false;
+ break;
+ }
+ }
+ if (!canRemove)
+ continue;
+
+ /* Merge states */
+ foreach(cell, trgmInfo->arcs)
+ {
+ ArcInfo *arcInfo = (ArcInfo *) lfirst(cell);
+ TrgmState *source = arcInfo->source, *target = arcInfo->target;
+ while (source->parent)
+ source = source->parent;
+ while (target->parent)
+ target = target->parent;
+ if (source != target)
+ mergeStates(source, target);
+ }
+
+ trgmInfo->expanded = false;
+ totalTrgmCount -= trgmInfo->count;
+ }
+ trgmCNFA->totalTrgmCount = totalTrgmCount;
+
+ /* Did we succeed in fitting into MAX_TRGM_COUNT? */
+ if (totalTrgmCount > MAX_TRGM_COUNT)
+ return false;
+
+ /*
+ * Sort color trigrams by colors (will be useful for bsearch) and enumerate
+ * expanded color trigrams.
+ */
+ number = 0;
+ qsort(colorTrgms, trgmCNFA->colorTrgmsCount, sizeof(ColorTrgmInfo),
+ colorTrgmInfoCmp);
+ for (i = 0; i < trgmCNFA->colorTrgmsCount; i++)
+ {
+ if (colorTrgms[i].expanded)
+ {
+ colorTrgms[i].number = number;
+ number++;
+ }
+ }
+
+ trgmCNFA->colorTrgms = colorTrgms;
+ return true;
+}
+
+/*
+ * Expand selected color trigrams into regular trigrams.
+ */
+static TRGM *
+expandColorTrigrams(TrgmCNFA *trgmCNFA)
+{
+ TRGM *trg;
+ trgm *p;
+ int i;
+ ColorInfo emptyColor;
+ mb_char emptyChar = 0;
+
+ /* Virtual "empty" color structure for representing zero character */
+ emptyColor.alphaCharsCount = 1;
+ emptyColor.alphaChars = &emptyChar;
+
+ trg = (TRGM *) palloc0(TRGMHDRSIZE +
+ trgmCNFA->totalTrgmCount * sizeof(trgm));
+ trg->flag = ARRKEY;
+ SET_VARSIZE(trg, CALCGTSIZE(ARRKEY, trgmCNFA->totalTrgmCount));
+ p = GETARR(trg);
+ for (i = 0; i < trgmCNFA->colorTrgmsCount; i++)
+ {
+ mb_char s[3];
+ ColorTrgmInfo *colorTrgm = &trgmCNFA->colorTrgms[i];
+ ColorInfo *c[3];
+ int j, i1, i2, i3;
+
+ if (!colorTrgm->expanded)
+ continue;
+
+ /* Get colors */
+ for (j = 0; j < 3; j++)
+ {
+ if (colorTrgm->trgm[j] != EMPTY_COLOR)
+ c[j] = &trgmCNFA->colorInfo[colorTrgm->trgm[j]];
+ else
+ c[j] = &emptyColor;
+ }
+
+ /* Iterate over all possible combinations of color characters */
+ for (i1 = 0; i1 < c[0]->alphaCharsCount; i1++)
+ {
+ s[0] = c[0]->alphaChars[i1];
+ for (i2 = 0; i2 < c[1]->alphaCharsCount; i2++)
+ {
+ s[1] = c[1]->alphaChars[i2];
+ for (i3 = 0; i3 < c[2]->alphaCharsCount; i3++)
+ {
+ s[2] = c[2]->alphaChars[i3];
+ fillTrgm(p, s);
+ p++;
+ }
+ }
+ }
+ }
+ return trg;
+}
+
+/*---------------------
+ * Subroutines for packing the graph into final representation
+ *---------------------
+ */
+
+/*
+ * Temporary structure for representing arc during packaging.
+ */
+typedef struct
+{
+ int sourceState;
+ int targetState;
+ int colorTrgm;
+} PackArcInfo;
+
+/*
+ * Comparison function for arcs during comparison. Compares arcs in following
+ * order: sourceState, colorTrgm, targetState.
+ */
+static int
+packArcInfoCmp(const void *a1, const void *a2)
+{
+ const PackArcInfo *p1 = (const PackArcInfo *) a1;
+ const PackArcInfo *p2 = (const PackArcInfo *) a2;
+
+ if (p1->sourceState < p2->sourceState)
+ return -1;
+ if (p1->sourceState > p2->sourceState)
+ return 1;
+ if (p1->colorTrgm < p2->colorTrgm)
+ return -1;
+ if (p1->colorTrgm > p2->colorTrgm)
+ return 1;
+ if (p1->targetState < p2->targetState)
+ return -1;
+ if (p1->targetState > p2->targetState)
+ return 1;
+ return 0;
+}
+
+/*
+ * Pack resulting graph into final representation.
+ */
+static PackedGraph *
+packGraph(TrgmCNFA *trgmCNFA, MemoryContext context)
+{
+ int number = 2,
+ arcIndex,
+ arcsCount;
+ HASH_SEQ_STATUS scan_status;
+ TrgmState *state;
+ PackArcInfo *arcs, *p1, *p2;
+ PackedArc *packedArcs;
+ PackedGraph *result;
+ int i,
+ j;
+
+ /* Enumerate states */
+ hash_seq_init(&scan_status, trgmCNFA->states);
+ while ((state = (TrgmState *) hash_seq_search(&scan_status)) != NULL)
+ {
+ while (state->parent)
+ state = state->parent;
+
+ if (state->number < 0)
+ {
+ if (state->init)
+ state->number = 0;
+ else if (state->fin)
+ state->number = 1;
+ else
+ {
+ state->number = number;
+ number++;
+ }
+ }
+ }
+
+ /* Collect array of all arcs */
+ arcs = (PackArcInfo *) palloc(sizeof(PackArcInfo) * trgmCNFA->arcsCount);
+ arcIndex = 0;
+ hash_seq_init(&scan_status, trgmCNFA->states);
+ while ((state = (TrgmState *) hash_seq_search(&scan_status)) != NULL)
+ {
+ TrgmState *source = state;
+ ListCell *cell;
+
+ while (source->parent)
+ source = source->parent;
+
+ foreach(cell, state->arcs)
+ {
+ TrgmArc *arc = (TrgmArc *) lfirst(cell);
+ TrgmState *target = arc->target;
+
+ while (target->parent)
+ target = target->parent;
+
+ if (source->number != target->number)
+ {
+ arcs[arcIndex].sourceState = source->number;
+ arcs[arcIndex].targetState = target->number;
+ arcs[arcIndex].colorTrgm = ((ColorTrgmInfo *)bsearch(&arc->trgm,
+ trgmCNFA->colorTrgms, trgmCNFA->colorTrgmsCount,
+ sizeof(ColorTrgmInfo), colorTrgmInfoCmp))->number;
+
+ arcIndex++;
+ }
+ }
+ }
+ qsort(arcs, arcIndex, sizeof(PackArcInfo), packArcInfoCmp);
+
+ /* We could have duplicates because states were merged. Remove them. */
+ p1 = p2 = arcs;
+ while (p1 < arcs + arcIndex)
+ {
+ if (packArcInfoCmp(p1, p2) > 0)
+ {
+ p2++;
+ *p2 = *p1;
+ }
+ p1++;
+ }
+ arcsCount = p2 - arcs + 1;
+
+ /* Write packed representation */
+ result = (PackedGraph *) MemoryContextAlloc(context, sizeof(PackedGraph));
+
+ /* Pack color trigrams information */
+ result->colorTrigramsCount = 0;
+ for (i = 0; i < trgmCNFA->colorTrgmsCount; i++)
+ if (trgmCNFA->colorTrgms[i].expanded)
+ result->colorTrigramsCount++;
+ result->colorTrigramGroups = (int *) MemoryContextAlloc(context,
+ sizeof(int) * result->colorTrigramsCount);
+ j = 0;
+ for (i = 0; i < trgmCNFA->colorTrgmsCount; i++)
+ {
+ if (trgmCNFA->colorTrgms[i].expanded)
+ {
+ result->colorTrigramGroups[j] = trgmCNFA->colorTrgms[i].count;
+ j++;
+ }
+ }
+
+ /* Pack states and arcs information */
+ result->states = (PackedState *) MemoryContextAlloc(context,
+ number * sizeof(PackedState));
+ result->statesCount = number;
+ packedArcs = (PackedArc *) MemoryContextAlloc(context,
+ arcsCount * sizeof(PackedArc));
+ j = 0;
+ for (i = 0; i < number; i++)
+ {
+ int cnt = 0;
+ result->states[i].arcs = &packedArcs[j];
+ while (j < arcsCount && arcs[j].sourceState == i)
+ {
+ packedArcs[j].targetState = arcs[j].targetState;
+ packedArcs[j].colorTrgm = arcs[j].colorTrgm;
+ cnt++;
+ j++;
+ }
+ result->states[i].arcsCount = cnt;
+ }
+
+ /* Allocate memory for evaluation */
+ result->colorTrigramsActive = (bool *) MemoryContextAlloc(context,
+ sizeof(bool) * result->colorTrigramsCount);
+ result->statesActive = (bool *) MemoryContextAlloc(context,
+ sizeof(bool) * result->statesCount);
+
+ return result;
+}
+
+
+/*---------------------
+ * Debugging functions
+ *---------------------
+ */
+
+#ifdef TRGM_REGEXP_DEBUG
+/*
+ * Print initial CNFA, in regexp library's representation
+ */
+static void
+printSourceCNFA(struct cnfa *cnfa, ColorInfo *colors, int ncolors)
+{
+ int state;
+ StringInfoData buf;
+ int i;
+
+ initStringInfo(&buf);
+
+ appendStringInfo(&buf, "\ndigraph sourceCNFA {\n");
+
+ for (state = 0; state < cnfa->nstates; state++)
+ {
+ struct carc *s = cnfa->states[state];
+
+ appendStringInfo(&buf, "s%d", state);
+ if (cnfa->post == state)
+ appendStringInfo(&buf, " [shape = doublecircle]");
+ appendStringInfo(&buf, ";\n");
+
+ while (s->co != COLORLESS)
+ {
+ appendStringInfo(&buf, " s%d -> s%d [label = \"%d\"];\n",
+ state, s->to, s->co);
+ s++;
+ }
+ }
+
+ appendStringInfo(&buf, " node [shape = point ]; initial;\n");
+ appendStringInfo(&buf, " initial -> s%d;\n", cnfa->pre);
+
+ /* Print colors */
+ appendStringInfo(&buf, " { rank = sink;\n");
+ appendStringInfo(&buf, " Colors [shape = none, margin=0, label=<\n");
+
+ for (i = 0; i < ncolors; i++)
+ {
+ ColorInfo *color = &colors[i];
+ int j;
+
+ appendStringInfo(&buf, "<br/>Color %d: ", i);
+
+ if (!color->expandable)
+ appendStringInfo(&buf, "not expandable");
+ else
+ {
+ for (j = 0; j < color->alphaCharsCount; j++)
+ {
+ char s[5];
+ memcpy(s, (char *) &color->alphaChars[j], 4);
+ s[4] = '\0';
+ appendStringInfo(&buf, "%s", s);
+ }
+ }
+ appendStringInfo(&buf, "\n");
+ }
+
+ appendStringInfo(&buf, " >];\n");
+ appendStringInfo(&buf, " }\n");
+
+
+ appendStringInfo(&buf, "}\n");
+
+ {
+ /* dot -Tpng -o /tmp/source.png < /tmp/source.dot */
+ FILE *fp = fopen("/tmp/source.dot", "w");
+ fprintf(fp, "%s", buf.data);
+ fclose(fp);
+ }
+
+ pfree(buf.data);
+}
+
+/*
+ * Print trigram-based CNFA-like graph.
+ */
+static void
+printTrgmCNFA(TrgmCNFA *trgmCNFA)
+{
+ StringInfoData buf;
+ HASH_SEQ_STATUS scan_status;
+ TrgmState *state;
+ TrgmState *initstate = NULL;
+
+ initStringInfo(&buf);
+
+ appendStringInfo(&buf, "\ndigraph transformedCNFA {\n");
+
+ hash_seq_init(&scan_status, trgmCNFA->states);
+ while ((state = (TrgmState *) hash_seq_search(&scan_status)) != NULL)
+ {
+ ListCell *cell;
+
+ appendStringInfo(&buf, "s%p", state);
+ if (state->fin)
+ appendStringInfo(&buf, " [shape = doublecircle]");
+ if (state->init)
+ initstate = state;
+ appendStringInfo(&buf, " [label = \"%d\"]", state->enterKey.nstate);
+ appendStringInfo(&buf, ";\n");
+
+ foreach(cell, state->arcs)
+ {
+ TrgmArc *arc = (TrgmArc *) lfirst(cell);
+
+ appendStringInfo(&buf, " s%p -> s%p [label = \"%d %d %d\"];\n",
+ (void *) state, (void *) arc->target,
+ (uint32) arc->trgm[0], (uint32) arc->trgm[1], (uint32) arc->trgm[2]);
+ }
+ }
+
+ if (initstate)
+ {
+ appendStringInfo(&buf, " node [shape = point ]; initial;\n");
+ appendStringInfo(&buf, " initial -> s%p;\n", (void *) initstate);
+ }
+ appendStringInfo(&buf, "}\n");
+
+ {
+ /* dot -Tpng -o /tmp/transformed.png < /tmp/transformed.dot */
+ FILE *fp = fopen("/tmp/transformed.dot", "w");
+ fprintf(fp, "%s", buf.data);
+ fclose(fp);
+ }
+
+ pfree(buf.data);
+}
+
+/*
+ * Print final packed representation of resulting trigram-based CNFA-like graph.
+ */
+static void
+printPackedGraph(PackedGraph *packedGraph, TRGM *trigrams)
+{
+ int i;
+ trgm *p;
+ StringInfoData buf;
+ initStringInfo(&buf);
+
+ appendStringInfo(&buf, "\ndigraph packedGraph {\n");
+
+ for (i = 0; i < packedGraph->statesCount; i++)
+ {
+ PackedState *state = &packedGraph->states[i];
+ int j;
+
+ appendStringInfo(&buf, " s%d", i);
+ if (i == 1)
+ appendStringInfo(&buf, " [shape = doublecircle]");
+
+ appendStringInfo(&buf, " [label = <s%d>];\n", i);
+
+ for (j = 0; j < state->arcsCount; j++)
+ {
+ PackedArc *arc = &state->arcs[j];
+ appendStringInfo(&buf, " s%d -> s%d [label = \"trigram %d\"];\n",
+ i, arc->targetState, arc->colorTrgm);
+ }
+ }
+
+ appendStringInfo(&buf, " node [shape = point ]; initial;\n");
+ appendStringInfo(&buf, " initial -> s%d;\n", 0);
+
+ /* Print trigrams */
+ appendStringInfo(&buf, " { rank = sink;\n");
+ appendStringInfo(&buf, " Trigrams [shape = none, margin=0, label=<\n");
+
+ p = GETARR(trigrams);
+ for (i = 0; i < packedGraph->colorTrigramsCount; i++)
+ {
+ int count = packedGraph->colorTrigramGroups[i];
+ int j;
+
+ appendStringInfo(&buf, "<br/>Trigram %d: ", i);
+
+ for (j = 0; j < count; j++)
+ {
+ if (j > 0)
+ appendStringInfo(&buf, ", ");
+ appendStringInfo(&buf, "\"%c%c%c\"", (*p)[0], (*p)[1], (*p)[2]);
+ p++;
+ }
+ }
+ appendStringInfo(&buf, " >];\n");
+ appendStringInfo(&buf, " }\n");
+
+ appendStringInfo(&buf, "}\n");
+
+ {
+ /* dot -Tpng -o /tmp/packed.png < /tmp/packed.dot */
+ FILE *fp = fopen("/tmp/packed.dot", "w");
+ fprintf(fp, "%s", buf.data);
+ fclose(fp);
+ }
+
+ pfree(buf.data);
+}
+#endif
diff --git a/doc/src/sgml/pgtrgm.sgml b/doc/src/sgml/pgtrgm.sgml
index 30e5355..c4105e1 100644
--- a/doc/src/sgml/pgtrgm.sgml
+++ b/doc/src/sgml/pgtrgm.sgml
@@ -145,9 +145,10 @@
operator classes that allow you to create an index over a text column for
the purpose of very fast similarity searches. These index types support
the above-described similarity operators, and additionally support
- trigram-based index searches for <literal>LIKE</> and <literal>ILIKE</>
- queries. (These indexes do not support equality nor simple comparison
- operators, so you may need a regular B-tree index too.)
+ trigram-based index searches for <literal>LIKE</>, <literal>ILIKE</>,
+ <literal>~</> and <literal>~*</> queries. (These indexes do not
+ support equality nor simple comparison operators, so you may need a
+ regular B-tree index too.)
</para>
<para>
@@ -203,6 +204,23 @@ SELECT * FROM test_trgm WHERE t LIKE '%foo%bar';
</para>
<para>
+ Beginning in <productname>PostgreSQL</> 9.3, these index types also support
+ index searches for <literal>~</> and <literal>~*</> (regular expression
+ matching), for example
+<programlisting>
+SELECT * FROM test_trgm WHERE t ~ '(foo|bar)';
+</programlisting>
+ The index search works by extracting trigrams from the regular expression
+ and then looking these up in the index. The more trigrams could be
+ extracted from regular expression, the more effective the index search is.
+ Unlike B-tree based searches, the search string need not be left-anchored.
+ However, sometimes regular expression is too complex for analysis, then
+ it performs the same as when no trigrams can be extracted from regular
+ expression. In this situation full index scan or sequential scan will
+ be performed depending on query plan.
+ </para>
+
+ <para>
The choice between GiST and GIN indexing depends on the relative
performance characteristics of GiST and GIN, which are discussed elsewhere.
As a rule of thumb, a GIN index is faster to search than a GiST index, but
diff --git a/src/backend/utils/adt/regexp.c b/src/backend/utils/adt/regexp.c
index 6a89fab..e29e734 100644
--- a/src/backend/utils/adt/regexp.c
+++ b/src/backend/utils/adt/regexp.c
@@ -128,7 +128,7 @@ static Datum build_regexp_split_result(regexp_matches_ctx *splitctx);
* Pattern is given in the database encoding. We internally convert to
* an array of pg_wchar, which is what Spencer's regex package wants.
*/
-static regex_t *
+regex_t *
RE_compile_and_cache(text *text_re, int cflags, Oid collation)
{
int text_re_len = VARSIZE_ANY_EXHDR(text_re);
diff --git a/src/include/regex/regex.h b/src/include/regex/regex.h
index 616c2c6..7e19e8a 100644
--- a/src/include/regex/regex.h
+++ b/src/include/regex/regex.h
@@ -171,5 +171,6 @@ extern int pg_regprefix(regex_t *, pg_wchar **, size_t *);
extern void pg_regfree(regex_t *);
extern size_t pg_regerror(int, const regex_t *, char *, size_t);
extern void pg_set_regex_collation(Oid collation);
+regex_t *RE_compile_and_cache(text *text_re, int cflags, Oid collation);
#endif /* _REGEX_H_ */