diff --git a/contrib/fasttrun/Makefile b/contrib/fasttrun/Makefile new file mode 100644 index 0000000..06c47a1 --- /dev/null +++ b/contrib/fasttrun/Makefile @@ -0,0 +1,15 @@ +MODULE_big = fasttrun +OBJS = fasttrun.o +DATA_built = fasttrun.sql +DOCS = README.fasttrun +REGRESS = fasttrun + +ifdef USE_PGXS +PGXS := $(shell pg_config --pgxs) +include $(PGXS) +else +subdir = contrib/fasttrun +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/contrib/fasttrun/README.fasttrun b/contrib/fasttrun/README.fasttrun new file mode 100644 index 0000000..4b1dfdc --- /dev/null +++ b/contrib/fasttrun/README.fasttrun @@ -0,0 +1,17 @@ +select fasttruncate('TABLE_NAME'); + +Function truncates the temporary table and doesn't grow +pg_class size. + +Warning: function isn't transaction safe! + +For tests: +create or replace function f() returns void as $$ +begin +for i in 1..1000 +loop + PERFORM fasttruncate('tt1'); +end loop; +end; +$$ language plpgsql; + diff --git a/contrib/fasttrun/expected/fasttrun.out b/contrib/fasttrun/expected/fasttrun.out new file mode 100644 index 0000000..9914e77 --- /dev/null +++ b/contrib/fasttrun/expected/fasttrun.out @@ -0,0 +1,115 @@ +\set ECHO none +create table persist ( a int ); +insert into persist values (1); +select fasttruncate('persist'); +ERROR: Relation isn't a temporary table +insert into persist values (2); +select * from persist order by a; + a +--- + 1 + 2 +(2 rows) + +create temp table temp1 (a int); +insert into temp1 values (1); +BEGIN; +create temp table temp2 (a int); +insert into temp2 values (1); +select * from temp1 order by a; + a +--- + 1 +(1 row) + +select * from temp2 order by a; + a +--- + 1 +(1 row) + +insert into temp1 (select * from generate_series(1,10000)); +insert into temp2 (select * from generate_series(1,11000)); +analyze temp2; +select relname, relpages>0, reltuples>0 from pg_class where relname in ('temp1', 'temp2') order by relname; + relname | ?column? | ?column? +---------+----------+---------- + temp1 | f | f + temp2 | t | t +(2 rows) + +select fasttruncate('temp1'); + fasttruncate +-------------- + +(1 row) + +select fasttruncate('temp2'); + fasttruncate +-------------- + +(1 row) + +insert into temp1 values (-2); +insert into temp2 values (-2); +select * from temp1 order by a; + a +---- + -2 +(1 row) + +select * from temp2 order by a; + a +---- + -2 +(1 row) + +COMMIT; +select * from temp1 order by a; + a +---- + -2 +(1 row) + +select * from temp2 order by a; + a +---- + -2 +(1 row) + +select relname, relpages>0, reltuples>0 from pg_class where relname in ('temp1', 'temp2') order by relname; + relname | ?column? | ?column? +---------+----------+---------- + temp1 | f | f + temp2 | f | f +(2 rows) + +select fasttruncate('temp1'); + fasttruncate +-------------- + +(1 row) + +select fasttruncate('temp2'); + fasttruncate +-------------- + +(1 row) + +select * from temp1 order by a; + a +--- +(0 rows) + +select * from temp2 order by a; + a +--- +(0 rows) + +select relname, relpages>0, reltuples>0 from pg_class where relname in ('temp1', 'temp2') order by relname; + relname | ?column? | ?column? +---------+----------+---------- + temp1 | f | f + temp2 | f | f +(2 rows) + diff --git a/contrib/fasttrun/fasttrun.c b/contrib/fasttrun/fasttrun.c new file mode 100644 index 0000000..e9407e3 --- /dev/null +++ b/contrib/fasttrun/fasttrun.c @@ -0,0 +1,73 @@ +#include "postgres.h" + +#include "access/genam.h" +#include "access/heapam.h" +#include "miscadmin.h" +#include "storage/lmgr.h" +#include "storage/bufmgr.h" +#include "catalog/namespace.h" +#include "utils/lsyscache.h" +#include "utils/builtins.h" +#include +#include +#include +#include +#include +#include + +#ifdef PG_MODULE_MAGIC +PG_MODULE_MAGIC; +#endif + +PG_FUNCTION_INFO_V1(fasttruncate); +Datum fasttruncate(PG_FUNCTION_ARGS); +Datum +fasttruncate(PG_FUNCTION_ARGS) { + text *name=PG_GETARG_TEXT_P(0); + char *relname; + List *relname_list; + RangeVar *relvar; + Oid relOid; + Relation rel; + bool makeanalyze = false; + + relname = palloc( VARSIZE(name) + 1); + memcpy(relname, VARDATA(name), VARSIZE(name)-VARHDRSZ); + relname[ VARSIZE(name)-VARHDRSZ ] = '\0'; + + relname_list = stringToQualifiedNameList(relname); + relvar = makeRangeVarFromNameList(relname_list); + relOid = RangeVarGetRelid(relvar, AccessExclusiveLock, false); + + if ( get_rel_relkind(relOid) != RELKIND_RELATION ) + elog(ERROR,"Relation isn't a ordinary table"); + + rel = heap_open(relOid, NoLock); + + if ( !isTempNamespace(get_rel_namespace(relOid)) ) + elog(ERROR,"Relation isn't a temporary table"); + + heap_truncate(list_make1_oid(relOid)); + + if ( rel->rd_rel->relpages > 0 || rel->rd_rel->reltuples > 0 ) + makeanalyze = true; + + /* + * heap_truncate doesn't unlock the table, + * so we should unlock it. + */ + + heap_close(rel, AccessExclusiveLock); + + if ( makeanalyze ) { + VacuumStmt *vac = makeNode(VacuumStmt); + + vac->options = VACOPT_ANALYZE; + vac->relation = relvar; + + vacuum(vac, relOid, false, + GetAccessStrategy(BAS_VACUUM), false, false); + } + + PG_RETURN_VOID(); +} diff --git a/contrib/fasttrun/fasttrun.sql.in b/contrib/fasttrun/fasttrun.sql.in new file mode 100644 index 0000000..0895c77 --- /dev/null +++ b/contrib/fasttrun/fasttrun.sql.in @@ -0,0 +1,8 @@ +BEGIN; + + +CREATE OR REPLACE FUNCTION fasttruncate(text) +RETURNS void AS 'MODULE_PATHNAME' +LANGUAGE C RETURNS NULL ON NULL INPUT VOLATILE; + +COMMIT; diff --git a/contrib/fasttrun/sql/fasttrun.sql b/contrib/fasttrun/sql/fasttrun.sql new file mode 100644 index 0000000..73beaf4 --- /dev/null +++ b/contrib/fasttrun/sql/fasttrun.sql @@ -0,0 +1,50 @@ +\set ECHO none +\i fasttrun.sql +\set ECHO all + +create table persist ( a int ); +insert into persist values (1); +select fasttruncate('persist'); +insert into persist values (2); +select * from persist order by a; + +create temp table temp1 (a int); +insert into temp1 values (1); + +BEGIN; + +create temp table temp2 (a int); +insert into temp2 values (1); + +select * from temp1 order by a; +select * from temp2 order by a; + +insert into temp1 (select * from generate_series(1,10000)); +insert into temp2 (select * from generate_series(1,11000)); + +analyze temp2; +select relname, relpages>0, reltuples>0 from pg_class where relname in ('temp1', 'temp2') order by relname; + +select fasttruncate('temp1'); +select fasttruncate('temp2'); + +insert into temp1 values (-2); +insert into temp2 values (-2); + +select * from temp1 order by a; +select * from temp2 order by a; + +COMMIT; + +select * from temp1 order by a; +select * from temp2 order by a; + +select relname, relpages>0, reltuples>0 from pg_class where relname in ('temp1', 'temp2') order by relname; + +select fasttruncate('temp1'); +select fasttruncate('temp2'); + +select * from temp1 order by a; +select * from temp2 order by a; + +select relname, relpages>0, reltuples>0 from pg_class where relname in ('temp1', 'temp2') order by relname; diff --git a/contrib/fulleq/Makefile b/contrib/fulleq/Makefile new file mode 100644 index 0000000..bc8bdd2 --- /dev/null +++ b/contrib/fulleq/Makefile @@ -0,0 +1,32 @@ +MODULE_big = fulleq +OBJS = fulleq.o +DATA_built = fulleq.sql +DOCS = README.fulleq +REGRESS = fulleq + +ARGTYPE = bool bytea char name int8 int2 int2vector int4 text \ + oid xid cid oidvector float4 float8 abstime reltime macaddr \ + inet cidr varchar date time timestamp timestamptz \ + interval timetz + +EXTRA_CLEAN = fulleq.sql.in + +ifdef USE_PGXS +PGXS := $(shell pg_config --pgxs) +include $(PGXS) +else +subdir = contrib/fulleq +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif + +fulleq.sql.in: fulleq.sql.in.in + echo 'BEGIN;' > $@ + echo 'SET search_path = public;' >> $@ + for type in $(ARGTYPE); \ + do \ + sed -e "s/ARGTYPE/$$type/g" < $< >> $@; \ + done + echo 'COMMIT;' >> $@ + diff --git a/contrib/fulleq/README.fulleq b/contrib/fulleq/README.fulleq new file mode 100644 index 0000000..a677c49 --- /dev/null +++ b/contrib/fulleq/README.fulleq @@ -0,0 +1,3 @@ +Introduce operator == which returns true when +operands are equal or both are nulls. + diff --git a/contrib/fulleq/expected/fulleq.out b/contrib/fulleq/expected/fulleq.out new file mode 100644 index 0000000..4842d8c --- /dev/null +++ b/contrib/fulleq/expected/fulleq.out @@ -0,0 +1,61 @@ +\set ECHO none +select 4::int == 4; + ?column? +---------- + t +(1 row) + +select 4::int == 5; + ?column? +---------- + f +(1 row) + +select 4::int == NULL; + ?column? +---------- + f +(1 row) + +select NULL::int == 5; + ?column? +---------- + f +(1 row) + +select NULL::int == NULL; + ?column? +---------- + t +(1 row) + +select '4'::text == '4'; + ?column? +---------- + t +(1 row) + +select '4'::text == '5'; + ?column? +---------- + f +(1 row) + +select '4'::text == NULL; + ?column? +---------- + f +(1 row) + +select NULL::text == '5'; + ?column? +---------- + f +(1 row) + +select NULL::text == NULL; + ?column? +---------- + t +(1 row) + diff --git a/contrib/fulleq/fulleq.c b/contrib/fulleq/fulleq.c new file mode 100644 index 0000000..8e8e17b --- /dev/null +++ b/contrib/fulleq/fulleq.c @@ -0,0 +1,72 @@ +#include "postgres.h" +#include "fmgr.h" +#include "access/hash.h" +#include "utils/builtins.h" +#include "utils/bytea.h" +#include "utils/int8.h" +#include "utils/nabstime.h" +#include "utils/timestamp.h" +#include "utils/date.h" + +#ifdef PG_MODULE_MAGIC +PG_MODULE_MAGIC; +#endif + +#define NULLHASHVALUE (-2147483647) + +#define FULLEQ_FUNC(type, cmpfunc, hashfunc) \ +PG_FUNCTION_INFO_V1( isfulleq_##type ); \ +Datum isfulleq_##type(PG_FUNCTION_ARGS); \ +Datum \ +isfulleq_##type(PG_FUNCTION_ARGS) { \ + if ( PG_ARGISNULL(0) && PG_ARGISNULL(1) ) \ + PG_RETURN_BOOL(true); \ + else if ( PG_ARGISNULL(0) || PG_ARGISNULL(1) ) \ + PG_RETURN_BOOL(false); \ + \ + PG_RETURN_DATUM( DirectFunctionCall2( cmpfunc, \ + PG_GETARG_DATUM(0), \ + PG_GETARG_DATUM(1) \ + ) ); \ +} \ + \ +PG_FUNCTION_INFO_V1( fullhash_##type ); \ +Datum fullhash_##type(PG_FUNCTION_ARGS); \ +Datum \ +fullhash_##type(PG_FUNCTION_ARGS) { \ + if ( PG_ARGISNULL(0) ) \ + PG_RETURN_INT32(NULLHASHVALUE); \ + \ + PG_RETURN_DATUM( DirectFunctionCall1( hashfunc, \ + PG_GETARG_DATUM(0) \ + ) ); \ +} + + +FULLEQ_FUNC( bool , booleq , hashchar ); +FULLEQ_FUNC( bytea , byteaeq , hashvarlena ); +FULLEQ_FUNC( char , chareq , hashchar ); +FULLEQ_FUNC( name , nameeq , hashname ); +FULLEQ_FUNC( int8 , int8eq , hashint8 ); +FULLEQ_FUNC( int2 , int2eq , hashint2 ); +FULLEQ_FUNC( int2vector , int2vectoreq , hashint2vector ); +FULLEQ_FUNC( int4 , int4eq , hashint4 ); +FULLEQ_FUNC( text , texteq , hashtext ); +FULLEQ_FUNC( oid , oideq , hashoid ); +FULLEQ_FUNC( xid , xideq , hashint4 ); +FULLEQ_FUNC( cid , cideq , hashint4 ); +FULLEQ_FUNC( oidvector , oidvectoreq , hashoidvector ); +FULLEQ_FUNC( float4 , float4eq , hashfloat4 ); +FULLEQ_FUNC( float8 , float8eq , hashfloat8 ); +FULLEQ_FUNC( abstime , abstimeeq , hashint4 ); +FULLEQ_FUNC( reltime , reltimeeq , hashint4 ); +FULLEQ_FUNC( macaddr , macaddr_eq , hashmacaddr ); +FULLEQ_FUNC( inet , network_eq , hashinet ); +FULLEQ_FUNC( cidr , network_eq , hashinet ); +FULLEQ_FUNC( varchar , texteq , hashtext ); +FULLEQ_FUNC( date , date_eq , hashint4 ); +FULLEQ_FUNC( time , time_eq , hashfloat8 ); +FULLEQ_FUNC( timestamp , timestamp_eq , hashfloat8 ); +FULLEQ_FUNC( timestamptz , timestamp_eq , hashfloat8 ); +FULLEQ_FUNC( interval , interval_eq , interval_hash ); +FULLEQ_FUNC( timetz , timetz_eq , timetz_hash ); diff --git a/contrib/fulleq/fulleq.sql.in.in b/contrib/fulleq/fulleq.sql.in.in new file mode 100644 index 0000000..55e980e --- /dev/null +++ b/contrib/fulleq/fulleq.sql.in.in @@ -0,0 +1,26 @@ +-- For ARGTYPE + +CREATE OR REPLACE FUNCTION isfulleq_ARGTYPE(ARGTYPE, ARGTYPE) +RETURNS bool AS 'MODULE_PATHNAME' +LANGUAGE C CALLED ON NULL INPUT IMMUTABLE; + +CREATE OR REPLACE FUNCTION fullhash_ARGTYPE(ARGTYPE) +RETURNS int4 AS 'MODULE_PATHNAME' +LANGUAGE C CALLED ON NULL INPUT IMMUTABLE; + + +CREATE OPERATOR == ( + LEFTARG = ARGTYPE, + RIGHTARG = ARGTYPE, + PROCEDURE = isfulleq_ARGTYPE, + COMMUTATOR = '==', + RESTRICT = eqsel, + JOIN = eqjoinsel, + HASHES +); + +CREATE OPERATOR CLASS ARGTYPE_fill_ops + FOR TYPE ARGTYPE USING hash AS + OPERATOR 1 ==, + FUNCTION 1 fullhash_ARGTYPE(ARGTYPE); + diff --git a/contrib/fulleq/sql/fulleq.sql b/contrib/fulleq/sql/fulleq.sql new file mode 100644 index 0000000..02b192c --- /dev/null +++ b/contrib/fulleq/sql/fulleq.sql @@ -0,0 +1,16 @@ +\set ECHO none +\i fulleq.sql +\set ECHO all + +select 4::int == 4; +select 4::int == 5; +select 4::int == NULL; +select NULL::int == 5; +select NULL::int == NULL; + +select '4'::text == '4'; +select '4'::text == '5'; +select '4'::text == NULL; +select NULL::text == '5'; +select NULL::text == NULL; + diff --git a/contrib/mchar/Changes b/contrib/mchar/Changes new file mode 100644 index 0000000..b597ee7 --- /dev/null +++ b/contrib/mchar/Changes @@ -0,0 +1,19 @@ +0.17 add == operation: + a == b => ( a = b or a is null and b is null ) +0.16 fix pg_dump - now mchar in pg_catalog scheme, not public + fix bug in mvarchar_substr() +0.15 add upper()/lower() +0.14 Add ESCAPE for LIKE, SIMILAR TO [ESCAPE], POSIX regexp +0.13 Outer binary format is now different from + inner: it's just a UTF-16 string +0.12 Fix copy binary +0.11 Force UTF-8 convertor if server_encoding='UTF8' +0.10 add (mchar|mvarchar)_(send|recv) functions to + allow binary copying. Note: that functions + don't recode values. +0.9 index support for like, improve recoding functions +0.8 initial suport for like optimizioation with index: + still thres no algo to find the nearest greater string +0.7 hash indexes and enable a hash joins +0.6 implicit casting mchar-mvarchar + cross type comparison operations diff --git a/contrib/mchar/Makefile b/contrib/mchar/Makefile new file mode 100644 index 0000000..27302df --- /dev/null +++ b/contrib/mchar/Makefile @@ -0,0 +1,27 @@ +MODULE_big = mchar +OBJS = mchar_io.o mchar_proc.o mchar_op.o mchar_recode.o \ + mchar_like.o +DATA_built = mchar.sql +DATA = uninstall_mchar.sql +DOCS = README.mchar +REGRESS = init mchar mvarchar mm like compat + +PG_CPPFLAGS=-I/usr/local/include + +ifdef USE_PGXS +PGXS := $(shell pg_config --pgxs) +include $(PGXS) +else +subdir = contrib/mchar +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif + +ifeq ($(PORTNAME),win32) +ICUNAME=icuin +else +ICUNAME=icui18n +endif + +SHLIB_LINK += -L/usr/local/lib -licuuc -l$(ICUNAME) -Wl,-rpath,'$$ORIGIN' diff --git a/contrib/mchar/README.mchar b/contrib/mchar/README.mchar new file mode 100644 index 0000000..479a7d1 --- /dev/null +++ b/contrib/mchar/README.mchar @@ -0,0 +1,20 @@ +MCHAR & VARCHAR + type modifier + length() + substr(str, pos[, length]) + || - concatenation with any (mchar,mvarchar) arguments + < <= = >= > - case-insensitive comparisons (libICU) + &< &<= &= &>= &> - case-sensitive comparisons (libICU) + implicit casting mchar<->mvarchar + B-tree and hash index + LIKE [ESCAPE] + SIMILAR TO [ESCAPE] + ~ (POSIX regexp) + index support for LIKE + + +Authors: + Oleg Bartunov + Teodor Sigaev + + diff --git a/contrib/mchar/expected/compat.out b/contrib/mchar/expected/compat.out new file mode 100644 index 0000000..480a286 --- /dev/null +++ b/contrib/mchar/expected/compat.out @@ -0,0 +1,66 @@ +--- table based checks +select '<' || ch || '>', '<' || vch || '>' from chvch; + ?column? | ?column? +----------------+-------------- + | + | + <1 space > | <1 space > +(3 rows) + +select * from chvch where vch = 'One space'; + ch | vch +--------------+------------ + One space | One space +(1 row) + +select * from chvch where vch = 'One space '; + ch | vch +--------------+------------ + One space | One space +(1 row) + +select * from ch where chcol = 'abcd' order by chcol; + chcol +---------------------------------- + abcd + AbcD +(2 rows) + +select * from ch t1 join ch t2 on t1.chcol = t2.chcol order by t1.chcol, t2.chcol; + chcol | chcol +----------------------------------+---------------------------------- + abcd | AbcD + abcd | abcd + AbcD | AbcD + AbcD | abcd + abcz | abcz + defg | dEfg + defg | defg + dEfg | dEfg + dEfg | defg + ee | Ee + ee | ee + Ee | Ee + Ee | ee +(13 rows) + +select * from ch where chcol > 'abcd' and chcol<'ee'; + chcol +---------------------------------- + abcz + defg + dEfg +(3 rows) + +select * from ch order by chcol; + chcol +---------------------------------- + abcd + AbcD + abcz + defg + dEfg + ee + Ee +(7 rows) + diff --git a/contrib/mchar/expected/init.out b/contrib/mchar/expected/init.out new file mode 100644 index 0000000..19aa916 --- /dev/null +++ b/contrib/mchar/expected/init.out @@ -0,0 +1,32 @@ +-- +-- first, define the datatype. Turn off echoing so that expected file +-- does not depend on contents of mchar.sql. +-- +\set ECHO none +psql:mchar.sql:20: NOTICE: type "mchar" is not yet defined +DETAIL: Creating a shell type definition. +psql:mchar.sql:25: NOTICE: argument type mchar is only a shell +psql:mchar.sql:30: NOTICE: argument type mchar is only a shell +psql:mchar.sql:35: NOTICE: return type mchar is only a shell +psql:mchar.sql:59: NOTICE: type "mvarchar" is not yet defined +DETAIL: Creating a shell type definition. +psql:mchar.sql:64: NOTICE: argument type mvarchar is only a shell +psql:mchar.sql:69: NOTICE: argument type mvarchar is only a shell +psql:mchar.sql:74: NOTICE: return type mvarchar is only a shell +create table ch ( + chcol mchar(32) +) without oids; +insert into ch values('abcd'); +insert into ch values('AbcD'); +insert into ch values('abcz'); +insert into ch values('defg'); +insert into ch values('dEfg'); +insert into ch values('ee'); +insert into ch values('Ee'); +create table chvch ( + ch mchar(12), + vch mvarchar(12) +) without oids; +insert into chvch values('No spaces', 'No spaces'); +insert into chvch values('One space ', 'One space '); +insert into chvch values('1 space', '1 space '); diff --git a/contrib/mchar/expected/like.out b/contrib/mchar/expected/like.out new file mode 100644 index 0000000..3a57082 --- /dev/null +++ b/contrib/mchar/expected/like.out @@ -0,0 +1,791 @@ +-- simplest examples +-- E061-04 like predicate +SELECT 'hawkeye'::mchar LIKE 'h%' AS "true"; + true +------ + t +(1 row) + +SELECT 'hawkeye'::mchar NOT LIKE 'h%' AS "false"; + false +------- + f +(1 row) + +SELECT 'hawkeye'::mchar LIKE 'H%' AS "true"; + true +------ + t +(1 row) + +SELECT 'hawkeye'::mchar NOT LIKE 'H%' AS "false"; + false +------- + f +(1 row) + +SELECT 'hawkeye'::mchar LIKE 'indio%' AS "false"; + false +------- + f +(1 row) + +SELECT 'hawkeye'::mchar NOT LIKE 'indio%' AS "true"; + true +------ + t +(1 row) + +SELECT 'hawkeye'::mchar LIKE 'h%eye' AS "true"; + true +------ + t +(1 row) + +SELECT 'hawkeye'::mchar NOT LIKE 'h%eye' AS "false"; + false +------- + f +(1 row) + +SELECT 'indio'::mchar LIKE '_ndio' AS "true"; + true +------ + t +(1 row) + +SELECT 'indio'::mchar NOT LIKE '_ndio' AS "false"; + false +------- + f +(1 row) + +SELECT 'indio'::mchar LIKE 'in__o' AS "true"; + true +------ + t +(1 row) + +SELECT 'indio'::mchar NOT LIKE 'in__o' AS "false"; + false +------- + f +(1 row) + +SELECT 'indio'::mchar LIKE 'in_o' AS "false"; + false +------- + f +(1 row) + +SELECT 'indio'::mchar NOT LIKE 'in_o' AS "true"; + true +------ + t +(1 row) + +SELECT 'hawkeye'::mvarchar LIKE 'h%' AS "true"; + true +------ + t +(1 row) + +SELECT 'hawkeye'::mvarchar NOT LIKE 'h%' AS "false"; + false +------- + f +(1 row) + +SELECT 'hawkeye'::mvarchar LIKE 'H%' AS "true"; + true +------ + t +(1 row) + +SELECT 'hawkeye'::mvarchar NOT LIKE 'H%' AS "false"; + false +------- + f +(1 row) + +SELECT 'hawkeye'::mvarchar LIKE 'indio%' AS "false"; + false +------- + f +(1 row) + +SELECT 'hawkeye'::mvarchar NOT LIKE 'indio%' AS "true"; + true +------ + t +(1 row) + +SELECT 'hawkeye'::mvarchar LIKE 'h%eye' AS "true"; + true +------ + t +(1 row) + +SELECT 'hawkeye'::mvarchar NOT LIKE 'h%eye' AS "false"; + false +------- + f +(1 row) + +SELECT 'indio'::mvarchar LIKE '_ndio' AS "true"; + true +------ + t +(1 row) + +SELECT 'indio'::mvarchar NOT LIKE '_ndio' AS "false"; + false +------- + f +(1 row) + +SELECT 'indio'::mvarchar LIKE 'in__o' AS "true"; + true +------ + t +(1 row) + +SELECT 'indio'::mvarchar NOT LIKE 'in__o' AS "false"; + false +------- + f +(1 row) + +SELECT 'indio'::mvarchar LIKE 'in_o' AS "false"; + false +------- + f +(1 row) + +SELECT 'indio'::mvarchar NOT LIKE 'in_o' AS "true"; + true +------ + t +(1 row) + +-- unused escape character +SELECT 'hawkeye'::mchar LIKE 'h%'::mchar ESCAPE '#' AS "true"; + true +------ + t +(1 row) + +SELECT 'hawkeye'::mchar NOT LIKE 'h%'::mchar ESCAPE '#' AS "false"; + false +------- + f +(1 row) + +SELECT 'indio'::mchar LIKE 'ind_o'::mchar ESCAPE '$' AS "true"; + true +------ + t +(1 row) + +SELECT 'indio'::mchar NOT LIKE 'ind_o'::mchar ESCAPE '$' AS "false"; + false +------- + f +(1 row) + +-- escape character +-- E061-05 like predicate with escape clause +SELECT 'h%'::mchar LIKE 'h#%'::mchar ESCAPE '#' AS "true"; + true +------ + t +(1 row) + +SELECT 'h%'::mchar NOT LIKE 'h#%'::mchar ESCAPE '#' AS "false"; + false +------- + f +(1 row) + +SELECT 'h%wkeye'::mchar LIKE 'h#%'::mchar ESCAPE '#' AS "false"; + false +------- + f +(1 row) + +SELECT 'h%wkeye'::mchar NOT LIKE 'h#%'::mchar ESCAPE '#' AS "true"; + true +------ + t +(1 row) + +SELECT 'h%wkeye'::mchar LIKE 'h#%%'::mchar ESCAPE '#' AS "true"; + true +------ + t +(1 row) + +SELECT 'h%wkeye'::mchar NOT LIKE 'h#%%'::mchar ESCAPE '#' AS "false"; + false +------- + f +(1 row) + +SELECT 'h%awkeye'::mchar LIKE 'h#%a%k%e'::mchar ESCAPE '#' AS "true"; + true +------ + t +(1 row) + +SELECT 'h%awkeye'::mchar NOT LIKE 'h#%a%k%e'::mchar ESCAPE '#' AS "false"; + false +------- + f +(1 row) + +SELECT 'indio'::mchar LIKE '_ndio'::mchar ESCAPE '$' AS "true"; + true +------ + t +(1 row) + +SELECT 'indio'::mchar NOT LIKE '_ndio'::mchar ESCAPE '$' AS "false"; + false +------- + f +(1 row) + +SELECT 'i_dio'::mchar LIKE 'i$_d_o'::mchar ESCAPE '$' AS "true"; + true +------ + t +(1 row) + +SELECT 'i_dio'::mchar NOT LIKE 'i$_d_o'::mchar ESCAPE '$' AS "false"; + false +------- + f +(1 row) + +SELECT 'i_dio'::mchar LIKE 'i$_nd_o'::mchar ESCAPE '$' AS "false"; + false +------- + f +(1 row) + +SELECT 'i_dio'::mchar NOT LIKE 'i$_nd_o'::mchar ESCAPE '$' AS "true"; + true +------ + t +(1 row) + +SELECT 'i_dio'::mchar LIKE 'i$_d%o'::mchar ESCAPE '$' AS "true"; + true +------ + t +(1 row) + +SELECT 'i_dio'::mchar NOT LIKE 'i$_d%o'::mchar ESCAPE '$' AS "false"; + false +------- + f +(1 row) + +-- escape character same as pattern character +SELECT 'maca'::mchar LIKE 'm%aca' ESCAPE '%'::mchar AS "true"; + true +------ + t +(1 row) + +SELECT 'maca'::mchar NOT LIKE 'm%aca' ESCAPE '%'::mchar AS "false"; + false +------- + f +(1 row) + +SELECT 'ma%a'::mchar LIKE 'm%a%%a' ESCAPE '%'::mchar AS "true"; + true +------ + t +(1 row) + +SELECT 'ma%a'::mchar NOT LIKE 'm%a%%a' ESCAPE '%'::mchar AS "false"; + false +------- + f +(1 row) + +SELECT 'bear'::mchar LIKE 'b_ear' ESCAPE '_'::mchar AS "true"; + true +------ + t +(1 row) + +SELECT 'bear'::mchar NOT LIKE 'b_ear'::mchar ESCAPE '_' AS "false"; + false +------- + f +(1 row) + +SELECT 'be_r'::mchar LIKE 'b_e__r' ESCAPE '_'::mchar AS "true"; + true +------ + t +(1 row) + +SELECT 'be_r'::mchar NOT LIKE 'b_e__r' ESCAPE '_'::mchar AS "false"; + false +------- + f +(1 row) + +SELECT 'be_r'::mchar LIKE '__e__r' ESCAPE '_'::mchar AS "false"; + false +------- + f +(1 row) + +SELECT 'be_r'::mchar NOT LIKE '__e__r'::mchar ESCAPE '_' AS "true"; + true +------ + t +(1 row) + +-- unused escape character +SELECT 'hawkeye'::mvarchar LIKE 'h%'::mvarchar ESCAPE '#' AS "true"; + true +------ + t +(1 row) + +SELECT 'hawkeye'::mvarchar NOT LIKE 'h%'::mvarchar ESCAPE '#' AS "false"; + false +------- + f +(1 row) + +SELECT 'indio'::mvarchar LIKE 'ind_o'::mvarchar ESCAPE '$' AS "true"; + true +------ + t +(1 row) + +SELECT 'indio'::mvarchar NOT LIKE 'ind_o'::mvarchar ESCAPE '$' AS "false"; + false +------- + f +(1 row) + +-- escape character +-- E061-05 like predicate with escape clause +SELECT 'h%'::mvarchar LIKE 'h#%'::mvarchar ESCAPE '#' AS "true"; + true +------ + t +(1 row) + +SELECT 'h%'::mvarchar NOT LIKE 'h#%'::mvarchar ESCAPE '#' AS "false"; + false +------- + f +(1 row) + +SELECT 'h%wkeye'::mvarchar LIKE 'h#%'::mvarchar ESCAPE '#' AS "false"; + false +------- + f +(1 row) + +SELECT 'h%wkeye'::mvarchar NOT LIKE 'h#%'::mvarchar ESCAPE '#' AS "true"; + true +------ + t +(1 row) + +SELECT 'h%wkeye'::mvarchar LIKE 'h#%%'::mvarchar ESCAPE '#' AS "true"; + true +------ + t +(1 row) + +SELECT 'h%wkeye'::mvarchar NOT LIKE 'h#%%'::mvarchar ESCAPE '#' AS "false"; + false +------- + f +(1 row) + +SELECT 'h%awkeye'::mvarchar LIKE 'h#%a%k%e'::mvarchar ESCAPE '#' AS "true"; + true +------ + t +(1 row) + +SELECT 'h%awkeye'::mvarchar NOT LIKE 'h#%a%k%e'::mvarchar ESCAPE '#' AS "false"; + false +------- + f +(1 row) + +SELECT 'indio'::mvarchar LIKE '_ndio'::mvarchar ESCAPE '$' AS "true"; + true +------ + t +(1 row) + +SELECT 'indio'::mvarchar NOT LIKE '_ndio'::mvarchar ESCAPE '$' AS "false"; + false +------- + f +(1 row) + +SELECT 'i_dio'::mvarchar LIKE 'i$_d_o'::mvarchar ESCAPE '$' AS "true"; + true +------ + t +(1 row) + +SELECT 'i_dio'::mvarchar NOT LIKE 'i$_d_o'::mvarchar ESCAPE '$' AS "false"; + false +------- + f +(1 row) + +SELECT 'i_dio'::mvarchar LIKE 'i$_nd_o'::mvarchar ESCAPE '$' AS "false"; + false +------- + f +(1 row) + +SELECT 'i_dio'::mvarchar NOT LIKE 'i$_nd_o'::mvarchar ESCAPE '$' AS "true"; + true +------ + t +(1 row) + +SELECT 'i_dio'::mvarchar LIKE 'i$_d%o'::mvarchar ESCAPE '$' AS "true"; + true +------ + t +(1 row) + +SELECT 'i_dio'::mvarchar NOT LIKE 'i$_d%o'::mvarchar ESCAPE '$' AS "false"; + false +------- + f +(1 row) + +-- escape character same as pattern character +SELECT 'maca'::mvarchar LIKE 'm%aca' ESCAPE '%'::mvarchar AS "true"; + true +------ + t +(1 row) + +SELECT 'maca'::mvarchar NOT LIKE 'm%aca' ESCAPE '%'::mvarchar AS "false"; + false +------- + f +(1 row) + +SELECT 'ma%a'::mvarchar LIKE 'm%a%%a' ESCAPE '%'::mvarchar AS "true"; + true +------ + t +(1 row) + +SELECT 'ma%a'::mvarchar NOT LIKE 'm%a%%a' ESCAPE '%'::mvarchar AS "false"; + false +------- + f +(1 row) + +SELECT 'bear'::mvarchar LIKE 'b_ear' ESCAPE '_'::mvarchar AS "true"; + true +------ + t +(1 row) + +SELECT 'bear'::mvarchar NOT LIKE 'b_ear'::mvarchar ESCAPE '_' AS "false"; + false +------- + f +(1 row) + +SELECT 'be_r'::mvarchar LIKE 'b_e__r' ESCAPE '_'::mvarchar AS "true"; + true +------ + t +(1 row) + +SELECT 'be_r'::mvarchar NOT LIKE 'b_e__r' ESCAPE '_'::mvarchar AS "false"; + false +------- + f +(1 row) + +SELECT 'be_r'::mvarchar LIKE '__e__r' ESCAPE '_'::mvarchar AS "false"; + false +------- + f +(1 row) + +SELECT 'be_r'::mvarchar NOT LIKE '__e__r'::mvarchar ESCAPE '_' AS "true"; + true +------ + t +(1 row) + +-- similar to +SELECT 'abc'::mchar SIMILAR TO 'abc'::mchar AS "true"; + true +------ + t +(1 row) + +SELECT 'abc'::mchar SIMILAR TO 'a'::mchar AS "false"; + false +------- + f +(1 row) + +SELECT 'abc'::mchar SIMILAR TO '%(b|d)%'::mchar AS "true"; + true +------ + t +(1 row) + +SELECT 'abc'::mchar SIMILAR TO '(b|c)%'::mchar AS "false"; + false +------- + f +(1 row) + +SELECT 'h%'::mchar SIMILAR TO 'h#%'::mchar AS "false"; + false +------- + f +(1 row) + +SELECT 'h%'::mchar SIMILAR TO 'h#%'::mchar ESCAPE '#' AS "true"; + true +------ + t +(1 row) + +SELECT 'abc'::mvarchar SIMILAR TO 'abc'::mvarchar AS "true"; + true +------ + t +(1 row) + +SELECT 'abc'::mvarchar SIMILAR TO 'a'::mvarchar AS "false"; + false +------- + f +(1 row) + +SELECT 'abc'::mvarchar SIMILAR TO '%(b|d)%'::mvarchar AS "true"; + true +------ + t +(1 row) + +SELECT 'abc'::mvarchar SIMILAR TO '(b|c)%'::mvarchar AS "false"; + false +------- + f +(1 row) + +SELECT 'h%'::mvarchar SIMILAR TO 'h#%'::mvarchar AS "false"; + false +------- + f +(1 row) + +SELECT 'h%'::mvarchar SIMILAR TO 'h#%'::mvarchar ESCAPE '#' AS "true"; + true +------ + t +(1 row) + +-- index support +SELECT * from ch where chcol like 'aB_d' order by chcol using &<; + chcol +---------------------------------- + AbcD + abcd +(2 rows) + +SELECT * from ch where chcol like 'aB%d' order by chcol using &<; + chcol +---------------------------------- + AbcD + abcd +(2 rows) + +SELECT * from ch where chcol like 'aB%' order by chcol using &<; + chcol +---------------------------------- + AbcD + abcd + abcz +(3 rows) + +SELECT * from ch where chcol like '%BC%' order by chcol using &<; + chcol +---------------------------------- + AbcD + abcd + abcz +(3 rows) + +set enable_seqscan = off; +SELECT * from ch where chcol like 'aB_d' order by chcol using &<; + chcol +---------------------------------- + AbcD + abcd +(2 rows) + +SELECT * from ch where chcol like 'aB%d' order by chcol using &<; + chcol +---------------------------------- + AbcD + abcd +(2 rows) + +SELECT * from ch where chcol like 'aB%' order by chcol using &<; + chcol +---------------------------------- + AbcD + abcd + abcz +(3 rows) + +SELECT * from ch where chcol like '%BC%' order by chcol using &<; + chcol +---------------------------------- + AbcD + abcd + abcz +(3 rows) + +set enable_seqscan = on; +create table testt (f1 mchar(10)); +insert into testt values ('Abc-000001'); +insert into testt values ('Abc-000002'); +insert into testt values ('0000000001'); +insert into testt values ('0000000002'); +select f1 from testt where f1::mvarchar like E'Abc\\-%'::mvarchar; + f1 +------------ + Abc-000001 + Abc-000002 +(2 rows) + +select * from testt where f1::mchar like E'Abc\\-%'::mchar; + f1 +------------ + Abc-000001 + Abc-000002 +(2 rows) + +create index testindex on testt(f1); +set enable_seqscan=off; +select f1 from testt where f1::mvarchar like E'Abc\\-%'::mvarchar; + f1 +------------ + Abc-000001 + Abc-000002 +(2 rows) + +select * from testt where f1::mchar like E'Abc\\-%'::mchar; + f1 +------------ + Abc-000001 + Abc-000002 +(2 rows) + +set enable_seqscan = on; +drop table testt; +create table testt (f1 mvarchar(10)); +insert into testt values ('Abc-000001'); +insert into testt values ('Abc-000002'); +insert into testt values ('0000000001'); +insert into testt values ('0000000002'); +select f1 from testt where f1::mvarchar like E'Abc\\-%'::mvarchar; + f1 +------------ + Abc-000001 + Abc-000002 +(2 rows) + +select * from testt where f1::mchar like E'Abc\\-%'::mchar; + f1 +------------ + Abc-000001 + Abc-000002 +(2 rows) + +select * from testt where f1::mchar like E'Abc\\- %'::mchar; + f1 +------------ + Abc-000001 + Abc-000002 +(2 rows) + +select * from testt where f1::mchar like E' %'::mchar; + f1 +------------ + Abc-000001 + Abc-000002 + 0000000001 + 0000000002 +(4 rows) + +create index testindex on testt(f1); +set enable_seqscan=off; +select f1 from testt where f1::mvarchar like E'Abc\\-%'::mvarchar; + f1 +------------ + Abc-000001 + Abc-000002 +(2 rows) + +select * from testt where f1::mchar like E'Abc\\-%'::mchar; + f1 +------------ + Abc-000001 + Abc-000002 +(2 rows) + +select * from testt where f1::mchar like E'Abc\\- %'::mchar; + f1 +------------ + Abc-000001 + Abc-000002 +(2 rows) + +select * from testt where f1::mchar like E' %'::mchar; + f1 +------------ + 0000000001 + 0000000002 + Abc-000001 + Abc-000002 +(4 rows) + +set enable_seqscan = on; +drop table testt; +CREATE TABLE test ( code mchar(5) NOT NULL ); +insert into test values('1111 '); +insert into test values('111 '); +insert into test values('11 '); +insert into test values('1 '); +SELECT * FROM test WHERE code LIKE ('% '); + code +------- + 1 +(1 row) + diff --git a/contrib/mchar/expected/mchar.out b/contrib/mchar/expected/mchar.out new file mode 100644 index 0000000..c587424 --- /dev/null +++ b/contrib/mchar/expected/mchar.out @@ -0,0 +1,363 @@ +-- I/O tests +select '1'::mchar; + mchar +------- + 1 +(1 row) + +select '2 '::mchar; + mchar +------- + 2 +(1 row) + +select '10 '::mchar; + mchar +------- + 10 +(1 row) + +select '1'::mchar(2); + mchar +------- + 1 +(1 row) + +select '2 '::mchar(2); + mchar +------- + 2 +(1 row) + +select '3 '::mchar(2); + mchar +------- + 3 +(1 row) + +select '10 '::mchar(2); + mchar +------- + 10 +(1 row) + +select ' '::mchar(10); + mchar +------------ + +(1 row) + +select ' '::mchar; + mchar +------- + +(1 row) + +-- operations & functions +select length('1'::mchar); + length +-------- + 1 +(1 row) + +select length('2 '::mchar); + length +-------- + 1 +(1 row) + +select length('10 '::mchar); + length +-------- + 2 +(1 row) + +select length('1'::mchar(2)); + length +-------- + 1 +(1 row) + +select length('2 '::mchar(2)); + length +-------- + 1 +(1 row) + +select length('3 '::mchar(2)); + length +-------- + 1 +(1 row) + +select length('10 '::mchar(2)); + length +-------- + 2 +(1 row) + +select length(' '::mchar(10)); + length +-------- + 0 +(1 row) + +select length(' '::mchar); + length +-------- + 0 +(1 row) + +select 'asd'::mchar(10) || '>'::mchar(10); + ?column? +---------------------- + asd > +(1 row) + +select length('asd'::mchar(10) || '>'::mchar(10)); + length +-------- + 11 +(1 row) + +select 'asd'::mchar(2) || '>'::mchar(10); + ?column? +-------------- + as> +(1 row) + +select length('asd'::mchar(2) || '>'::mchar(10)); + length +-------- + 3 +(1 row) + +-- Comparisons +select 'asdf'::mchar = 'aSdf'::mchar; + ?column? +---------- + t +(1 row) + +select 'asdf'::mchar = 'aSdf '::mchar; + ?column? +---------- + t +(1 row) + +select 'asdf'::mchar = 'aSdf 1'::mchar(4); + ?column? +---------- + t +(1 row) + +select 'asdf'::mchar = 'aSdf 1'::mchar(5); + ?column? +---------- + t +(1 row) + +select 'asdf'::mchar = 'aSdf 1'::mchar(6); + ?column? +---------- + f +(1 row) + +select 'asdf'::mchar(3) = 'aSdf 1'::mchar(5); + ?column? +---------- + f +(1 row) + +select 'asdf'::mchar(3) = 'aSdf 1'::mchar(3); + ?column? +---------- + t +(1 row) + +select 'asdf'::mchar < 'aSdf'::mchar; + ?column? +---------- + f +(1 row) + +select 'asdf'::mchar < 'aSdf '::mchar; + ?column? +---------- + f +(1 row) + +select 'asdf'::mchar < 'aSdf 1'::mchar(4); + ?column? +---------- + f +(1 row) + +select 'asdf'::mchar < 'aSdf 1'::mchar(5); + ?column? +---------- + f +(1 row) + +select 'asdf'::mchar < 'aSdf 1'::mchar(6); + ?column? +---------- + t +(1 row) + +select 'asdf'::mchar <= 'aSdf'::mchar; + ?column? +---------- + t +(1 row) + +select 'asdf'::mchar <= 'aSdf '::mchar; + ?column? +---------- + t +(1 row) + +select 'asdf'::mchar <= 'aSdf 1'::mchar(4); + ?column? +---------- + t +(1 row) + +select 'asdf'::mchar <= 'aSdf 1'::mchar(5); + ?column? +---------- + t +(1 row) + +select 'asdf'::mchar <= 'aSdf 1'::mchar(6); + ?column? +---------- + t +(1 row) + +select 'asdf'::mchar >= 'aSdf'::mchar; + ?column? +---------- + t +(1 row) + +select 'asdf'::mchar >= 'aSdf '::mchar; + ?column? +---------- + t +(1 row) + +select 'asdf'::mchar >= 'aSdf 1'::mchar(4); + ?column? +---------- + t +(1 row) + +select 'asdf'::mchar >= 'aSdf 1'::mchar(5); + ?column? +---------- + t +(1 row) + +select 'asdf'::mchar >= 'aSdf 1'::mchar(6); + ?column? +---------- + f +(1 row) + +select 'asdf'::mchar > 'aSdf'::mchar; + ?column? +---------- + f +(1 row) + +select 'asdf'::mchar > 'aSdf '::mchar; + ?column? +---------- + f +(1 row) + +select 'asdf'::mchar > 'aSdf 1'::mchar(4); + ?column? +---------- + f +(1 row) + +select 'asdf'::mchar > 'aSdf 1'::mchar(5); + ?column? +---------- + f +(1 row) + +select 'asdf'::mchar > 'aSdf 1'::mchar(6); + ?column? +---------- + f +(1 row) + +select max(ch) from chvch; + max +-------------- + One space +(1 row) + +select min(ch) from chvch; + min +-------------- + 1 space +(1 row) + +select substr('1234567890'::mchar, 3) = '34567890' as "34567890"; + 34567890 +---------- + f +(1 row) + +select substr('1234567890'::mchar, 4, 3) = '456' as "456"; + 456 +----- + t +(1 row) + +select lower('asdfASDF'::mchar); + lower +---------- + asdfasdf +(1 row) + +select upper('asdfASDF'::mchar); + upper +---------- + ASDFASDF +(1 row) + +select 'asd'::mchar == 'aSd'::mchar; + ?column? +---------- + t +(1 row) + +select 'asd'::mchar == 'aCd'::mchar; + ?column? +---------- + f +(1 row) + +select 'asd'::mchar == NULL; + ?column? +---------- + f +(1 row) + +select NULL == 'aCd'::mchar; + ?column? +---------- + f +(1 row) + +select NULL::mchar == NULL; + ?column? +---------- + t +(1 row) + diff --git a/contrib/mchar/expected/mm.out b/contrib/mchar/expected/mm.out new file mode 100644 index 0000000..fa2e924 --- /dev/null +++ b/contrib/mchar/expected/mm.out @@ -0,0 +1,805 @@ +select 'asd'::mchar::mvarchar; + mvarchar +---------- + asd +(1 row) + +select 'asd '::mchar::mvarchar; + mvarchar +---------- + asd +(1 row) + +select 'asd'::mchar(2)::mvarchar; + mvarchar +---------- + as +(1 row) + +select 'asd '::mchar(2)::mvarchar; + mvarchar +---------- + as +(1 row) + +select 'asd'::mchar(5)::mvarchar; + mvarchar +---------- + asd +(1 row) + +select 'asd '::mchar(5)::mvarchar; + mvarchar +---------- + asd +(1 row) + +select 'asd'::mchar::mvarchar(2); + mvarchar +---------- + as +(1 row) + +select 'asd '::mchar::mvarchar(2); + mvarchar +---------- + as +(1 row) + +select 'asd'::mchar(2)::mvarchar(2); + mvarchar +---------- + as +(1 row) + +select 'asd '::mchar(2)::mvarchar(2); + mvarchar +---------- + as +(1 row) + +select 'asd'::mchar(5)::mvarchar(2); + mvarchar +---------- + as +(1 row) + +select 'asd '::mchar(5)::mvarchar(2); + mvarchar +---------- + as +(1 row) + +select 'asd'::mchar::mvarchar(5); + mvarchar +---------- + asd +(1 row) + +select 'asd '::mchar::mvarchar(5); + mvarchar +---------- + asd +(1 row) + +select 'asd'::mchar(2)::mvarchar(5); + mvarchar +---------- + as +(1 row) + +select 'asd '::mchar(2)::mvarchar(5); + mvarchar +---------- + as +(1 row) + +select 'asd'::mchar(5)::mvarchar(5); + mvarchar +---------- + asd +(1 row) + +select 'asd '::mchar(5)::mvarchar(5); + mvarchar +---------- + asd +(1 row) + +select 'asd'::mvarchar::mchar; + mchar +------- + asd +(1 row) + +select 'asd '::mvarchar::mchar; + mchar +------- + asd +(1 row) + +select 'asd'::mvarchar(2)::mchar; + mchar +------- + as +(1 row) + +select 'asd '::mvarchar(2)::mchar; + mchar +------- + as +(1 row) + +select 'asd'::mvarchar(5)::mchar; + mchar +------- + asd +(1 row) + +select 'asd '::mvarchar(5)::mchar; + mchar +------- + asd +(1 row) + +select 'asd'::mvarchar::mchar(2); + mchar +------- + as +(1 row) + +select 'asd '::mvarchar::mchar(2); + mchar +------- + as +(1 row) + +select 'asd'::mvarchar(2)::mchar(2); + mchar +------- + as +(1 row) + +select 'asd '::mvarchar(2)::mchar(2); + mchar +------- + as +(1 row) + +select 'asd'::mvarchar(5)::mchar(2); + mchar +------- + as +(1 row) + +select 'asd '::mvarchar(5)::mchar(2); + mchar +------- + as +(1 row) + +select 'asd'::mvarchar::mchar(5); + mchar +------- + asd +(1 row) + +select 'asd '::mvarchar::mchar(5); + mchar +------- + asd +(1 row) + +select 'asd'::mvarchar(2)::mchar(5); + mchar +------- + as +(1 row) + +select 'asd '::mvarchar(2)::mchar(5); + mchar +------- + as +(1 row) + +select 'asd'::mvarchar(5)::mchar(5); + mchar +------- + asd +(1 row) + +select 'asd '::mvarchar(5)::mchar(5); + mchar +------- + asd +(1 row) + +select 'asd'::mchar || '123'; + ?column? +---------- + asd123 +(1 row) + +select 'asd'::mchar || '123'::mchar; + ?column? +---------- + asd123 +(1 row) + +select 'asd'::mchar || '123'::mvarchar; + ?column? +---------- + asd123 +(1 row) + +select 'asd '::mchar || '123'; + ?column? +---------- + asd123 +(1 row) + +select 'asd '::mchar || '123'::mchar; + ?column? +---------- + asd123 +(1 row) + +select 'asd '::mchar || '123'::mvarchar; + ?column? +---------- + asd123 +(1 row) + +select 'asd '::mchar || '123 '; + ?column? +---------- + asd123 +(1 row) + +select 'asd '::mchar || '123 '::mchar; + ?column? +---------- + asd123 +(1 row) + +select 'asd '::mchar || '123 '::mvarchar; + ?column? +---------- + asd123 +(1 row) + +select 'asd'::mvarchar || '123'; + ?column? +---------- + asd123 +(1 row) + +select 'asd'::mvarchar || '123'::mchar; + ?column? +---------- + asd123 +(1 row) + +select 'asd'::mvarchar || '123'::mvarchar; + ?column? +---------- + asd123 +(1 row) + +select 'asd '::mvarchar || '123'; + ?column? +---------- + asd 123 +(1 row) + +select 'asd '::mvarchar || '123'::mchar; + ?column? +---------- + asd 123 +(1 row) + +select 'asd '::mvarchar || '123'::mvarchar; + ?column? +---------- + asd 123 +(1 row) + +select 'asd '::mvarchar || '123 '; + ?column? +---------- + asd 123 +(1 row) + +select 'asd '::mvarchar || '123 '::mchar; + ?column? +---------- + asd 123 +(1 row) + +select 'asd '::mvarchar || '123 '::mvarchar; + ?column? +---------- + asd 123 +(1 row) + +select 'asd'::mchar(2) || '123'; + ?column? +---------- + as123 +(1 row) + +select 'asd'::mchar(2) || '123'::mchar; + ?column? +---------- + as123 +(1 row) + +select 'asd'::mchar(2) || '123'::mvarchar; + ?column? +---------- + as123 +(1 row) + +select 'asd '::mchar(2) || '123'; + ?column? +---------- + as123 +(1 row) + +select 'asd '::mchar(2) || '123'::mchar; + ?column? +---------- + as123 +(1 row) + +select 'asd '::mchar(2) || '123'::mvarchar; + ?column? +---------- + as123 +(1 row) + +select 'asd '::mchar(2) || '123 '; + ?column? +---------- + as123 +(1 row) + +select 'asd '::mchar(2) || '123 '::mchar; + ?column? +---------- + as123 +(1 row) + +select 'asd '::mchar(2) || '123 '::mvarchar; + ?column? +---------- + as123 +(1 row) + +select 'asd'::mvarchar(2) || '123'; + ?column? +---------- + as123 +(1 row) + +select 'asd'::mvarchar(2) || '123'::mchar; + ?column? +---------- + as123 +(1 row) + +select 'asd'::mvarchar(2) || '123'::mvarchar; + ?column? +---------- + as123 +(1 row) + +select 'asd '::mvarchar(2) || '123'; + ?column? +---------- + as123 +(1 row) + +select 'asd '::mvarchar(2) || '123'::mchar; + ?column? +---------- + as123 +(1 row) + +select 'asd '::mvarchar(2) || '123'::mvarchar; + ?column? +---------- + as123 +(1 row) + +select 'asd '::mvarchar(2) || '123 '; + ?column? +---------- + as123 +(1 row) + +select 'asd '::mvarchar(2) || '123 '::mchar; + ?column? +---------- + as123 +(1 row) + +select 'asd '::mvarchar(2) || '123 '::mvarchar; + ?column? +---------- + as123 +(1 row) + +select 'asd'::mchar(4) || '143'; + ?column? +---------- + asd 143 +(1 row) + +select 'asd'::mchar(4) || '123'::mchar; + ?column? +---------- + asd 123 +(1 row) + +select 'asd'::mchar(4) || '123'::mvarchar; + ?column? +---------- + asd 123 +(1 row) + +select 'asd '::mchar(4) || '123'; + ?column? +---------- + asd 123 +(1 row) + +select 'asd '::mchar(4) || '123'::mchar; + ?column? +---------- + asd 123 +(1 row) + +select 'asd '::mchar(4) || '123'::mvarchar; + ?column? +---------- + asd 123 +(1 row) + +select 'asd '::mchar(4) || '123 '; + ?column? +---------- + asd 123 +(1 row) + +select 'asd '::mchar(4) || '123 '::mchar; + ?column? +---------- + asd 123 +(1 row) + +select 'asd '::mchar(4) || '123 '::mvarchar; + ?column? +---------- + asd 123 +(1 row) + +select 'asd'::mvarchar(4) || '123'; + ?column? +---------- + asd123 +(1 row) + +select 'asd'::mvarchar(4) || '123'::mchar; + ?column? +---------- + asd123 +(1 row) + +select 'asd'::mvarchar(4) || '123'::mvarchar; + ?column? +---------- + asd123 +(1 row) + +select 'asd '::mvarchar(4) || '123'; + ?column? +---------- + asd 123 +(1 row) + +select 'asd '::mvarchar(4) || '123'::mchar; + ?column? +---------- + asd 123 +(1 row) + +select 'asd '::mvarchar(4) || '123'::mvarchar; + ?column? +---------- + asd 123 +(1 row) + +select 'asd '::mvarchar(4) || '123 '; + ?column? +---------- + asd 123 +(1 row) + +select 'asd '::mvarchar(4) || '123 '::mchar; + ?column? +---------- + asd 123 +(1 row) + +select 'asd '::mvarchar(4) || '123 '::mvarchar; + ?column? +---------- + asd 123 +(1 row) + +select 'asd '::mvarchar(4) || '123 '::mchar(4); + ?column? +---------- + asd 123 +(1 row) + +select 'asd '::mvarchar(4) || '123 '::mvarchar(4); + ?column? +---------- + asd 123 +(1 row) + +select 'asd '::mvarchar(4) || '123'::mchar(4); + ?column? +---------- + asd 123 +(1 row) + +select 'asd '::mvarchar(4) || '123'::mvarchar(4); + ?column? +---------- + asd 123 +(1 row) + +select 1 where 'f'::mchar='F'::mvarchar; + ?column? +---------- + 1 +(1 row) + +select 1 where 'f'::mchar='F '::mvarchar; + ?column? +---------- + 1 +(1 row) + +select 1 where 'f '::mchar='F'::mvarchar; + ?column? +---------- + 1 +(1 row) + +select 1 where 'f '::mchar='F '::mvarchar; + ?column? +---------- + 1 +(1 row) + +select 1 where 'f'::mchar='F'::mvarchar(2); + ?column? +---------- + 1 +(1 row) + +select 1 where 'f'::mchar='F '::mvarchar(2); + ?column? +---------- + 1 +(1 row) + +select 1 where 'f '::mchar='F'::mvarchar(2); + ?column? +---------- + 1 +(1 row) + +select 1 where 'f '::mchar='F '::mvarchar(2); + ?column? +---------- + 1 +(1 row) + +select 1 where 'f'::mchar(2)='F'::mvarchar; + ?column? +---------- + 1 +(1 row) + +select 1 where 'f'::mchar(2)='F '::mvarchar; + ?column? +---------- + 1 +(1 row) + +select 1 where 'f '::mchar(2)='F'::mvarchar; + ?column? +---------- + 1 +(1 row) + +select 1 where 'f '::mchar(2)='F '::mvarchar; + ?column? +---------- + 1 +(1 row) + +select 1 where 'f'::mchar(2)='F'::mvarchar(2); + ?column? +---------- + 1 +(1 row) + +select 1 where 'f'::mchar(2)='F '::mvarchar(2); + ?column? +---------- + 1 +(1 row) + +select 1 where 'f '::mchar(2)='F'::mvarchar(2); + ?column? +---------- + 1 +(1 row) + +select 1 where 'f '::mchar(2)='F '::mvarchar(2); + ?column? +---------- + 1 +(1 row) + +select 1 where 'foo'::mchar='FOO'::mvarchar; + ?column? +---------- + 1 +(1 row) + +select 1 where 'foo'::mchar='FOO '::mvarchar; + ?column? +---------- + 1 +(1 row) + +select 1 where 'foo '::mchar='FOO'::mvarchar; + ?column? +---------- + 1 +(1 row) + +select 1 where 'foo '::mchar='FOO '::mvarchar; + ?column? +---------- + 1 +(1 row) + +select 1 where 'foo'::mchar='FOO'::mvarchar(2); + ?column? +---------- +(0 rows) + +select 1 where 'foo'::mchar='FOO '::mvarchar(2); + ?column? +---------- +(0 rows) + +select 1 where 'foo '::mchar='FOO'::mvarchar(2); + ?column? +---------- +(0 rows) + +select 1 where 'foo '::mchar='FOO '::mvarchar(2); + ?column? +---------- +(0 rows) + +select 1 where 'foo'::mchar(2)='FOO'::mvarchar; + ?column? +---------- +(0 rows) + +select 1 where 'foo'::mchar(2)='FOO '::mvarchar; + ?column? +---------- +(0 rows) + +select 1 where 'foo '::mchar(2)='FOO'::mvarchar; + ?column? +---------- +(0 rows) + +select 1 where 'foo '::mchar(2)='FOO '::mvarchar; + ?column? +---------- +(0 rows) + +select 1 where 'foo'::mchar(2)='FOO'::mvarchar(2); + ?column? +---------- + 1 +(1 row) + +select 1 where 'foo'::mchar(2)='FOO '::mvarchar(2); + ?column? +---------- + 1 +(1 row) + +select 1 where 'foo '::mchar(2)='FOO'::mvarchar(2); + ?column? +---------- + 1 +(1 row) + +select 1 where 'foo '::mchar(2)='FOO '::mvarchar(2); + ?column? +---------- + 1 +(1 row) + +Select 'f'::mchar(1) Union Select 'o'::mvarchar(1); + mchar +------- + f + o +(2 rows) + +Select 'f'::mvarchar(1) Union Select 'o'::mchar(1); + mvarchar +---------- + f + o +(2 rows) + +select * from chvch where ch=vch; + ch | vch +--------------+------------ + No spaces | No spaces + One space | One space + 1 space | 1 space +(3 rows) + +select ch.* from ch, (select 'dEfg'::mvarchar as q) as p where chcol > p.q; + chcol +---------------------------------- + ee + Ee +(2 rows) + +create index qq on ch (chcol); +set enable_seqscan=off; +select ch.* from ch, (select 'dEfg'::mvarchar as q) as p where chcol > p.q; + chcol +---------------------------------- + ee + Ee +(2 rows) + +set enable_seqscan=on; +--\copy chvch to 'results/chvch.dump' binary +--truncate table chvch; +--\copy chvch from 'results/chvch.dump' binary +--test joins +CREATE TABLE a (mchar2 MCHAR(2) NOT NULL); +CREATE TABLE c (mvarchar255 mvarchar NOT NULL); +SELECT * FROM a, c WHERE mchar2 = mvarchar255; + mchar2 | mvarchar255 +--------+------------- +(0 rows) + +SELECT * FROM a, c WHERE mvarchar255 = mchar2; + mchar2 | mvarchar255 +--------+------------- +(0 rows) + +DROP TABLE a; +DROP TABLE c; diff --git a/contrib/mchar/expected/mvarchar.out b/contrib/mchar/expected/mvarchar.out new file mode 100644 index 0000000..5c866b4 --- /dev/null +++ b/contrib/mchar/expected/mvarchar.out @@ -0,0 +1,363 @@ +-- I/O tests +select '1'::mvarchar; + mvarchar +---------- + 1 +(1 row) + +select '2 '::mvarchar; + mvarchar +---------- + 2 +(1 row) + +select '10 '::mvarchar; + mvarchar +-------------- + 10 +(1 row) + +select '1'::mvarchar(2); + mvarchar +---------- + 1 +(1 row) + +select '2 '::mvarchar(2); + mvarchar +---------- + 2 +(1 row) + +select '3 '::mvarchar(2); + mvarchar +---------- + 3 +(1 row) + +select '10 '::mvarchar(2); + mvarchar +---------- + 10 +(1 row) + +select ' '::mvarchar(10); + mvarchar +------------ + +(1 row) + +select ' '::mvarchar; + mvarchar +-------------------- + +(1 row) + +-- operations & functions +select length('1'::mvarchar); + length +-------- + 1 +(1 row) + +select length('2 '::mvarchar); + length +-------- + 1 +(1 row) + +select length('10 '::mvarchar); + length +-------- + 2 +(1 row) + +select length('1'::mvarchar(2)); + length +-------- + 1 +(1 row) + +select length('2 '::mvarchar(2)); + length +-------- + 1 +(1 row) + +select length('3 '::mvarchar(2)); + length +-------- + 1 +(1 row) + +select length('10 '::mvarchar(2)); + length +-------- + 2 +(1 row) + +select length(' '::mvarchar(10)); + length +-------- + 0 +(1 row) + +select length(' '::mvarchar); + length +-------- + 0 +(1 row) + +select 'asd'::mvarchar(10) || '>'::mvarchar(10); + ?column? +---------- + asd> +(1 row) + +select length('asd'::mvarchar(10) || '>'::mvarchar(10)); + length +-------- + 4 +(1 row) + +select 'asd'::mvarchar(2) || '>'::mvarchar(10); + ?column? +---------- + as> +(1 row) + +select length('asd'::mvarchar(2) || '>'::mvarchar(10)); + length +-------- + 3 +(1 row) + +-- Comparisons +select 'asdf'::mvarchar = 'aSdf'::mvarchar; + ?column? +---------- + t +(1 row) + +select 'asdf'::mvarchar = 'aSdf '::mvarchar; + ?column? +---------- + t +(1 row) + +select 'asdf'::mvarchar = 'aSdf 1'::mvarchar(4); + ?column? +---------- + t +(1 row) + +select 'asdf'::mvarchar = 'aSdf 1'::mvarchar(5); + ?column? +---------- + t +(1 row) + +select 'asdf'::mvarchar = 'aSdf 1'::mvarchar(6); + ?column? +---------- + f +(1 row) + +select 'asdf'::mvarchar(3) = 'aSdf 1'::mvarchar(5); + ?column? +---------- + f +(1 row) + +select 'asdf'::mvarchar(3) = 'aSdf 1'::mvarchar(3); + ?column? +---------- + t +(1 row) + +select 'asdf'::mvarchar < 'aSdf'::mvarchar; + ?column? +---------- + f +(1 row) + +select 'asdf'::mvarchar < 'aSdf '::mvarchar; + ?column? +---------- + f +(1 row) + +select 'asdf'::mvarchar < 'aSdf 1'::mvarchar(4); + ?column? +---------- + f +(1 row) + +select 'asdf'::mvarchar < 'aSdf 1'::mvarchar(5); + ?column? +---------- + f +(1 row) + +select 'asdf'::mvarchar < 'aSdf 1'::mvarchar(6); + ?column? +---------- + t +(1 row) + +select 'asdf'::mvarchar <= 'aSdf'::mvarchar; + ?column? +---------- + t +(1 row) + +select 'asdf'::mvarchar <= 'aSdf '::mvarchar; + ?column? +---------- + t +(1 row) + +select 'asdf'::mvarchar <= 'aSdf 1'::mvarchar(4); + ?column? +---------- + t +(1 row) + +select 'asdf'::mvarchar <= 'aSdf 1'::mvarchar(5); + ?column? +---------- + t +(1 row) + +select 'asdf'::mvarchar <= 'aSdf 1'::mvarchar(6); + ?column? +---------- + t +(1 row) + +select 'asdf'::mvarchar >= 'aSdf'::mvarchar; + ?column? +---------- + t +(1 row) + +select 'asdf'::mvarchar >= 'aSdf '::mvarchar; + ?column? +---------- + t +(1 row) + +select 'asdf'::mvarchar >= 'aSdf 1'::mvarchar(4); + ?column? +---------- + t +(1 row) + +select 'asdf'::mvarchar >= 'aSdf 1'::mvarchar(5); + ?column? +---------- + t +(1 row) + +select 'asdf'::mvarchar >= 'aSdf 1'::mvarchar(6); + ?column? +---------- + f +(1 row) + +select 'asdf'::mvarchar > 'aSdf'::mvarchar; + ?column? +---------- + f +(1 row) + +select 'asdf'::mvarchar > 'aSdf '::mvarchar; + ?column? +---------- + f +(1 row) + +select 'asdf'::mvarchar > 'aSdf 1'::mvarchar(4); + ?column? +---------- + f +(1 row) + +select 'asdf'::mvarchar > 'aSdf 1'::mvarchar(5); + ?column? +---------- + f +(1 row) + +select 'asdf'::mvarchar > 'aSdf 1'::mvarchar(6); + ?column? +---------- + f +(1 row) + +select max(vch) from chvch; + max +------------ + One space +(1 row) + +select min(vch) from chvch; + min +---------- + 1 space +(1 row) + +select substr('1234567890'::mvarchar, 3) = '34567890' as "34567890"; + 34567890 +---------- + f +(1 row) + +select substr('1234567890'::mvarchar, 4, 3) = '456' as "456"; + 456 +----- + t +(1 row) + +select lower('asdfASDF'::mvarchar); + lower +---------- + asdfasdf +(1 row) + +select upper('asdfASDF'::mvarchar); + upper +---------- + ASDFASDF +(1 row) + +select 'asd'::mvarchar == 'aSd'::mvarchar; + ?column? +---------- + t +(1 row) + +select 'asd'::mvarchar == 'aCd'::mvarchar; + ?column? +---------- + f +(1 row) + +select 'asd'::mvarchar == NULL; + ?column? +---------- + f +(1 row) + +select NULL == 'aCd'::mvarchar; + ?column? +---------- + f +(1 row) + +select NULL::mvarchar == NULL; + ?column? +---------- + t +(1 row) + diff --git a/contrib/mchar/mchar.h b/contrib/mchar/mchar.h new file mode 100644 index 0000000..8823169 --- /dev/null +++ b/contrib/mchar/mchar.h @@ -0,0 +1,62 @@ +#ifndef __MCHAR_H__ +#define __MCHAR_H__ + +#include "postgres.h" +#include "mb/pg_wchar.h" +#include "utils/builtins.h" +#include "unicode/uchar.h" +#include "unicode/ustring.h" + +typedef struct { + int32 len; + int32 typmod; + UChar data[1]; +} MChar; + +#define MCHARHDRSZ offsetof(MChar, data) +#define MCHARLENGTH(m) ( VARSIZE(m)-MCHARHDRSZ ) +#define UCHARLENGTH(m) ( MCHARLENGTH(m)/sizeof(UChar) ) + +#define DatumGetMChar(m) ((MChar*)DatumGetPointer(m)) +#define MCharGetDatum(m) PointerGetDatum(m) + +#define PG_GETARG_MCHAR(n) DatumGetMChar(PG_DETOAST_DATUM(PG_GETARG_DATUM(n))) +#define PG_GETARG_MCHAR_COPY(n) DatumGetMChar(PG_DETOAST_DATUM_COPY(PG_GETARG_DATUM(n))) + +#define PG_RETURN_MCHAR(m) PG_RETURN_POINTER(m) + +typedef struct { + int32 len; + UChar data[1]; +} MVarChar; + +#define MVARCHARHDRSZ offsetof(MVarChar, data) +#define MVARCHARLENGTH(m) ( VARSIZE(m)-MVARCHARHDRSZ ) +#define UVARCHARLENGTH(m) ( MVARCHARLENGTH(m)/sizeof(UChar) ) + +#define DatumGetMVarChar(m) ((MVarChar*)DatumGetPointer(m)) +#define MVarCharGetDatum(m) PointerGetDatum(m) + +#define PG_GETARG_MVARCHAR(n) DatumGetMVarChar(PG_DETOAST_DATUM(PG_GETARG_DATUM(n))) +#define PG_GETARG_MVARCHAR_COPY(n) DatumGetMVarChar(PG_DETOAST_DATUM_COPY(PG_GETARG_DATUM(n))) + +#define PG_RETURN_MVARCHAR(m) PG_RETURN_POINTER(m) + + +int Char2UChar(const char * src, int srclen, UChar *dst); +int UChar2Char(const UChar * src, int srclen, char *dst); +int UChar2Wchar(UChar * src, int srclen, pg_wchar *dst); +int UCharCompare(UChar * a, int alen, UChar *b, int blen); +int UCharCaseCompare(UChar * a, int alen, UChar *b, int blen); + +void FillWhiteSpace( UChar *dst, int n ); + +int lengthWithoutSpaceVarChar(MVarChar *m); +int lengthWithoutSpaceChar(MChar *m); + +extern Datum mchar_hash(PG_FUNCTION_ARGS); +extern Datum mvarchar_hash(PG_FUNCTION_ARGS); + +int m_isspace(UChar c); /* is == ' ' */ + +#endif diff --git a/contrib/mchar/mchar.sql.in b/contrib/mchar/mchar.sql.in new file mode 100644 index 0000000..b5ef4e6 --- /dev/null +++ b/contrib/mchar/mchar.sql.in @@ -0,0 +1,1328 @@ +SET search_path = public; + +BEGIN; + +-- I/O functions + +CREATE FUNCTION mchartypmod_in(cstring[]) +RETURNS int4 +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mchartypmod_out(int4) +RETURNS cstring +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mchar_in(cstring) +RETURNS mchar +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mchar_out(mchar) +RETURNS cstring +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mchar_send(mchar) +RETURNS bytea +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mchar_recv(internal) +RETURNS mchar +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE TYPE mchar ( + INTERNALLENGTH = -1, + INPUT = mchar_in, + OUTPUT = mchar_out, + TYPMOD_IN = mchartypmod_in, + TYPMOD_OUT = mchartypmod_out, + RECEIVE = mchar_recv, + SEND = mchar_send, + STORAGE = extended +); + +CREATE FUNCTION mchar(mchar, integer, boolean) +RETURNS mchar +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE CAST (mchar as mchar) +WITH FUNCTION mchar(mchar, integer, boolean) as IMPLICIT; + +CREATE FUNCTION mvarchar_in(cstring) +RETURNS mvarchar +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mvarchar_out(mvarchar) +RETURNS cstring +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mvarchar_send(mvarchar) +RETURNS bytea +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mvarchar_recv(internal) +RETURNS mvarchar +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE TYPE mvarchar ( + INTERNALLENGTH = -1, + INPUT = mvarchar_in, + OUTPUT = mvarchar_out, + TYPMOD_IN = mchartypmod_in, + TYPMOD_OUT = mchartypmod_out, + RECEIVE = mvarchar_recv, + SEND = mvarchar_send, + STORAGE = extended +); + +CREATE FUNCTION mvarchar(mvarchar, integer, boolean) +RETURNS mvarchar +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE CAST (mvarchar as mvarchar) +WITH FUNCTION mvarchar(mvarchar, integer, boolean) as IMPLICIT; + +--Operations and functions + +CREATE FUNCTION length(mchar) +RETURNS int4 +AS 'MODULE_PATHNAME', 'mchar_length' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION upper(mchar) +RETURNS mchar +AS 'MODULE_PATHNAME', 'mchar_upper' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION lower(mchar) +RETURNS mchar +AS 'MODULE_PATHNAME', 'mchar_lower' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mchar_hash(mchar) +RETURNS int4 +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mchar_concat(mchar, mchar) +RETURNS mchar +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE OPERATOR || ( + LEFTARG = mchar, + RIGHTARG = mchar, + PROCEDURE = mchar_concat +); + +CREATE FUNCTION mchar_like(mchar, mvarchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mchar_notlike(mchar, mvarchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE OPERATOR ~~ ( + LEFTARG = mchar, + RIGHTARG = mvarchar, + PROCEDURE = mchar_like, + RESTRICT = likesel, + JOIN = likejoinsel, + NEGATOR = '!~~' +); + +CREATE OPERATOR !~~ ( + LEFTARG = mchar, + RIGHTARG = mvarchar, + PROCEDURE = mchar_notlike, + RESTRICT = nlikesel, + JOIN = nlikejoinsel, + NEGATOR = '~~' +); + +CREATE FUNCTION mchar_regexeq(mchar, mchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mchar_regexne(mchar, mchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE OPERATOR ~ ( + LEFTARG = mchar, + RIGHTARG = mchar, + PROCEDURE = mchar_regexeq, + RESTRICT = regexeqsel, + JOIN = regexeqjoinsel, + NEGATOR = '!~' +); + +CREATE OPERATOR !~ ( + LEFTARG = mchar, + RIGHTARG = mchar, + PROCEDURE = mchar_regexne, + RESTRICT = regexnesel, + JOIN = regexnejoinsel, + NEGATOR = '~' +); + +CREATE FUNCTION similar_escape(mchar, mchar) +RETURNS mchar +AS 'MODULE_PATHNAME', 'mchar_similar_escape' +LANGUAGE C IMMUTABLE; + +CREATE FUNCTION length(mvarchar) +RETURNS int4 +AS 'MODULE_PATHNAME', 'mvarchar_length' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION upper(mvarchar) +RETURNS mvarchar +AS 'MODULE_PATHNAME', 'mvarchar_upper' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION lower(mvarchar) +RETURNS mvarchar +AS 'MODULE_PATHNAME', 'mvarchar_lower' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mvarchar_hash(mvarchar) +RETURNS int4 +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mvarchar_concat(mvarchar, mvarchar) +RETURNS mvarchar +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE OPERATOR || ( + LEFTARG = mvarchar, + RIGHTARG = mvarchar, + PROCEDURE = mvarchar_concat +); + +CREATE FUNCTION mvarchar_like(mvarchar, mvarchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION like_escape(mvarchar, mvarchar) +RETURNS mvarchar +AS 'MODULE_PATHNAME', 'mvarchar_like_escape' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mvarchar_notlike(mvarchar, mvarchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE OPERATOR ~~ ( + LEFTARG = mvarchar, + RIGHTARG = mvarchar, + PROCEDURE = mvarchar_like, + RESTRICT = likesel, + JOIN = likejoinsel, + NEGATOR = '!~~' +); + +CREATE OPERATOR !~~ ( + LEFTARG = mvarchar, + RIGHTARG = mvarchar, + PROCEDURE = mvarchar_notlike, + RESTRICT = nlikesel, + JOIN = nlikejoinsel, + NEGATOR = '~~' +); + +CREATE FUNCTION mvarchar_regexeq(mvarchar, mvarchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mvarchar_regexne(mvarchar, mvarchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE OPERATOR ~ ( + LEFTARG = mvarchar, + RIGHTARG = mvarchar, + PROCEDURE = mvarchar_regexeq, + RESTRICT = regexeqsel, + JOIN = regexeqjoinsel, + NEGATOR = '!~' +); + +CREATE OPERATOR !~ ( + LEFTARG = mvarchar, + RIGHTARG = mvarchar, + PROCEDURE = mvarchar_regexne, + RESTRICT = regexnesel, + JOIN = regexnejoinsel, + NEGATOR = '~' +); + +CREATE FUNCTION similar_escape(mvarchar, mvarchar) +RETURNS mvarchar +AS 'MODULE_PATHNAME', 'mvarchar_similar_escape' +LANGUAGE C IMMUTABLE; + +CREATE FUNCTION substr (mchar, int4) +RETURNS mchar +AS 'MODULE_PATHNAME', 'mchar_substring_no_len' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION substr (mchar, int4, int4) +RETURNS mchar +AS 'MODULE_PATHNAME', 'mchar_substring' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION substr (mvarchar, int4) +RETURNS mvarchar +AS 'MODULE_PATHNAME', 'mvarchar_substring_no_len' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION substr (mvarchar, int4, int4) +RETURNS mvarchar +AS 'MODULE_PATHNAME', 'mvarchar_substring' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +-- Comparing +-- MCHAR + +CREATE FUNCTION mchar_icase_cmp(mchar, mchar) +RETURNS int4 +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mchar_icase_eq(mchar, mchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mchar_icase_ne(mchar, mchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mchar_icase_lt(mchar, mchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mchar_icase_le(mchar, mchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mchar_icase_gt(mchar, mchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mchar_icase_ge(mchar, mchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + + +CREATE OPERATOR < ( + LEFTARG = mchar, + RIGHTARG = mchar, + PROCEDURE = mchar_icase_lt, + COMMUTATOR = '>', + NEGATOR = '>=', + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel +); + +CREATE OPERATOR > ( + LEFTARG = mchar, + RIGHTARG = mchar, + PROCEDURE = mchar_icase_gt, + COMMUTATOR = '<', + NEGATOR = '<=', + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel +); + +CREATE OPERATOR <= ( + LEFTARG = mchar, + RIGHTARG = mchar, + PROCEDURE = mchar_icase_le, + COMMUTATOR = '>=', + NEGATOR = '>', + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel +); + +CREATE OPERATOR >= ( + LEFTARG = mchar, + RIGHTARG = mchar, + PROCEDURE = mchar_icase_ge, + COMMUTATOR = '<=', + NEGATOR = '<', + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel +); + +CREATE OPERATOR = ( + LEFTARG = mchar, + RIGHTARG = mchar, + PROCEDURE = mchar_icase_eq, + COMMUTATOR = '=', + NEGATOR = '<>', + RESTRICT = eqsel, + JOIN = eqjoinsel, + SORT1 = '<', + SORT2 = '<', + HASHES +); + +CREATE OPERATOR <> ( + LEFTARG = mchar, + RIGHTARG = mchar, + PROCEDURE = mchar_icase_ne, + COMMUTATOR = '<>', + NEGATOR = '=', + RESTRICT = neqsel, + JOIN = neqjoinsel +); + +CREATE FUNCTION mchar_case_cmp(mchar, mchar) +RETURNS int4 +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mchar_case_eq(mchar, mchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mchar_case_ne(mchar, mchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mchar_case_lt(mchar, mchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mchar_case_le(mchar, mchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mchar_case_gt(mchar, mchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mchar_case_ge(mchar, mchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + + +CREATE OPERATOR &< ( + LEFTARG = mchar, + RIGHTARG = mchar, + PROCEDURE = mchar_case_lt, + COMMUTATOR = '&>', + NEGATOR = '&>=', + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel +); + +CREATE OPERATOR &> ( + LEFTARG = mchar, + RIGHTARG = mchar, + PROCEDURE = mchar_case_gt, + COMMUTATOR = '&<', + NEGATOR = '&<=', + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel +); + +CREATE OPERATOR &<= ( + LEFTARG = mchar, + RIGHTARG = mchar, + PROCEDURE = mchar_case_le, + COMMUTATOR = '&>=', + NEGATOR = '&>', + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel +); + +CREATE OPERATOR &>= ( + LEFTARG = mchar, + RIGHTARG = mchar, + PROCEDURE = mchar_case_ge, + COMMUTATOR = '&<=', + NEGATOR = '&<', + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel +); + +CREATE OPERATOR &= ( + LEFTARG = mchar, + RIGHTARG = mchar, + PROCEDURE = mchar_case_eq, + COMMUTATOR = '&=', + NEGATOR = '&<>', + RESTRICT = eqsel, + JOIN = eqjoinsel, + SORT1 = '&<', + SORT2 = '&<' +); + +CREATE OPERATOR &<> ( + LEFTARG = mchar, + RIGHTARG = mchar, + PROCEDURE = mchar_case_ne, + COMMUTATOR = '&<>', + NEGATOR = '&=', + RESTRICT = neqsel, + JOIN = neqjoinsel +); + +--MVARCHAR + +CREATE FUNCTION mvarchar_icase_cmp(mvarchar, mvarchar) +RETURNS int4 +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mvarchar_icase_eq(mvarchar, mvarchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mvarchar_icase_ne(mvarchar, mvarchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mvarchar_icase_lt(mvarchar, mvarchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mvarchar_icase_le(mvarchar, mvarchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mvarchar_icase_gt(mvarchar, mvarchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mvarchar_icase_ge(mvarchar, mvarchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + + +CREATE OPERATOR < ( + LEFTARG = mvarchar, + RIGHTARG = mvarchar, + PROCEDURE = mvarchar_icase_lt, + COMMUTATOR = '>', + NEGATOR = '>=', + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel +); + +CREATE OPERATOR > ( + LEFTARG = mvarchar, + RIGHTARG = mvarchar, + PROCEDURE = mvarchar_icase_gt, + COMMUTATOR = '<', + NEGATOR = '<=', + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel +); + +CREATE OPERATOR <= ( + LEFTARG = mvarchar, + RIGHTARG = mvarchar, + PROCEDURE = mvarchar_icase_le, + COMMUTATOR = '>=', + NEGATOR = '>', + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel +); + +CREATE OPERATOR >= ( + LEFTARG = mvarchar, + RIGHTARG = mvarchar, + PROCEDURE = mvarchar_icase_ge, + COMMUTATOR = '<=', + NEGATOR = '<', + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel +); + +CREATE OPERATOR = ( + LEFTARG = mvarchar, + RIGHTARG = mvarchar, + PROCEDURE = mvarchar_icase_eq, + COMMUTATOR = '=', + NEGATOR = '<>', + RESTRICT = eqsel, + JOIN = eqjoinsel, + SORT1 = '<', + SORT2 = '<', + HASHES +); + +CREATE OPERATOR <> ( + LEFTARG = mvarchar, + RIGHTARG = mvarchar, + PROCEDURE = mvarchar_icase_ne, + COMMUTATOR = '<>', + NEGATOR = '=', + RESTRICT = neqsel, + JOIN = neqjoinsel +); + +CREATE FUNCTION mvarchar_case_cmp(mvarchar, mvarchar) +RETURNS int4 +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mvarchar_case_eq(mvarchar, mvarchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mvarchar_case_ne(mvarchar, mvarchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mvarchar_case_lt(mvarchar, mvarchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mvarchar_case_le(mvarchar, mvarchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mvarchar_case_gt(mvarchar, mvarchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mvarchar_case_ge(mvarchar, mvarchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + + +CREATE OPERATOR &< ( + LEFTARG = mvarchar, + RIGHTARG = mvarchar, + PROCEDURE = mvarchar_case_lt, + COMMUTATOR = '&>', + NEGATOR = '&>=', + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel +); + +CREATE OPERATOR &> ( + LEFTARG = mvarchar, + RIGHTARG = mvarchar, + PROCEDURE = mvarchar_case_gt, + COMMUTATOR = '&<', + NEGATOR = '&<=', + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel +); + +CREATE OPERATOR &<= ( + LEFTARG = mvarchar, + RIGHTARG = mvarchar, + PROCEDURE = mvarchar_case_le, + COMMUTATOR = '&>=', + NEGATOR = '&>', + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel +); + +CREATE OPERATOR &>= ( + LEFTARG = mvarchar, + RIGHTARG = mvarchar, + PROCEDURE = mvarchar_case_ge, + COMMUTATOR = '&<=', + NEGATOR = '&<', + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel +); + +CREATE OPERATOR &= ( + LEFTARG = mvarchar, + RIGHTARG = mvarchar, + PROCEDURE = mvarchar_case_eq, + COMMUTATOR = '&=', + NEGATOR = '&<>', + RESTRICT = eqsel, + JOIN = eqjoinsel, + SORT1 = '&<', + SORT2 = '&<' +); + +CREATE OPERATOR &<> ( + LEFTARG = mvarchar, + RIGHTARG = mvarchar, + PROCEDURE = mvarchar_case_ne, + COMMUTATOR = '&<>', + NEGATOR = '&=', + RESTRICT = neqsel, + JOIN = neqjoinsel +); + +-- MCHAR <> MVARCHAR + +CREATE FUNCTION mc_mv_icase_cmp(mchar, mvarchar) +RETURNS int4 +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mc_mv_icase_eq(mchar, mvarchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mc_mv_icase_ne(mchar, mvarchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mc_mv_icase_lt(mchar, mvarchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mc_mv_icase_le(mchar, mvarchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mc_mv_icase_gt(mchar, mvarchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mc_mv_icase_ge(mchar, mvarchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + + +CREATE OPERATOR < ( + LEFTARG = mchar, + RIGHTARG = mvarchar, + PROCEDURE = mc_mv_icase_lt, + COMMUTATOR = '>', + NEGATOR = '>=', + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel +); + +CREATE OPERATOR > ( + LEFTARG = mchar, + RIGHTARG = mvarchar, + PROCEDURE = mc_mv_icase_gt, + COMMUTATOR = '<', + NEGATOR = '<=', + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel +); + +CREATE OPERATOR <= ( + LEFTARG = mchar, + RIGHTARG = mvarchar, + PROCEDURE = mc_mv_icase_le, + COMMUTATOR = '>=', + NEGATOR = '>', + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel +); + +CREATE OPERATOR >= ( + LEFTARG = mchar, + RIGHTARG = mvarchar, + PROCEDURE = mc_mv_icase_ge, + COMMUTATOR = '<=', + NEGATOR = '<', + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel +); + +CREATE OPERATOR = ( + LEFTARG = mchar, + RIGHTARG = mvarchar, + PROCEDURE = mc_mv_icase_eq, + COMMUTATOR = '=', + NEGATOR = '<>', + RESTRICT = eqsel, + JOIN = eqjoinsel, + SORT1 = '<', + SORT2 = '<' +); + +CREATE OPERATOR <> ( + LEFTARG = mchar, + RIGHTARG = mvarchar, + PROCEDURE = mc_mv_icase_ne, + COMMUTATOR = '<>', + NEGATOR = '=', + RESTRICT = neqsel, + JOIN = neqjoinsel +); + +CREATE FUNCTION mc_mv_case_cmp(mchar, mvarchar) +RETURNS int4 +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mc_mv_case_eq(mchar, mvarchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mc_mv_case_ne(mchar, mvarchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mc_mv_case_lt(mchar, mvarchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mc_mv_case_le(mchar, mvarchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mc_mv_case_gt(mchar, mvarchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mc_mv_case_ge(mchar, mvarchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + + +CREATE OPERATOR &< ( + LEFTARG = mchar, + RIGHTARG = mvarchar, + PROCEDURE = mc_mv_case_lt, + COMMUTATOR = '&>', + NEGATOR = '&>=', + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel +); + +CREATE OPERATOR &> ( + LEFTARG = mchar, + RIGHTARG = mvarchar, + PROCEDURE = mc_mv_case_gt, + COMMUTATOR = '&<', + NEGATOR = '&<=', + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel +); + +CREATE OPERATOR &<= ( + LEFTARG = mchar, + RIGHTARG = mvarchar, + PROCEDURE = mc_mv_case_le, + COMMUTATOR = '&>=', + NEGATOR = '&>', + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel +); + +CREATE OPERATOR &>= ( + LEFTARG = mchar, + RIGHTARG = mvarchar, + PROCEDURE = mc_mv_case_ge, + COMMUTATOR = '&<=', + NEGATOR = '&<', + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel +); + +CREATE OPERATOR &= ( + LEFTARG = mchar, + RIGHTARG = mvarchar, + PROCEDURE = mc_mv_case_eq, + COMMUTATOR = '&=', + NEGATOR = '&<>', + RESTRICT = eqsel, + JOIN = eqjoinsel, + SORT1 = '&<', + SORT2 = '&<' +); + +CREATE OPERATOR &<> ( + LEFTARG = mchar, + RIGHTARG = mvarchar, + PROCEDURE = mc_mv_case_ne, + COMMUTATOR = '&<>', + NEGATOR = '&=', + RESTRICT = neqsel, + JOIN = neqjoinsel +); + +-- MVARCHAR <> MCHAR + +CREATE FUNCTION mv_mc_icase_cmp(mvarchar, mchar) +RETURNS int4 +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mv_mc_icase_eq(mvarchar, mchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mv_mc_icase_ne(mvarchar, mchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mv_mc_icase_lt(mvarchar, mchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mv_mc_icase_le(mvarchar, mchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mv_mc_icase_gt(mvarchar, mchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mv_mc_icase_ge(mvarchar, mchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + + +CREATE OPERATOR < ( + LEFTARG = mvarchar, + RIGHTARG = mchar, + PROCEDURE = mv_mc_icase_lt, + COMMUTATOR = '>', + NEGATOR = '>=', + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel +); + +CREATE OPERATOR > ( + LEFTARG = mvarchar, + RIGHTARG = mchar, + PROCEDURE = mv_mc_icase_gt, + COMMUTATOR = '<', + NEGATOR = '<=', + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel +); + +CREATE OPERATOR <= ( + LEFTARG = mvarchar, + RIGHTARG = mchar, + PROCEDURE = mv_mc_icase_le, + COMMUTATOR = '>=', + NEGATOR = '>', + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel +); + +CREATE OPERATOR >= ( + LEFTARG = mvarchar, + RIGHTARG = mchar, + PROCEDURE = mv_mc_icase_ge, + COMMUTATOR = '<=', + NEGATOR = '<', + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel +); + +CREATE OPERATOR = ( + LEFTARG = mvarchar, + RIGHTARG = mchar, + PROCEDURE = mv_mc_icase_eq, + COMMUTATOR = '=', + NEGATOR = '<>', + RESTRICT = eqsel, + JOIN = eqjoinsel, + SORT1 = '<', + SORT2 = '<' +); + +CREATE OPERATOR <> ( + LEFTARG = mvarchar, + RIGHTARG = mchar, + PROCEDURE = mv_mc_icase_ne, + COMMUTATOR = '<>', + NEGATOR = '=', + RESTRICT = neqsel, + JOIN = neqjoinsel +); + +CREATE FUNCTION mv_mc_case_cmp(mvarchar, mchar) +RETURNS int4 +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mv_mc_case_eq(mvarchar, mchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mv_mc_case_ne(mvarchar, mchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mv_mc_case_lt(mvarchar, mchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mv_mc_case_le(mvarchar, mchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mv_mc_case_gt(mvarchar, mchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mv_mc_case_ge(mvarchar, mchar) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + + +CREATE OPERATOR &< ( + LEFTARG = mvarchar, + RIGHTARG = mchar, + PROCEDURE = mv_mc_case_lt, + COMMUTATOR = '&>', + NEGATOR = '&>=', + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel +); + +CREATE OPERATOR &> ( + LEFTARG = mvarchar, + RIGHTARG = mchar, + PROCEDURE = mv_mc_case_gt, + COMMUTATOR = '&<', + NEGATOR = '&<=', + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel +); + +CREATE OPERATOR &<= ( + LEFTARG = mvarchar, + RIGHTARG = mchar, + PROCEDURE = mv_mc_case_le, + COMMUTATOR = '&>=', + NEGATOR = '&>', + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel +); + +CREATE OPERATOR &>= ( + LEFTARG = mvarchar, + RIGHTARG = mchar, + PROCEDURE = mv_mc_case_ge, + COMMUTATOR = '&<=', + NEGATOR = '&<', + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel +); + +CREATE OPERATOR &= ( + LEFTARG = mvarchar, + RIGHTARG = mchar, + PROCEDURE = mv_mc_case_eq, + COMMUTATOR = '&=', + NEGATOR = '&<>', + RESTRICT = eqsel, + JOIN = eqjoinsel, + SORT1 = '&<', + SORT2 = '&<' +); + +CREATE OPERATOR &<> ( + LEFTARG = mvarchar, + RIGHTARG = mchar, + PROCEDURE = mv_mc_case_ne, + COMMUTATOR = '&<>', + NEGATOR = '&=', + RESTRICT = neqsel, + JOIN = neqjoinsel +); + +-- MCHAR - VARCHAR operations + +CREATE FUNCTION mchar_mvarchar_concat(mchar, mvarchar) +RETURNS mvarchar +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE OPERATOR || ( + LEFTARG = mchar, + RIGHTARG = mvarchar, + PROCEDURE = mchar_mvarchar_concat +); + +CREATE FUNCTION mvarchar_mchar_concat(mvarchar, mchar) +RETURNS mvarchar +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE OPERATOR || ( + LEFTARG = mvarchar, + RIGHTARG = mchar, + PROCEDURE = mvarchar_mchar_concat +); + +CREATE FUNCTION mvarchar_mchar(mvarchar, integer, boolean) +RETURNS mchar +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE CAST (mvarchar as mchar) +WITH FUNCTION mvarchar_mchar(mvarchar, integer, boolean) as IMPLICIT; + +CREATE FUNCTION mchar_mvarchar(mchar, integer, boolean) +RETURNS mvarchar +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE CAST (mchar as mvarchar) +WITH FUNCTION mchar_mvarchar(mchar, integer, boolean) as IMPLICIT; + +-- Aggregates + +CREATE FUNCTION mchar_larger(mchar, mchar) +RETURNS mchar +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE AGGREGATE max ( + BASETYPE = mchar, + SFUNC = mchar_larger, + STYPE = mchar, + SORTOP = '>' +); + +CREATE FUNCTION mchar_smaller(mchar, mchar) +RETURNS mchar +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE AGGREGATE min ( + BASETYPE = mchar, + SFUNC = mchar_smaller, + STYPE = mchar, + SORTOP = '<' +); + +CREATE FUNCTION mvarchar_larger(mvarchar, mvarchar) +RETURNS mvarchar +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE AGGREGATE max ( + BASETYPE = mvarchar, + SFUNC = mvarchar_larger, + STYPE = mvarchar, + SORTOP = '>' +); + +CREATE FUNCTION mvarchar_smaller(mvarchar, mvarchar) +RETURNS mvarchar +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE AGGREGATE min ( + BASETYPE = mvarchar, + SFUNC = mvarchar_smaller, + STYPE = mvarchar, + SORTOP = '<' +); + +-- B-tree support +CREATE OPERATOR FAMILY icase_ops USING btree; +CREATE OPERATOR FAMILY case_ops USING btree; + +CREATE OPERATOR CLASS mchar_icase_ops +DEFAULT FOR TYPE mchar USING btree FAMILY icase_ops AS + OPERATOR 1 < , + OPERATOR 2 <= , + OPERATOR 3 = , + OPERATOR 4 >= , + OPERATOR 5 > , + FUNCTION 1 mchar_icase_cmp(mchar, mchar), + OPERATOR 1 < (mchar, mvarchar), + OPERATOR 2 <= (mchar, mvarchar), + OPERATOR 3 = (mchar, mvarchar), + OPERATOR 4 >= (mchar, mvarchar), + OPERATOR 5 > (mchar, mvarchar), + FUNCTION 1 mc_mv_icase_cmp(mchar, mvarchar); + +CREATE OPERATOR CLASS mchar_case_ops +FOR TYPE mchar USING btree FAMILY case_ops AS + OPERATOR 1 &< , + OPERATOR 2 &<= , + OPERATOR 3 &= , + OPERATOR 4 &>= , + OPERATOR 5 &> , + FUNCTION 1 mchar_case_cmp(mchar, mchar), + OPERATOR 1 &< (mchar, mvarchar), + OPERATOR 2 &<= (mchar, mvarchar), + OPERATOR 3 &= (mchar, mvarchar), + OPERATOR 4 &>= (mchar, mvarchar), + OPERATOR 5 &> (mchar, mvarchar), + FUNCTION 1 mc_mv_case_cmp(mchar, mvarchar); + +CREATE OPERATOR CLASS mchar_icase_ops +DEFAULT FOR TYPE mchar USING hash AS + OPERATOR 1 = , + FUNCTION 1 mchar_hash(mchar); + +CREATE OPERATOR CLASS mvarchar_icase_ops +DEFAULT FOR TYPE mvarchar USING btree FAMILY icase_ops AS + OPERATOR 1 < , + OPERATOR 2 <= , + OPERATOR 3 = , + OPERATOR 4 >= , + OPERATOR 5 > , + FUNCTION 1 mvarchar_icase_cmp(mvarchar, mvarchar), + OPERATOR 1 < (mvarchar, mchar), + OPERATOR 2 <= (mvarchar, mchar), + OPERATOR 3 = (mvarchar, mchar), + OPERATOR 4 >= (mvarchar, mchar), + OPERATOR 5 > (mvarchar, mchar), + FUNCTION 1 mv_mc_icase_cmp(mvarchar, mchar); + +CREATE OPERATOR CLASS mvarchar_case_ops +FOR TYPE mvarchar USING btree FAMILY case_ops AS + OPERATOR 1 &< , + OPERATOR 2 &<= , + OPERATOR 3 &= , + OPERATOR 4 &>= , + OPERATOR 5 &> , + FUNCTION 1 mvarchar_case_cmp(mvarchar, mvarchar), + OPERATOR 1 &< (mvarchar, mchar), + OPERATOR 2 &<= (mvarchar, mchar), + OPERATOR 3 &= (mvarchar, mchar), + OPERATOR 4 &>= (mvarchar, mchar), + OPERATOR 5 &> (mvarchar, mchar), + FUNCTION 1 mv_mc_case_cmp(mvarchar, mchar); + +CREATE OPERATOR CLASS mvarchar_icase_ops +DEFAULT FOR TYPE mvarchar USING hash AS + OPERATOR 1 = , + FUNCTION 1 mvarchar_hash(mvarchar); + + +-- Index support for LIKE + +CREATE FUNCTION mchar_pattern_fixed_prefix(internal, internal, internal) +RETURNS int4 +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE FUNCTION mchar_greaterstring(internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE OR REPLACE FUNCTION isfulleq_mchar(mchar, mchar) +RETURNS bool AS 'MODULE_PATHNAME' +LANGUAGE C CALLED ON NULL INPUT IMMUTABLE; + +CREATE OR REPLACE FUNCTION fullhash_mchar(mchar) +RETURNS int4 AS 'MODULE_PATHNAME' +LANGUAGE C CALLED ON NULL INPUT IMMUTABLE; + + +CREATE OPERATOR == ( + LEFTARG = mchar, + RIGHTARG = mchar, + PROCEDURE = isfulleq_mchar, + COMMUTATOR = '==', + RESTRICT = eqsel, + JOIN = eqjoinsel, + HASHES +); + +CREATE OPERATOR CLASS mchar_fill_ops + FOR TYPE mchar USING hash AS + OPERATOR 1 ==, + FUNCTION 1 fullhash_mchar(mchar); + +CREATE OR REPLACE FUNCTION isfulleq_mvarchar(mvarchar, mvarchar) +RETURNS bool AS 'MODULE_PATHNAME' +LANGUAGE C CALLED ON NULL INPUT IMMUTABLE; + +CREATE OR REPLACE FUNCTION fullhash_mvarchar(mvarchar) +RETURNS int4 AS 'MODULE_PATHNAME' +LANGUAGE C CALLED ON NULL INPUT IMMUTABLE; + + +CREATE OPERATOR == ( + LEFTARG = mvarchar, + RIGHTARG = mvarchar, + PROCEDURE = isfulleq_mvarchar, + COMMUTATOR = '==', + RESTRICT = eqsel, + JOIN = eqjoinsel, + HASHES +); + +CREATE OPERATOR CLASS mvarchar_fill_ops + FOR TYPE mvarchar USING hash AS + OPERATOR 1 ==, + FUNCTION 1 fullhash_mvarchar(mvarchar); + +COMMIT; +SET search_path = public; + diff --git a/contrib/mchar/mchar_io.c b/contrib/mchar/mchar_io.c new file mode 100644 index 0000000..9179412 --- /dev/null +++ b/contrib/mchar/mchar_io.c @@ -0,0 +1,372 @@ +#include "mchar.h" +#include "mb/pg_wchar.h" +#include "fmgr.h" +#include "libpq/pqformat.h" +#include + +#ifdef PG_MODULE_MAGIC +PG_MODULE_MAGIC; +#endif + +PG_FUNCTION_INFO_V1(mchar_in); +Datum mchar_in(PG_FUNCTION_ARGS); +PG_FUNCTION_INFO_V1(mchar_out); +Datum mchar_out(PG_FUNCTION_ARGS); +PG_FUNCTION_INFO_V1(mchar); +Datum mchar(PG_FUNCTION_ARGS); + +PG_FUNCTION_INFO_V1(mvarchar_in); +Datum mvarchar_in(PG_FUNCTION_ARGS); +PG_FUNCTION_INFO_V1(mvarchar_out); +Datum mvarchar_out(PG_FUNCTION_ARGS); +PG_FUNCTION_INFO_V1(mvarchar); +Datum mvarchar(PG_FUNCTION_ARGS); + +PG_FUNCTION_INFO_V1(mchartypmod_in); +Datum mchartypmod_in(PG_FUNCTION_ARGS); +Datum +mchartypmod_in(PG_FUNCTION_ARGS) { + ArrayType *ta = PG_GETARG_ARRAYTYPE_P(0); + int32 *tl; + int n; + + tl = ArrayGetIntegerTypmods(ta, &n); + + if (n != 1) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid type modifier"))); + if (*tl < 1) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("length for type mchar/mvarchar must be at least 1"))); + + return *tl; +} + +PG_FUNCTION_INFO_V1(mchartypmod_out); +Datum mchartypmod_out(PG_FUNCTION_ARGS); +Datum +mchartypmod_out(PG_FUNCTION_ARGS) { + int32 typmod = PG_GETARG_INT32(0); + char *res = (char *) palloc(64); + + if (typmod >0) + snprintf(res, 64, "(%d)", (int) (typmod)); + else + *res = '\0'; + + PG_RETURN_CSTRING( res ); +} + +static void +mchar_strip( MChar * m, int atttypmod ) { + int maxlen; + + if ( atttypmod<=0 ) { + atttypmod =-1; + } else { + int charlen = u_countChar32( m->data, UCHARLENGTH(m) ); + + if ( charlen > atttypmod ) { + int i=0; + U16_FWD_N( m->data, i, UCHARLENGTH(m), atttypmod); + SET_VARSIZE( m, sizeof(UChar) * i + MCHARHDRSZ ); + } + } + + m->typmod = atttypmod; + + maxlen = UCHARLENGTH(m); + while( maxlen>0 && m_isspace( m->data[ maxlen-1 ] ) ) + maxlen--; + + SET_VARSIZE(m, sizeof(UChar) * maxlen + MCHARHDRSZ); +} + + +Datum +mchar_in(PG_FUNCTION_ARGS) { + char *s = PG_GETARG_CSTRING(0); +#ifdef NOT_USED + Oid typelem = PG_GETARG_OID(1); +#endif + int32 atttypmod = PG_GETARG_INT32(2); + MChar *result; + int32 slen = strlen(s), rlen; + + pg_verifymbstr(s, slen, false); + + result = (MChar*)palloc( MCHARHDRSZ + slen * sizeof(UChar) * 4 /* upper limit of length */ ); + rlen = Char2UChar( s, slen, result->data ); + SET_VARSIZE(result, sizeof(UChar) * rlen + MCHARHDRSZ); + + mchar_strip(result, atttypmod); + + PG_RETURN_MCHAR(result); +} + +Datum +mchar_out(PG_FUNCTION_ARGS) { + MChar *in = PG_GETARG_MCHAR(0); + char *out; + size_t size, inlen = UCHARLENGTH(in); + size_t charlen = u_countChar32(in->data, inlen); + + Assert( in->typmod < 0 || charlen<=in->typmod ); + size = ( in->typmod < 0 ) ? inlen : in->typmod; + size *= pg_database_encoding_max_length(); + + out = (char*)palloc( size+1 ); + size = UChar2Char( in->data, inlen, out ); + + if ( in->typmod>0 && charlen < in->typmod ) { + memset( out+size, ' ', in->typmod - charlen); + size += in->typmod - charlen; + } + + out[size] = '\0'; + + PG_FREE_IF_COPY(in,0); + + PG_RETURN_CSTRING(out); +} + +Datum +mchar(PG_FUNCTION_ARGS) { + MChar *source = PG_GETARG_MCHAR(0); + MChar *result; + int32 typmod = PG_GETARG_INT32(1); +#ifdef NOT_USED + bool isExplicit = PG_GETARG_BOOL(2); +#endif + + result = palloc( VARSIZE(source) ); + memcpy( result, source, VARSIZE(source) ); + PG_FREE_IF_COPY(source,0); + + mchar_strip(result, typmod); + + PG_RETURN_MCHAR(result); +} + +Datum +mvarchar_in(PG_FUNCTION_ARGS) { + char *s = PG_GETARG_CSTRING(0); +#ifdef NOT_USED + Oid typelem = PG_GETARG_OID(1); +#endif + int32 atttypmod = PG_GETARG_INT32(2); + MVarChar *result; + int32 slen = strlen(s), rlen; + + pg_verifymbstr(s, slen, false); + + result = (MVarChar*)palloc( MVARCHARHDRSZ + slen * sizeof(UChar) * 2 /* upper limit of length */ ); + rlen = Char2UChar( s, slen, result->data ); + SET_VARSIZE(result, sizeof(UChar) * rlen + MVARCHARHDRSZ); + + if ( atttypmod > 0 && atttypmod < u_countChar32(result->data, UVARCHARLENGTH(result)) ) + elog(ERROR,"value too long for type mvarchar(%d)", atttypmod); + + PG_RETURN_MVARCHAR(result); +} + +Datum +mvarchar_out(PG_FUNCTION_ARGS) { + MVarChar *in = PG_GETARG_MVARCHAR(0); + char *out; + size_t size = UVARCHARLENGTH(in); + + size *= pg_database_encoding_max_length(); + + out = (char*)palloc( size+1 ); + size = UChar2Char( in->data, UVARCHARLENGTH(in), out ); + + out[size] = '\0'; + + PG_FREE_IF_COPY(in,0); + + PG_RETURN_CSTRING(out); +} + +static void +mvarchar_strip(MVarChar *m, int atttypmod) { + int charlen = u_countChar32(m->data, UVARCHARLENGTH(m)); + + if ( atttypmod>=0 && atttypmod < charlen ) { + int i=0; + U16_FWD_N( m->data, i, charlen, atttypmod); + SET_VARSIZE(m, sizeof(UChar) * i + MVARCHARHDRSZ); + } +} + +Datum +mvarchar(PG_FUNCTION_ARGS) { + MVarChar *source = PG_GETARG_MVARCHAR(0); + MVarChar *result; + int32 typmod = PG_GETARG_INT32(1); + bool isExplicit = PG_GETARG_BOOL(2); + int charlen = u_countChar32(source->data, UVARCHARLENGTH(source)); + + result = palloc( VARSIZE(source) ); + memcpy( result, source, VARSIZE(source) ); + PG_FREE_IF_COPY(source,0); + + if ( typmod>=0 && typmod < charlen ) { + if ( isExplicit ) + mvarchar_strip(result, typmod); + else + elog(ERROR,"value too long for type mvarchar(%d)", typmod); + } + + PG_RETURN_MVARCHAR(result); +} + +PG_FUNCTION_INFO_V1(mvarchar_mchar); +Datum mvarchar_mchar(PG_FUNCTION_ARGS); +Datum +mvarchar_mchar(PG_FUNCTION_ARGS) { + MVarChar *source = PG_GETARG_MVARCHAR(0); + MChar *result; + int32 typmod = PG_GETARG_INT32(1); +#ifdef NOT_USED + bool isExplicit = PG_GETARG_BOOL(2); +#endif + + result = palloc( MVARCHARLENGTH(source) + MCHARHDRSZ ); + SET_VARSIZE(result, MVARCHARLENGTH(source) + MCHARHDRSZ); + memcpy( result->data, source->data, MVARCHARLENGTH(source)); + + PG_FREE_IF_COPY(source,0); + + mchar_strip( result, typmod ); + + PG_RETURN_MCHAR(result); +} + +PG_FUNCTION_INFO_V1(mchar_mvarchar); +Datum mchar_mvarchar(PG_FUNCTION_ARGS); +Datum +mchar_mvarchar(PG_FUNCTION_ARGS) { + MChar *source = PG_GETARG_MCHAR(0); + MVarChar *result; + int32 typmod = PG_GETARG_INT32(1); + int32 scharlen = u_countChar32(source->data, UCHARLENGTH(source)); + int32 curlen = 0, maxcharlen; +#ifdef NOT_USED + bool isExplicit = PG_GETARG_BOOL(2); +#endif + + maxcharlen = (source->typmod > 0) ? source->typmod : scharlen; + + result = palloc( MVARCHARHDRSZ + sizeof(UChar) * 2 * maxcharlen ); + + curlen = UCHARLENGTH( source ); + if ( curlen > 0 ) + memcpy( result->data, source->data, MCHARLENGTH(source) ); + if ( source->typmod > 0 && scharlen < source->typmod ) { + FillWhiteSpace( result->data + curlen, source->typmod-scharlen ); + curlen += source->typmod-scharlen; + } + SET_VARSIZE(result, MVARCHARHDRSZ + curlen *sizeof(UChar)); + + PG_FREE_IF_COPY(source,0); + + mvarchar_strip( result, typmod ); + + PG_RETURN_MCHAR(result); +} + +PG_FUNCTION_INFO_V1(mchar_send); +Datum mchar_send(PG_FUNCTION_ARGS); +Datum +mchar_send(PG_FUNCTION_ARGS) { + MChar *in = PG_GETARG_MCHAR(0); + size_t inlen = UCHARLENGTH(in); + size_t charlen = u_countChar32(in->data, inlen); + StringInfoData buf; + + Assert( in->typmod < 0 || charlen<=in->typmod ); + + pq_begintypsend(&buf); + pq_sendbytes(&buf, (char*)in->data, inlen * sizeof(UChar) ); + + if ( in->typmod>0 && charlen < in->typmod ) { + int nw = in->typmod - charlen; + UChar *white = palloc( sizeof(UChar) * nw ); + + FillWhiteSpace( white, nw ); + pq_sendbytes(&buf, (char*)white, sizeof(UChar) * nw); + pfree(white); + } + + PG_FREE_IF_COPY(in,0); + + PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); +} + +PG_FUNCTION_INFO_V1(mchar_recv); +Datum mchar_recv(PG_FUNCTION_ARGS); +Datum +mchar_recv(PG_FUNCTION_ARGS) { + StringInfo buf = (StringInfo) PG_GETARG_POINTER(0); + MChar *res; + int nbytes; +#ifdef NOT_USED + Oid typelem = PG_GETARG_OID(1); +#endif + int32 atttypmod = PG_GETARG_INT32(2); + + nbytes = buf->len - buf->cursor; + res = (MChar*)palloc( nbytes + MCHARHDRSZ ); + res->len = nbytes + MCHARHDRSZ; + res->typmod = -1; + SET_VARSIZE(res, res->len); + pq_copymsgbytes(buf, (char*)res->data, nbytes); + + mchar_strip( res, atttypmod ); + + PG_RETURN_MCHAR(res); +} + +PG_FUNCTION_INFO_V1(mvarchar_send); +Datum mvarchar_send(PG_FUNCTION_ARGS); +Datum +mvarchar_send(PG_FUNCTION_ARGS) { + MVarChar *in = PG_GETARG_MVARCHAR(0); + size_t inlen = UVARCHARLENGTH(in); + StringInfoData buf; + + pq_begintypsend(&buf); + pq_sendbytes(&buf, (char*)in->data, inlen * sizeof(UChar) ); + + PG_FREE_IF_COPY(in,0); + + PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); +} + +PG_FUNCTION_INFO_V1(mvarchar_recv); +Datum mvarchar_recv(PG_FUNCTION_ARGS); +Datum +mvarchar_recv(PG_FUNCTION_ARGS) { + StringInfo buf = (StringInfo) PG_GETARG_POINTER(0); + MVarChar *res; + int nbytes; +#ifdef NOT_USED + Oid typelem = PG_GETARG_OID(1); +#endif + int32 atttypmod = PG_GETARG_INT32(2); + + nbytes = buf->len - buf->cursor; + res = (MVarChar*)palloc( nbytes + MVARCHARHDRSZ ); + res->len = nbytes + MVARCHARHDRSZ; + SET_VARSIZE(res, res->len); + pq_copymsgbytes(buf, (char*)res->data, nbytes); + + mvarchar_strip( res, atttypmod ); + + PG_RETURN_MVARCHAR(res); +} + + diff --git a/contrib/mchar/mchar_like.c b/contrib/mchar/mchar_like.c new file mode 100644 index 0000000..3af13d8 --- /dev/null +++ b/contrib/mchar/mchar_like.c @@ -0,0 +1,862 @@ +#include "mchar.h" +#include "mb/pg_wchar.h" + +#include "catalog/pg_collation.h" +#include "utils/selfuncs.h" +#include "nodes/primnodes.h" +#include "nodes/makefuncs.h" +#include "regex/regex.h" + +/* +** Originally written by Rich $alz, mirror!rs, Wed Nov 26 19:03:17 EST 1986. +** Rich $alz is now . +** Special thanks to Lars Mathiesen for the LABORT code. +** +** This code was shamelessly stolen from the "pql" code by myself and +** slightly modified :) +** +** All references to the word "star" were replaced by "percent" +** All references to the word "wild" were replaced by "like" +** +** All the nice shell RE matching stuff was replaced by just "_" and "%" +** +** As I don't have a copy of the SQL standard handy I wasn't sure whether +** to leave in the '\' escape character handling. +** +** Keith Parks. +** +** SQL92 lets you specify the escape character by saying +** LIKE ESCAPE . We are a small operation +** so we force you to use '\'. - ay 7/95 +** +** Now we have the like_escape() function that converts patterns with +** any specified escape character (or none at all) to the internal +** default escape character, which is still '\'. - tgl 9/2000 +** +** The code is rewritten to avoid requiring null-terminated strings, +** which in turn allows us to leave out some memcpy() operations. +** This code should be faster and take less memory, but no promises... +** - thomas 2000-08-06 +** +** Adopted for UTF-16 by teodor +*/ + +#define LIKE_TRUE 1 +#define LIKE_FALSE 0 +#define LIKE_ABORT (-1) + + +static int +uchareq(UChar *p1, UChar *p2) { + int l1=0, l2=0; + /* + * Count length of char: + * We suppose that string is correct!! + */ + U16_FWD_1(p1, l1, 2); + U16_FWD_1(p2, l2, 2); + + return (UCharCaseCompare(p1, l1, p2, l2)==0) ? 1 : 0; +} + +#define NextChar(p, plen) \ + do { \ + int __l = 0; \ + U16_FWD_1((p), __l, (plen));\ + (p) +=__l; \ + (plen) -=__l; \ + } while(0) + +#define CopyAdvChar(dst, src, srclen) \ + do { \ + int __l = 0; \ + U16_FWD_1((src), __l, (srclen));\ + (srclen) -= __l; \ + while (__l-- > 0) \ + *(dst)++ = *(src)++; \ + } while (0) + + +static UChar UCharPercent = 0; +static UChar UCharBackSlesh = 0; +static UChar UCharUnderLine = 0; +static UChar UCharStar = 0; +static UChar UCharDotDot = 0; +static UChar UCharUp = 0; +static UChar UCharLBracket = 0; +static UChar UCharQ = 0; +static UChar UCharRBracket = 0; +static UChar UCharDollar = 0; +static UChar UCharDot = 0; +static UChar UCharLFBracket = 0; +static UChar UCharRFBracket = 0; +static UChar UCharQuote = 0; +static UChar UCharSpace = 0; + +#define MkUChar(uc, c) do { \ + char __c = (c); \ + u_charsToUChars( &__c, &(uc), 1 ); \ +} while(0) + +#define SET_UCHAR if ( UCharPercent == 0 ) { \ + MkUChar( UCharPercent, '%' ); \ + MkUChar( UCharBackSlesh, '\\' ); \ + MkUChar( UCharUnderLine, '_' ); \ + MkUChar( UCharStar, '*' ); \ + MkUChar( UCharDotDot, ':' ); \ + MkUChar( UCharUp, '^' ); \ + MkUChar( UCharLBracket, '(' ); \ + MkUChar( UCharQ, '?' ); \ + MkUChar( UCharRBracket, ')' ); \ + MkUChar( UCharDollar, '$' ); \ + MkUChar( UCharDot, '.' ); \ + MkUChar( UCharLFBracket, '{' ); \ + MkUChar( UCharRFBracket, '}' ); \ + MkUChar( UCharQuote, '"' ); \ + MkUChar( UCharSpace, ' ' ); \ + } + +int +m_isspace(UChar c) { + SET_UCHAR; + + return (c == UCharSpace); +} + +static int +MatchUChar(UChar *t, int tlen, UChar *p, int plen) { + SET_UCHAR; + + /* Fast path for match-everything pattern */ + if ((plen == 1) && (*p == UCharPercent)) + return LIKE_TRUE; + + while ((tlen > 0) && (plen > 0)) { + if (*p == UCharBackSlesh) { + /* Next pattern char must match literally, whatever it is */ + NextChar(p, plen); + if ((plen <= 0) || !uchareq(t, p)) + return LIKE_FALSE; + } else if (*p == UCharPercent) { + /* %% is the same as % according to the SQL standard */ + /* Advance past all %'s */ + while ((plen > 0) && (*p == UCharPercent)) + NextChar(p, plen); + /* Trailing percent matches everything. */ + if (plen <= 0) + return LIKE_TRUE; + + /* + * Otherwise, scan for a text position at which we can match the + * rest of the pattern. + */ + while (tlen > 0) { + /* + * Optimization to prevent most recursion: don't recurse + * unless first pattern char might match this text char. + */ + if (uchareq(t, p) || (*p == UCharBackSlesh) || (*p == UCharUnderLine)) { + int matched = MatchUChar(t, tlen, p, plen); + + if (matched != LIKE_FALSE) + return matched; /* TRUE or ABORT */ + } + + NextChar(t, tlen); + } + + /* + * End of text with no match, so no point in trying later places + * to start matching this pattern. + */ + return LIKE_ABORT; + } if ((*p != UCharUnderLine) && !uchareq(t, p)) { + /* + * Not the single-character wildcard and no explicit match? Then + * time to quit... + */ + return LIKE_FALSE; + } + + NextChar(t, tlen); + NextChar(p, plen); + } + + if (tlen > 0) + return LIKE_FALSE; /* end of pattern, but not of text */ + + /* End of input string. Do we have matching pattern remaining? */ + while ((plen > 0) && (*p == UCharPercent)) /* allow multiple %'s at end of + * pattern */ + NextChar(p, plen); + if (plen <= 0) + return LIKE_TRUE; + + /* + * End of text with no match, so no point in trying later places to start + * matching this pattern. + */ + + return LIKE_ABORT; +} + +PG_FUNCTION_INFO_V1( mvarchar_like ); +Datum mvarchar_like( PG_FUNCTION_ARGS ); +Datum +mvarchar_like( PG_FUNCTION_ARGS ) { + MVarChar *str = PG_GETARG_MVARCHAR(0); + MVarChar *pat = PG_GETARG_MVARCHAR(1); + bool result; + + result = MatchUChar( str->data, UVARCHARLENGTH(str), pat->data, UVARCHARLENGTH(pat) ); + + PG_FREE_IF_COPY(str,0); + PG_FREE_IF_COPY(pat,1); + + PG_RETURN_BOOL(result == LIKE_TRUE); +} + +PG_FUNCTION_INFO_V1( mvarchar_notlike ); +Datum mvarchar_notlike( PG_FUNCTION_ARGS ); +Datum +mvarchar_notlike( PG_FUNCTION_ARGS ) { + bool res = DatumGetBool( DirectFunctionCall2( + mvarchar_like, + PG_GETARG_DATUM(0), + PG_GETARG_DATUM(1) + )); + PG_RETURN_BOOL( !res ); +} + +/* + * Removes trailing spaces in '111 %' pattern + */ +static UChar * +removeTrailingSpaces( UChar *src, int srclen, int *dstlen, bool *isSpecialLast) { + UChar* dst = src; + UChar *ptr, *dptr, *markptr; + + *dstlen = srclen; + ptr = src + srclen-1; + SET_UCHAR; + + *isSpecialLast = ( srclen > 0 && (u_isspace(*ptr) || *ptr == UCharPercent || *ptr == UCharUnderLine ) ) ? true : false; + while( ptr>=src ) { + if ( *ptr == UCharPercent || *ptr == UCharUnderLine ) { + if ( ptr==src ) + return dst; /* first character */ + + if ( *(ptr-1) == UCharBackSlesh ) + return dst; /* use src as is */ + + if ( u_isspace( *(ptr-1) ) ) { + ptr--; + break; /* % or _ is after space which should be removed */ + } + } else { + return dst; + } + ptr--; + } + + markptr = ptr+1; + dst = (UChar*)palloc( sizeof(UChar) * srclen ); + + /* find last non-space character */ + while( ptr>=src && u_isspace(*ptr) ) + ptr--; + + dptr = dst + (ptr-src+1); + + if ( ptr>=src ) + memcpy( dst, src, sizeof(UChar) * (ptr-src+1) ); + + while( markptr - src < srclen ) { + *dptr = *markptr; + dptr++; + markptr++; + } + + *dstlen = dptr - dst; + return dst; +} + +static UChar* +addTrailingSpace( MChar *src, int *newlen ) { + int scharlen = u_countChar32(src->data, UCHARLENGTH(src)); + + if ( src->typmod > scharlen ) { + UChar *res = (UChar*) palloc( sizeof(UChar) * (UCHARLENGTH(src) + src->typmod) ); + + memcpy( res, src->data, sizeof(UChar) * UCHARLENGTH(src)); + FillWhiteSpace( res+UCHARLENGTH(src), src->typmod - scharlen ); + + *newlen = src->typmod; + + return res; + } else { + *newlen = UCHARLENGTH(src); + return src->data; + } +} + +PG_FUNCTION_INFO_V1( mchar_like ); +Datum mchar_like( PG_FUNCTION_ARGS ); +Datum +mchar_like( PG_FUNCTION_ARGS ) { + MChar *str = PG_GETARG_MCHAR(0); + MVarChar *pat = PG_GETARG_MVARCHAR(1); + bool result, isNeedAdd = false; + UChar *cleaned, *filled; + int clen=0, flen=0; + + cleaned = removeTrailingSpaces(pat->data, UVARCHARLENGTH(pat), &clen, &isNeedAdd); + if ( isNeedAdd ) + filled = addTrailingSpace(str, &flen); + else { + filled = str->data; + flen = UCHARLENGTH(str); + } + + result = MatchUChar( filled, flen, cleaned, clen ); + + if ( pat->data != cleaned ) + pfree( cleaned ); + if ( str->data != filled ) + pfree( filled ); + + PG_FREE_IF_COPY(str,0); + PG_FREE_IF_COPY(pat,1); + + + PG_RETURN_BOOL(result == LIKE_TRUE); +} + +PG_FUNCTION_INFO_V1( mchar_notlike ); +Datum mchar_notlike( PG_FUNCTION_ARGS ); +Datum +mchar_notlike( PG_FUNCTION_ARGS ) { + bool res = DatumGetBool( DirectFunctionCall2( + mchar_like, + PG_GETARG_DATUM(0), + PG_GETARG_DATUM(1) + )); + + PG_RETURN_BOOL( !res ); +} + + + +PG_FUNCTION_INFO_V1( mchar_pattern_fixed_prefix ); +Datum mchar_pattern_fixed_prefix( PG_FUNCTION_ARGS ); +Datum +mchar_pattern_fixed_prefix( PG_FUNCTION_ARGS ) { + Const *patt = (Const*)PG_GETARG_POINTER(0); + Pattern_Type ptype = (Pattern_Type)PG_GETARG_INT32(1); + Const **prefix = (Const**)PG_GETARG_POINTER(2); + UChar *spatt; + int32 slen, prefixlen=0, restlen=0, i=0; + MVarChar *sprefix; + MVarChar *srest; + Pattern_Prefix_Status status = Pattern_Prefix_None; + + *prefix = NULL; + + if ( ptype != Pattern_Type_Like ) + PG_RETURN_INT32(Pattern_Prefix_None); + + SET_UCHAR; + + spatt = ((MVarChar*)DatumGetPointer(patt->constvalue))->data; + slen = UVARCHARLENGTH( DatumGetPointer(patt->constvalue) ); + + sprefix = (MVarChar*)palloc( MCHARHDRSZ /*The biggest hdr!! */ + sizeof(UChar) * slen ); + srest = (MVarChar*)palloc( MCHARHDRSZ /*The biggest hdr!! */ + sizeof(UChar) * slen ); + + while( prefixlen < slen && i < slen ) { + if ( spatt[i] == UCharPercent || spatt[i] == UCharUnderLine ) + break; + else if ( spatt[i] == UCharBackSlesh ) { + i++; + if ( i>= slen ) + break; + } + sprefix->data[ prefixlen++ ] = spatt[i++]; + } + + while( prefixlen > 0 ) { + if ( ! u_isspace( sprefix->data[ prefixlen-1 ] ) ) + break; + prefixlen--; + } + + if ( prefixlen == 0 ) + PG_RETURN_INT32(Pattern_Prefix_None); + + for(;idata[ restlen++ ] = spatt[i]; + + SET_VARSIZE(sprefix, sizeof(UChar) * prefixlen + MVARCHARHDRSZ); + SET_VARSIZE(srest, sizeof(UChar) * restlen + MVARCHARHDRSZ); + + *prefix = makeConst( patt->consttype, -1, DEFAULT_COLLATION_OID, VARSIZE(sprefix), PointerGetDatum(sprefix), false, false ); + + if ( prefixlen == slen ) /* in LIKE, an empty pattern is an exact match! */ + status = Pattern_Prefix_Exact; + else if ( prefixlen > 0 ) + status = Pattern_Prefix_Partial; + + PG_RETURN_INT32( status ); +} + +static bool +checkCmp( UChar *left, int32 leftlen, UChar *right, int32 rightlen ) { + + return (UCharCaseCompare( left, leftlen, right, rightlen) < 0 ) ? true : false; +} + + +PG_FUNCTION_INFO_V1( mchar_greaterstring ); +Datum mchar_greaterstring( PG_FUNCTION_ARGS ); +Datum +mchar_greaterstring( PG_FUNCTION_ARGS ) { + Const *patt = (Const*)PG_GETARG_POINTER(0); + char *src = (char*)DatumGetPointer( patt->constvalue ); + int dstlen, srclen = VARSIZE(src); + char *dst = palloc( srclen ); + UChar *ptr, *srcptr; + + memcpy( dst, src, srclen ); + + srclen = dstlen = UVARCHARLENGTH( dst ); + ptr = ((MVarChar*)dst)->data; + srcptr = ((MVarChar*)src)->data; + + while( dstlen > 0 ) { + UChar *lastchar = ptr + dstlen - 1; + + if ( !U16_IS_LEAD( *lastchar ) ) { + while( *lastchar<0xffff ) { + + (*lastchar)++; + + if ( ublock_getCode(*lastchar) == UBLOCK_INVALID_CODE || !checkCmp( srcptr, srclen, ptr, dstlen ) ) + continue; + else { + SET_VARSIZE(dst, sizeof(UChar) * dstlen + MVARCHARHDRSZ); + + PG_RETURN_POINTER( makeConst( patt->consttype, -1, DEFAULT_COLLATION_OID, VARSIZE(dst), PointerGetDatum(dst), false, false ) ); + } + } + } + + dstlen--; + } + + PG_RETURN_POINTER(NULL); +} + +static int +do_like_escape( UChar *pat, int plen, UChar *esc, int elen, UChar *result) { + UChar *p = pat,*e =esc ,*r; + bool afterescape; + + r = result; + SET_UCHAR; + + if ( elen == 0 ) { + /* + * No escape character is wanted. Double any backslashes in the + * pattern to make them act like ordinary characters. + */ + while (plen > 0) { + if (*p == UCharBackSlesh ) + *r++ = UCharBackSlesh; + CopyAdvChar(r, p, plen); + } + } else { + /* + * The specified escape must be only a single character. + */ + NextChar(e, elen); + + if (elen != 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_ESCAPE_SEQUENCE), + errmsg("invalid escape string"), + errhint("Escape string must be empty or one character."))); + + e = esc; + + /* + * If specified escape is '\', just copy the pattern as-is. + */ + if ( *e == UCharBackSlesh ) { + memcpy(result, pat, plen * sizeof(UChar)); + return plen; + } + + /* + * Otherwise, convert occurrences of the specified escape character to + * '\', and double occurrences of '\' --- unless they immediately + * follow an escape character! + */ + afterescape = false; + + while (plen > 0) { + if ( uchareq(p,e) && !afterescape) { + *r++ = UCharBackSlesh; + NextChar(p, plen); + afterescape = true; + } else if ( *p == UCharBackSlesh ) { + *r++ = UCharBackSlesh; + if (!afterescape) + *r++ = UCharBackSlesh; + NextChar(p, plen); + afterescape = false; + } else { + CopyAdvChar(r, p, plen); + afterescape = false; + } + } + } + + return ( r - result ); +} + +PG_FUNCTION_INFO_V1( mvarchar_like_escape ); +Datum mvarchar_like_escape( PG_FUNCTION_ARGS ); +Datum +mvarchar_like_escape( PG_FUNCTION_ARGS ) { + MVarChar *pat = PG_GETARG_MVARCHAR(0); + MVarChar *esc = PG_GETARG_MVARCHAR(1); + MVarChar *result; + + result = (MVarChar*)palloc( MVARCHARHDRSZ + sizeof(UChar)*2*UVARCHARLENGTH(pat) ); + result->len = MVARCHARHDRSZ + do_like_escape( pat->data, UVARCHARLENGTH(pat), + esc->data, UVARCHARLENGTH(esc), + result->data ) * sizeof(UChar); + + SET_VARSIZE(result, result->len); + PG_FREE_IF_COPY(pat,0); + PG_FREE_IF_COPY(esc,1); + + PG_RETURN_MVARCHAR(result); +} + +#define RE_CACHE_SIZE 32 +typedef struct ReCache { + UChar *pattern; + int length; + int flags; + regex_t re; +} ReCache; + +static int num_res = 0; +static ReCache re_array[RE_CACHE_SIZE]; /* cached re's */ +static const int mchar_regex_flavor = REG_ADVANCED | REG_ICASE; + +static regex_t * +RE_compile_and_cache(UChar *text_re, int text_re_len, int cflags) { + pg_wchar *pattern; + size_t pattern_len; + int i; + int regcomp_result; + ReCache re_temp; + char errMsg[128]; + + + for (i = 0; i < num_res; i++) { + if ( re_array[i].length == text_re_len && + re_array[i].flags == cflags && + memcmp(re_array[i].pattern, text_re, sizeof(UChar)*text_re_len) == 0 ) { + + /* Found, move it to front */ + if ( i>0 ) { + re_temp = re_array[i]; + memmove(&re_array[1], &re_array[0], i * sizeof(ReCache)); + re_array[0] = re_temp; + } + + return &re_array[0].re; + } + } + + pattern = (pg_wchar *) palloc((1 + text_re_len) * sizeof(pg_wchar)); + pattern_len = UChar2Wchar(text_re, text_re_len, pattern); + + regcomp_result = pg_regcomp(&re_temp.re, + pattern, + pattern_len, + cflags, + DEFAULT_COLLATION_OID); + pfree( pattern ); + + if (regcomp_result != REG_OKAY) { + pg_regerror(regcomp_result, &re_temp.re, errMsg, sizeof(errMsg)); + ereport(ERROR, + (errcode(ERRCODE_INVALID_REGULAR_EXPRESSION), + errmsg("invalid regular expression: %s", errMsg))); + } + + re_temp.pattern = malloc( text_re_len*sizeof(UChar) ); + if ( re_temp.pattern == NULL ) + elog(ERROR,"Out of memory"); + + memcpy(re_temp.pattern, text_re, text_re_len*sizeof(UChar) ); + re_temp.length = text_re_len; + re_temp.flags = cflags; + + if (num_res >= RE_CACHE_SIZE) { + --num_res; + Assert(num_res < RE_CACHE_SIZE); + pg_regfree(&re_array[num_res].re); + free(re_array[num_res].pattern); + } + + if (num_res > 0) + memmove(&re_array[1], &re_array[0], num_res * sizeof(ReCache)); + + re_array[0] = re_temp; + num_res++; + + return &re_array[0].re; +} + +static bool +RE_compile_and_execute(UChar *pat, int pat_len, UChar *dat, int dat_len, + int cflags, int nmatch, regmatch_t *pmatch) { + pg_wchar *data; + size_t data_len; + int regexec_result; + regex_t *re; + char errMsg[128]; + + data = (pg_wchar *) palloc((1+dat_len) * sizeof(pg_wchar)); + data_len = UChar2Wchar(dat, dat_len, data); + + re = RE_compile_and_cache(pat, pat_len, cflags); + + regexec_result = pg_regexec(re, + data, + data_len, + 0, + NULL, + nmatch, + pmatch, + 0); + pfree(data); + + if (regexec_result != REG_OKAY && regexec_result != REG_NOMATCH) { + /* re failed??? */ + pg_regerror(regexec_result, re, errMsg, sizeof(errMsg)); + ereport(ERROR, + (errcode(ERRCODE_INVALID_REGULAR_EXPRESSION), + errmsg("regular expression failed: %s", errMsg))); + } + + return (regexec_result == REG_OKAY); +} + +PG_FUNCTION_INFO_V1( mchar_regexeq ); +Datum mchar_regexeq( PG_FUNCTION_ARGS ); +Datum +mchar_regexeq( PG_FUNCTION_ARGS ) { + MChar *t = PG_GETARG_MCHAR(0); + MChar *p = PG_GETARG_MCHAR(1); + bool res; + + res = RE_compile_and_execute(p->data, UCHARLENGTH(p), + t->data, UCHARLENGTH(t), + mchar_regex_flavor, + 0, NULL); + PG_FREE_IF_COPY(t, 0); + PG_FREE_IF_COPY(p, 1); + + PG_RETURN_BOOL(res); +} + +PG_FUNCTION_INFO_V1( mchar_regexne ); +Datum mchar_regexne( PG_FUNCTION_ARGS ); +Datum +mchar_regexne( PG_FUNCTION_ARGS ) { + MChar *t = PG_GETARG_MCHAR(0); + MChar *p = PG_GETARG_MCHAR(1); + bool res; + + res = RE_compile_and_execute(p->data, UCHARLENGTH(p), + t->data, UCHARLENGTH(t), + mchar_regex_flavor, + 0, NULL); + PG_FREE_IF_COPY(t, 0); + PG_FREE_IF_COPY(p, 1); + + PG_RETURN_BOOL(!res); +} + +PG_FUNCTION_INFO_V1( mvarchar_regexeq ); +Datum mvarchar_regexeq( PG_FUNCTION_ARGS ); +Datum +mvarchar_regexeq( PG_FUNCTION_ARGS ) { + MVarChar *t = PG_GETARG_MVARCHAR(0); + MVarChar *p = PG_GETARG_MVARCHAR(1); + bool res; + + res = RE_compile_and_execute(p->data, UVARCHARLENGTH(p), + t->data, UVARCHARLENGTH(t), + mchar_regex_flavor, + 0, NULL); + PG_FREE_IF_COPY(t, 0); + PG_FREE_IF_COPY(p, 1); + + PG_RETURN_BOOL(res); +} + +PG_FUNCTION_INFO_V1( mvarchar_regexne ); +Datum mvarchar_regexne( PG_FUNCTION_ARGS ); +Datum +mvarchar_regexne( PG_FUNCTION_ARGS ) { + MVarChar *t = PG_GETARG_MVARCHAR(0); + MVarChar *p = PG_GETARG_MVARCHAR(1); + bool res; + + res = RE_compile_and_execute(p->data, UVARCHARLENGTH(p), + t->data, UVARCHARLENGTH(t), + mchar_regex_flavor, + 0, NULL); + PG_FREE_IF_COPY(t, 0); + PG_FREE_IF_COPY(p, 1); + + PG_RETURN_BOOL(!res); +} + +static int +do_similar_escape(UChar *p, int plen, UChar *e, int elen, UChar *result) { + UChar *r; + bool afterescape = false; + int nquotes = 0; + + SET_UCHAR; + + if (e==NULL || elen <0 ) { + e = &UCharBackSlesh; + elen = 1; + } else { + if ( elen == 0 ) + e = NULL; + else if ( elen != 1) + ereport(ERROR, + (errcode(ERRCODE_INVALID_ESCAPE_SEQUENCE), + errmsg("invalid escape string"), + errhint("Escape string must be empty or one character."))); + } + + /* + * Look explanation of following in ./utils/adt/regexp.c + */ + r = result; + + *r++ = UCharStar; + *r++ = UCharStar; + *r++ = UCharStar; + *r++ = UCharDotDot; + *r++ = UCharUp; + *r++ = UCharLBracket; + *r++ = UCharQ; + *r++ = UCharDotDot; + + while( plen>0 ) { + if (afterescape) { + if ( *p == UCharQuote ) { + *r++ = ((nquotes++ % 2) == 0) ? UCharLBracket : UCharRBracket; + } else { + *r++ = UCharBackSlesh; + *r++ = *p; + } + afterescape = false; + } else if ( e && *p == *e ) { + afterescape = true; + } else if ( *p == UCharPercent ) { + *r++ = UCharDot; + *r++ = UCharStar; + } else if ( *p == UCharUnderLine ) { + *r++ = UCharDot; + } else if ( *p == UCharBackSlesh || *p == UCharDot || *p == UCharQ || *p == UCharLFBracket ) { + *r++ = UCharBackSlesh; + *r++ = *p; + } else + *r++ = *p; + + p++, plen--; + } + + *r++ = UCharRBracket; + *r++ = UCharDollar; + + return r-result; +} + +PG_FUNCTION_INFO_V1( mchar_similar_escape ); +Datum mchar_similar_escape( PG_FUNCTION_ARGS ); +Datum +mchar_similar_escape( PG_FUNCTION_ARGS ) { + MChar *pat; + MChar *esc; + MChar *result; + + if (PG_ARGISNULL(0)) + PG_RETURN_NULL(); + pat = PG_GETARG_MCHAR(0); + + if (PG_ARGISNULL(1)) { + esc = NULL; + } else { + esc = PG_GETARG_MCHAR(1); + } + + result = (MChar*)palloc( MCHARHDRSZ + sizeof(UChar)*(10 + 2*UCHARLENGTH(pat)) ); + result->len = MCHARHDRSZ + do_similar_escape( pat->data, UCHARLENGTH(pat), + (esc) ? esc->data : NULL, (esc) ? UCHARLENGTH(esc) : -1, + result->data ) * sizeof(UChar); + result->typmod=-1; + + SET_VARSIZE(result, result->len); + PG_FREE_IF_COPY(pat,0); + if ( esc ) + PG_FREE_IF_COPY(esc,1); + + PG_RETURN_MCHAR(result); +} + +PG_FUNCTION_INFO_V1( mvarchar_similar_escape ); +Datum mvarchar_similar_escape( PG_FUNCTION_ARGS ); +Datum +mvarchar_similar_escape( PG_FUNCTION_ARGS ) { + MVarChar *pat; + MVarChar *esc; + MVarChar *result; + + if (PG_ARGISNULL(0)) + PG_RETURN_NULL(); + pat = PG_GETARG_MVARCHAR(0); + + if (PG_ARGISNULL(1)) { + esc = NULL; + } else { + esc = PG_GETARG_MVARCHAR(1); + } + + result = (MVarChar*)palloc( MVARCHARHDRSZ + sizeof(UChar)*(10 + 2*UVARCHARLENGTH(pat)) ); + result->len = MVARCHARHDRSZ + do_similar_escape( pat->data, UVARCHARLENGTH(pat), + (esc) ? esc->data : NULL, (esc) ? UVARCHARLENGTH(esc) : -1, + result->data ) * sizeof(UChar); + + SET_VARSIZE(result, result->len); + PG_FREE_IF_COPY(pat,0); + if ( esc ) + PG_FREE_IF_COPY(esc,1); + + PG_RETURN_MVARCHAR(result); +} + +#define RE_CACHE_SIZE 32 diff --git a/contrib/mchar/mchar_op.c b/contrib/mchar/mchar_op.c new file mode 100644 index 0000000..4694d9c --- /dev/null +++ b/contrib/mchar/mchar_op.c @@ -0,0 +1,449 @@ +#include "mchar.h" + +int +lengthWithoutSpaceVarChar(MVarChar *m) { + int l = UVARCHARLENGTH(m); + + while( l>0 && m_isspace( m->data[ l-1 ] ) ) + l--; + + return l; +} + +int +lengthWithoutSpaceChar(MChar *m) { + int l = UCHARLENGTH(m); + + while( l>0 && m_isspace( m->data[ l-1 ] ) ) + l--; + + return l; +} + +static inline int +mchar_icase_compare( MChar *a, MChar *b ) { + return UCharCaseCompare( + a->data, lengthWithoutSpaceChar(a), + b->data, lengthWithoutSpaceChar(b) + ); +} + +static inline int +mchar_case_compare( MChar *a, MChar *b ) { + return UCharCompare( + a->data, lengthWithoutSpaceChar(a), + b->data, lengthWithoutSpaceChar(b) + ); +} + +#define MCHARCMPFUNC( c, type, action, ret ) \ +PG_FUNCTION_INFO_V1( mchar_##c##_##type ); \ +Datum mchar_##c##_##type(PG_FUNCTION_ARGS);\ +Datum \ +mchar_##c##_##type(PG_FUNCTION_ARGS) { \ + MChar *a = PG_GETARG_MCHAR(0); \ + MChar *b = PG_GETARG_MCHAR(1); \ + int res = mchar_##c##_compare(a,b); \ + \ + PG_FREE_IF_COPY(a,0); \ + PG_FREE_IF_COPY(b,1); \ + PG_RETURN_##ret( res action 0 ); \ +} + + +MCHARCMPFUNC( case, eq, ==, BOOL ) +MCHARCMPFUNC( case, ne, !=, BOOL ) +MCHARCMPFUNC( case, lt, <, BOOL ) +MCHARCMPFUNC( case, le, <=, BOOL ) +MCHARCMPFUNC( case, ge, >=, BOOL ) +MCHARCMPFUNC( case, gt, >, BOOL ) +MCHARCMPFUNC( case, cmp, +, INT32 ) + +MCHARCMPFUNC( icase, eq, ==, BOOL ) +MCHARCMPFUNC( icase, ne, !=, BOOL ) +MCHARCMPFUNC( icase, lt, <, BOOL ) +MCHARCMPFUNC( icase, le, <=, BOOL ) +MCHARCMPFUNC( icase, ge, >=, BOOL ) +MCHARCMPFUNC( icase, gt, >, BOOL ) +MCHARCMPFUNC( icase, cmp, +, INT32 ) + +PG_FUNCTION_INFO_V1( mchar_larger ); +Datum mchar_larger( PG_FUNCTION_ARGS ); +Datum +mchar_larger( PG_FUNCTION_ARGS ) { + MChar *a = PG_GETARG_MCHAR(0); + MChar *b = PG_GETARG_MCHAR(1); + MChar *r; + + r = ( mchar_icase_compare(a,b) > 0 ) ? a : b; + + PG_RETURN_MCHAR(r); +} + +PG_FUNCTION_INFO_V1( mchar_smaller ); +Datum mchar_smaller( PG_FUNCTION_ARGS ); +Datum +mchar_smaller( PG_FUNCTION_ARGS ) { + MChar *a = PG_GETARG_MCHAR(0); + MChar *b = PG_GETARG_MCHAR(1); + MChar *r; + + r = ( mchar_icase_compare(a,b) < 0 ) ? a : b; + + PG_RETURN_MCHAR(r); +} + + +PG_FUNCTION_INFO_V1( mchar_concat ); +Datum mchar_concat( PG_FUNCTION_ARGS ); +Datum +mchar_concat( PG_FUNCTION_ARGS ) { + MChar *a = PG_GETARG_MCHAR(0); + MChar *b = PG_GETARG_MCHAR(1); + MChar *result; + int maxcharlen, curlen; + int acharlen = u_countChar32(a->data, UCHARLENGTH(a)), + bcharlen = u_countChar32(b->data, UCHARLENGTH(b)); + + + maxcharlen = ((a->typmod<=0) ? acharlen : a->typmod) + + ((b->typmod<=0) ? bcharlen : b->typmod); + + result = (MChar*)palloc( MCHARHDRSZ + sizeof(UChar) * 2 * maxcharlen ); + + curlen = UCHARLENGTH( a ); + if ( curlen > 0 ) + memcpy( result->data, a->data, MCHARLENGTH(a) ); + if ( a->typmod > 0 && acharlen < a->typmod ) { + FillWhiteSpace( result->data + curlen, a->typmod-acharlen ); + curlen += a->typmod-acharlen; + } + + if ( UCHARLENGTH(b) > 0 ) { + memcpy( result->data + curlen, b->data, MCHARLENGTH( b ) ); + curlen += UCHARLENGTH( b ); + } + if ( b->typmod > 0 && bcharlen < b->typmod ) { + FillWhiteSpace( result->data + curlen, b->typmod-bcharlen ); + curlen += b->typmod-bcharlen; + } + + + result->typmod = -1; + SET_VARSIZE(result, sizeof(UChar) * curlen + MCHARHDRSZ); + + PG_FREE_IF_COPY(a,0); + PG_FREE_IF_COPY(b,1); + + PG_RETURN_MCHAR(result); +} + +static inline int +mvarchar_icase_compare( MVarChar *a, MVarChar *b ) { + + return UCharCaseCompare( + a->data, lengthWithoutSpaceVarChar(a), + b->data, lengthWithoutSpaceVarChar(b) + ); +} + +static inline int +mvarchar_case_compare( MVarChar *a, MVarChar *b ) { + return UCharCompare( + a->data, lengthWithoutSpaceVarChar(a), + b->data, lengthWithoutSpaceVarChar(b) + ); +} + +#define MVARCHARCMPFUNC( c, type, action, ret ) \ +PG_FUNCTION_INFO_V1( mvarchar_##c##_##type ); \ +Datum mvarchar_##c##_##type(PG_FUNCTION_ARGS); \ +Datum \ +mvarchar_##c##_##type(PG_FUNCTION_ARGS) { \ + MVarChar *a = PG_GETARG_MVARCHAR(0); \ + MVarChar *b = PG_GETARG_MVARCHAR(1); \ + int res = mvarchar_##c##_compare(a,b); \ + \ + PG_FREE_IF_COPY(a,0); \ + PG_FREE_IF_COPY(b,1); \ + PG_RETURN_##ret( res action 0 ); \ +} + + +MVARCHARCMPFUNC( case, eq, ==, BOOL ) +MVARCHARCMPFUNC( case, ne, !=, BOOL ) +MVARCHARCMPFUNC( case, lt, <, BOOL ) +MVARCHARCMPFUNC( case, le, <=, BOOL ) +MVARCHARCMPFUNC( case, ge, >=, BOOL ) +MVARCHARCMPFUNC( case, gt, >, BOOL ) +MVARCHARCMPFUNC( case, cmp, +, INT32 ) + +MVARCHARCMPFUNC( icase, eq, ==, BOOL ) +MVARCHARCMPFUNC( icase, ne, !=, BOOL ) +MVARCHARCMPFUNC( icase, lt, <, BOOL ) +MVARCHARCMPFUNC( icase, le, <=, BOOL ) +MVARCHARCMPFUNC( icase, ge, >=, BOOL ) +MVARCHARCMPFUNC( icase, gt, >, BOOL ) +MVARCHARCMPFUNC( icase, cmp, +, INT32 ) + +PG_FUNCTION_INFO_V1( mvarchar_larger ); +Datum mvarchar_larger( PG_FUNCTION_ARGS ); +Datum +mvarchar_larger( PG_FUNCTION_ARGS ) { + MVarChar *a = PG_GETARG_MVARCHAR(0); + MVarChar *b = PG_GETARG_MVARCHAR(1); + MVarChar *r; + + r = ( mvarchar_icase_compare(a,b) > 0 ) ? a : b; + + PG_RETURN_MVARCHAR(r); +} + +PG_FUNCTION_INFO_V1( mvarchar_smaller ); +Datum mvarchar_smaller( PG_FUNCTION_ARGS ); +Datum +mvarchar_smaller( PG_FUNCTION_ARGS ) { + MVarChar *a = PG_GETARG_MVARCHAR(0); + MVarChar *b = PG_GETARG_MVARCHAR(1); + MVarChar *r; + + r = ( mvarchar_icase_compare(a,b) < 0 ) ? a : b; + + PG_RETURN_MVARCHAR(r); +} + +PG_FUNCTION_INFO_V1( mvarchar_concat ); +Datum mvarchar_concat( PG_FUNCTION_ARGS ); +Datum +mvarchar_concat( PG_FUNCTION_ARGS ) { + MVarChar *a = PG_GETARG_MVARCHAR(0); + MVarChar *b = PG_GETARG_MVARCHAR(1); + MVarChar *result; + int curlen; + int acharlen = u_countChar32(a->data, UVARCHARLENGTH(a)), + bcharlen = u_countChar32(b->data, UVARCHARLENGTH(b)); + + result = (MVarChar*)palloc( MVARCHARHDRSZ + sizeof(UChar) * 2 * (acharlen + bcharlen) ); + + curlen = UVARCHARLENGTH( a ); + if ( curlen > 0 ) + memcpy( result->data, a->data, MVARCHARLENGTH(a) ); + + if ( UVARCHARLENGTH(b) > 0 ) { + memcpy( result->data + curlen, b->data, MVARCHARLENGTH( b ) ); + curlen += UVARCHARLENGTH( b ); + } + + SET_VARSIZE(result, sizeof(UChar) * curlen + MVARCHARHDRSZ); + + PG_FREE_IF_COPY(a,0); + PG_FREE_IF_COPY(b,1); + + PG_RETURN_MVARCHAR(result); +} + +PG_FUNCTION_INFO_V1( mchar_mvarchar_concat ); +Datum mchar_mvarchar_concat( PG_FUNCTION_ARGS ); +Datum +mchar_mvarchar_concat( PG_FUNCTION_ARGS ) { + MChar *a = PG_GETARG_MCHAR(0); + MVarChar *b = PG_GETARG_MVARCHAR(1); + MVarChar *result; + int curlen, maxcharlen; + int acharlen = u_countChar32(a->data, UCHARLENGTH(a)), + bcharlen = u_countChar32(b->data, UVARCHARLENGTH(b)); + + maxcharlen = ((a->typmod<=0) ? acharlen : a->typmod) + bcharlen; + + result = (MVarChar*)palloc( MVARCHARHDRSZ + sizeof(UChar) * 2 * maxcharlen ); + + curlen = UCHARLENGTH( a ); + if ( curlen > 0 ) + memcpy( result->data, a->data, MCHARLENGTH(a) ); + if ( a->typmod > 0 && acharlen < a->typmod ) { + FillWhiteSpace( result->data + curlen, a->typmod-acharlen ); + curlen += a->typmod-acharlen; + } + + if ( UVARCHARLENGTH(b) > 0 ) { + memcpy( result->data + curlen, b->data, MVARCHARLENGTH( b ) ); + curlen += UVARCHARLENGTH( b ); + } + + SET_VARSIZE(result, sizeof(UChar) * curlen + MVARCHARHDRSZ); + + PG_FREE_IF_COPY(a,0); + PG_FREE_IF_COPY(b,1); + + PG_RETURN_MVARCHAR(result); +} + +PG_FUNCTION_INFO_V1( mvarchar_mchar_concat ); +Datum mvarchar_mchar_concat( PG_FUNCTION_ARGS ); +Datum +mvarchar_mchar_concat( PG_FUNCTION_ARGS ) { + MVarChar *a = PG_GETARG_MVARCHAR(0); + MChar *b = PG_GETARG_MCHAR(1); + MVarChar *result; + int curlen, maxcharlen; + int acharlen = u_countChar32(a->data, UVARCHARLENGTH(a)), + bcharlen = u_countChar32(b->data, UCHARLENGTH(b)); + + maxcharlen = acharlen + ((b->typmod<=0) ? bcharlen : b->typmod); + + result = (MVarChar*)palloc( MVARCHARHDRSZ + sizeof(UChar) * 2 * maxcharlen ); + + curlen = UVARCHARLENGTH( a ); + if ( curlen > 0 ) + memcpy( result->data, a->data, MVARCHARLENGTH(a) ); + + if ( UCHARLENGTH(b) > 0 ) { + memcpy( result->data + curlen, b->data, MCHARLENGTH( b ) ); + curlen += UCHARLENGTH( b ); + } + if ( b->typmod > 0 && bcharlen < b->typmod ) { + FillWhiteSpace( result->data + curlen, b->typmod-bcharlen ); + curlen += b->typmod-bcharlen; + } + + SET_VARSIZE(result, sizeof(UChar) * curlen + MVARCHARHDRSZ); + + PG_FREE_IF_COPY(a,0); + PG_FREE_IF_COPY(b,1); + + PG_RETURN_MVARCHAR(result); +} + +/* + * mchar <> mvarchar + */ +static inline int +mc_mv_icase_compare( MChar *a, MVarChar *b ) { + return UCharCaseCompare( + a->data, lengthWithoutSpaceChar(a), + b->data, lengthWithoutSpaceVarChar(b) + ); +} + +static inline int +mc_mv_case_compare( MChar *a, MVarChar *b ) { + return UCharCompare( + a->data, lengthWithoutSpaceChar(a), + b->data, lengthWithoutSpaceVarChar(b) + ); +} + +#define MC_MV_CHARCMPFUNC( c, type, action, ret ) \ +PG_FUNCTION_INFO_V1( mc_mv_##c##_##type ); \ +Datum mc_mv_##c##_##type(PG_FUNCTION_ARGS);\ +Datum \ +mc_mv_##c##_##type(PG_FUNCTION_ARGS) { \ + MChar *a = PG_GETARG_MCHAR(0); \ + MVarChar *b = PG_GETARG_MVARCHAR(1); \ + int res = mc_mv_##c##_compare(a,b); \ + \ + PG_FREE_IF_COPY(a,0); \ + PG_FREE_IF_COPY(b,1); \ + PG_RETURN_##ret( res action 0 ); \ +} + + +MC_MV_CHARCMPFUNC( case, eq, ==, BOOL ) +MC_MV_CHARCMPFUNC( case, ne, !=, BOOL ) +MC_MV_CHARCMPFUNC( case, lt, <, BOOL ) +MC_MV_CHARCMPFUNC( case, le, <=, BOOL ) +MC_MV_CHARCMPFUNC( case, ge, >=, BOOL ) +MC_MV_CHARCMPFUNC( case, gt, >, BOOL ) +MC_MV_CHARCMPFUNC( case, cmp, +, INT32 ) + +MC_MV_CHARCMPFUNC( icase, eq, ==, BOOL ) +MC_MV_CHARCMPFUNC( icase, ne, !=, BOOL ) +MC_MV_CHARCMPFUNC( icase, lt, <, BOOL ) +MC_MV_CHARCMPFUNC( icase, le, <=, BOOL ) +MC_MV_CHARCMPFUNC( icase, ge, >=, BOOL ) +MC_MV_CHARCMPFUNC( icase, gt, >, BOOL ) +MC_MV_CHARCMPFUNC( icase, cmp, +, INT32 ) + +/* + * mvarchar <> mchar + */ +static inline int +mv_mc_icase_compare( MVarChar *a, MChar *b ) { + return UCharCaseCompare( + a->data, lengthWithoutSpaceVarChar(a), + b->data, lengthWithoutSpaceChar(b) + ); +} + +static inline int +mv_mc_case_compare( MVarChar *a, MChar *b ) { + return UCharCompare( + a->data, lengthWithoutSpaceVarChar(a), + b->data, lengthWithoutSpaceChar(b) + ); +} + +#define MV_MC_CHARCMPFUNC( c, type, action, ret ) \ +PG_FUNCTION_INFO_V1( mv_mc_##c##_##type ); \ +Datum mv_mc_##c##_##type(PG_FUNCTION_ARGS);\ +Datum \ +mv_mc_##c##_##type(PG_FUNCTION_ARGS) { \ + MVarChar *a = PG_GETARG_MVARCHAR(0); \ + MChar *b = PG_GETARG_MCHAR(1); \ + int res = mv_mc_##c##_compare(a,b); \ + \ + PG_FREE_IF_COPY(a,0); \ + PG_FREE_IF_COPY(b,1); \ + PG_RETURN_##ret( res action 0 ); \ +} + + +MV_MC_CHARCMPFUNC( case, eq, ==, BOOL ) +MV_MC_CHARCMPFUNC( case, ne, !=, BOOL ) +MV_MC_CHARCMPFUNC( case, lt, <, BOOL ) +MV_MC_CHARCMPFUNC( case, le, <=, BOOL ) +MV_MC_CHARCMPFUNC( case, ge, >=, BOOL ) +MV_MC_CHARCMPFUNC( case, gt, >, BOOL ) +MV_MC_CHARCMPFUNC( case, cmp, +, INT32 ) + +MV_MC_CHARCMPFUNC( icase, eq, ==, BOOL ) +MV_MC_CHARCMPFUNC( icase, ne, !=, BOOL ) +MV_MC_CHARCMPFUNC( icase, lt, <, BOOL ) +MV_MC_CHARCMPFUNC( icase, le, <=, BOOL ) +MV_MC_CHARCMPFUNC( icase, ge, >=, BOOL ) +MV_MC_CHARCMPFUNC( icase, gt, >, BOOL ) +MV_MC_CHARCMPFUNC( icase, cmp, +, INT32 ) + +#define NULLHASHVALUE (-2147483647) + +#define FULLEQ_FUNC(type, cmpfunc, hashfunc) \ +PG_FUNCTION_INFO_V1( isfulleq_##type ); \ +Datum isfulleq_##type(PG_FUNCTION_ARGS); \ +Datum \ +isfulleq_##type(PG_FUNCTION_ARGS) { \ + if ( PG_ARGISNULL(0) && PG_ARGISNULL(1) ) \ + PG_RETURN_BOOL(true); \ + else if ( PG_ARGISNULL(0) || PG_ARGISNULL(1) ) \ + PG_RETURN_BOOL(false); \ + \ + PG_RETURN_DATUM( DirectFunctionCall2( cmpfunc, \ + PG_GETARG_DATUM(0), \ + PG_GETARG_DATUM(1) \ + ) ); \ +} \ + \ +PG_FUNCTION_INFO_V1( fullhash_##type ); \ +Datum fullhash_##type(PG_FUNCTION_ARGS); \ +Datum \ +fullhash_##type(PG_FUNCTION_ARGS) { \ + if ( PG_ARGISNULL(0) ) \ + PG_RETURN_INT32(NULLHASHVALUE); \ + \ + PG_RETURN_DATUM( DirectFunctionCall1( hashfunc, \ + PG_GETARG_DATUM(0) \ + ) ); \ +} + +FULLEQ_FUNC( mchar, mchar_icase_eq, mchar_hash ); +FULLEQ_FUNC( mvarchar, mvarchar_icase_eq, mvarchar_hash ); + diff --git a/contrib/mchar/mchar_proc.c b/contrib/mchar/mchar_proc.c new file mode 100644 index 0000000..165d44c --- /dev/null +++ b/contrib/mchar/mchar_proc.c @@ -0,0 +1,339 @@ +#include "mchar.h" +#include "mb/pg_wchar.h" +#include "access/hash.h" + +PG_FUNCTION_INFO_V1(mchar_length); +Datum mchar_length(PG_FUNCTION_ARGS); + +Datum +mchar_length(PG_FUNCTION_ARGS) { + MChar *m = PG_GETARG_MCHAR(0); + int32 l = UCHARLENGTH(m); + + while( l>0 && m_isspace( m->data[ l-1 ] ) ) + l--; + + l = u_countChar32(m->data, l); + + PG_FREE_IF_COPY(m,0); + + PG_RETURN_INT32(l); +} + +PG_FUNCTION_INFO_V1(mvarchar_length); +Datum mvarchar_length(PG_FUNCTION_ARGS); + +Datum +mvarchar_length(PG_FUNCTION_ARGS) { + MVarChar *m = PG_GETARG_MVARCHAR(0); + int32 l = UVARCHARLENGTH(m); + + while( l>0 && m_isspace( m->data[ l-1 ] ) ) + l--; + + l = u_countChar32(m->data, l); + + PG_FREE_IF_COPY(m,0); + + PG_RETURN_INT32(l); +} + +static int32 +uchar_substring( + UChar *str, int32 strl, + int32 start, int32 length, bool length_not_specified, + UChar *dst) { + int32 S = start-1; /* start position */ + int32 S1; /* adjusted start position */ + int32 L1; /* adjusted substring length */ + int32 subbegin=0, subend=0; + + S1 = Max(S, 0); + if (length_not_specified) + L1 = -1; + else { + /* end position */ + int32 E = S + length; + + /* + * A negative value for L is the only way for the end position to + * be before the start. SQL99 says to throw an error. + */ + + if (E < S) + ereport(ERROR, + (errcode(ERRCODE_SUBSTRING_ERROR), + errmsg("negative substring length not allowed"))); + + /* + * A zero or negative value for the end position can happen if the + * start was negative or one. SQL99 says to return a zero-length + * string. + */ + if (E < 0) + return 0; + + L1 = E - S1; + } + + U16_FWD_N( str, subbegin, strl, S1 ); + if ( subbegin >= strl ) + return 0; + subend = subbegin; + U16_FWD_N( str, subend, strl, L1 ); + + memcpy( dst, str+subbegin, sizeof(UChar)*(subend-subbegin) ); + + return subend-subbegin; +} + +PG_FUNCTION_INFO_V1(mchar_substring); +Datum mchar_substring(PG_FUNCTION_ARGS); +Datum +mchar_substring(PG_FUNCTION_ARGS) { + MChar *src = PG_GETARG_MCHAR(0); + MChar *dst; + int32 length; + + dst = (MChar*)palloc( VARSIZE(src) ); + length = uchar_substring( + src->data, UCHARLENGTH(src), + PG_GETARG_INT32(1), PG_GETARG_INT32(2), false, + dst->data); + + dst->typmod = src->typmod; + SET_VARSIZE(dst, MCHARHDRSZ + length *sizeof(UChar)); + + PG_FREE_IF_COPY(src, 0); + PG_RETURN_MCHAR(dst); +} + +PG_FUNCTION_INFO_V1(mchar_substring_no_len); +Datum mchar_substring_no_len(PG_FUNCTION_ARGS); +Datum +mchar_substring_no_len(PG_FUNCTION_ARGS) { + MChar *src = PG_GETARG_MCHAR(0); + MChar *dst; + int32 length; + + dst = (MChar*)palloc( VARSIZE(src) ); + length = uchar_substring( + src->data, UCHARLENGTH(src), + PG_GETARG_INT32(1), -1, true, + dst->data); + + dst->typmod = src->typmod; + SET_VARSIZE(dst, MCHARHDRSZ + length *sizeof(UChar)); + + PG_FREE_IF_COPY(src, 0); + PG_RETURN_MCHAR(dst); +} + +PG_FUNCTION_INFO_V1(mvarchar_substring); +Datum mvarchar_substring(PG_FUNCTION_ARGS); +Datum +mvarchar_substring(PG_FUNCTION_ARGS) { + MVarChar *src = PG_GETARG_MVARCHAR(0); + MVarChar *dst; + int32 length; + + dst = (MVarChar*)palloc( VARSIZE(src) ); + length = uchar_substring( + src->data, UVARCHARLENGTH(src), + PG_GETARG_INT32(1), PG_GETARG_INT32(2), false, + dst->data); + + SET_VARSIZE(dst, MVARCHARHDRSZ + length *sizeof(UChar)); + + PG_FREE_IF_COPY(src, 0); + PG_RETURN_MVARCHAR(dst); +} + +PG_FUNCTION_INFO_V1(mvarchar_substring_no_len); +Datum mvarchar_substring_no_len(PG_FUNCTION_ARGS); +Datum +mvarchar_substring_no_len(PG_FUNCTION_ARGS) { + MVarChar *src = PG_GETARG_MVARCHAR(0); + MVarChar *dst; + int32 length; + + dst = (MVarChar*)palloc( VARSIZE(src) ); + length = uchar_substring( + src->data, UVARCHARLENGTH(src), + PG_GETARG_INT32(1), -1, true, + dst->data); + + SET_VARSIZE(dst, MVARCHARHDRSZ + length *sizeof(UChar)); + + PG_FREE_IF_COPY(src, 0); + PG_RETURN_MVARCHAR(dst); +} + +static Datum +hash_uchar( UChar *s, int len ) { + int32 length; + UErrorCode err = 0; + UChar *d; + Datum res; + + if ( len == 0 ) + return hash_any( NULL, 0 ); + + err = 0; + d = (UChar*) palloc( sizeof(UChar) * len * 2 ); + length = u_strFoldCase(d, len*2, s, len, U_FOLD_CASE_DEFAULT, &err); + + if ( U_FAILURE(err) ) + elog(ERROR,"ICU u_strFoldCase fails and returns %d (%s)", err, u_errorName(err)); + + res = hash_any( (unsigned char*) d, length * sizeof(UChar) ); + + pfree(d); + return res; +} + +PG_FUNCTION_INFO_V1(mvarchar_hash); +Datum +mvarchar_hash(PG_FUNCTION_ARGS) { + MVarChar *src = PG_GETARG_MVARCHAR(0); + Datum res; + + res = hash_uchar( src->data, lengthWithoutSpaceVarChar(src) ); + + PG_FREE_IF_COPY(src, 0); + PG_RETURN_DATUM( res ); +} + +PG_FUNCTION_INFO_V1(mchar_hash); +Datum +mchar_hash(PG_FUNCTION_ARGS) { + MChar *src = PG_GETARG_MCHAR(0); + Datum res; + + res = hash_uchar( src->data, lengthWithoutSpaceChar(src) ); + + PG_FREE_IF_COPY(src, 0); + PG_RETURN_DATUM( res ); +} + +PG_FUNCTION_INFO_V1(mchar_upper); +Datum mchar_upper(PG_FUNCTION_ARGS); +Datum +mchar_upper(PG_FUNCTION_ARGS) { + MChar *src = PG_GETARG_MCHAR(0); + MChar *dst = (MChar*)palloc( VARSIZE(src) * 2 ); + + dst->len = MCHARHDRSZ; + dst->typmod = src->typmod; + if ( UCHARLENGTH(src) != 0 ) { + int length; + UErrorCode err=0; + + length = u_strToUpper( dst->data, VARSIZE(src) * 2 - MCHARHDRSZ, + src->data, UCHARLENGTH(src), + NULL, &err ); + + Assert( length <= VARSIZE(src) * 2 - MCHARHDRSZ ); + + if ( U_FAILURE(err) ) + elog(ERROR,"ICU u_strToUpper fails and returns %d (%s)", err, u_errorName(err)); + + dst->len += sizeof(UChar) * length; + } + + SET_VARSIZE( dst, dst->len ); + PG_FREE_IF_COPY(src, 0); + PG_RETURN_MCHAR( dst ); +} + +PG_FUNCTION_INFO_V1(mchar_lower); +Datum mchar_lower(PG_FUNCTION_ARGS); +Datum +mchar_lower(PG_FUNCTION_ARGS) { + MChar *src = PG_GETARG_MCHAR(0); + MChar *dst = (MChar*)palloc( VARSIZE(src) * 2 ); + + dst->len = MCHARHDRSZ; + dst->typmod = src->typmod; + if ( UCHARLENGTH(src) != 0 ) { + int length; + UErrorCode err=0; + + length = u_strToLower( dst->data, VARSIZE(src) * 2 - MCHARHDRSZ, + src->data, UCHARLENGTH(src), + NULL, &err ); + + Assert( length <= VARSIZE(src) * 2 - MCHARHDRSZ ); + + if ( U_FAILURE(err) ) + elog(ERROR,"ICU u_strToLower fails and returns %d (%s)", err, u_errorName(err)); + + dst->len += sizeof(UChar) * length; + } + + SET_VARSIZE( dst, dst->len ); + PG_FREE_IF_COPY(src, 0); + PG_RETURN_MCHAR( dst ); +} + +PG_FUNCTION_INFO_V1(mvarchar_upper); +Datum mvarchar_upper(PG_FUNCTION_ARGS); +Datum +mvarchar_upper(PG_FUNCTION_ARGS) { + MVarChar *src = PG_GETARG_MVARCHAR(0); + MVarChar *dst = (MVarChar*)palloc( VARSIZE(src) * 2 ); + + dst->len = MVARCHARHDRSZ; + + if ( UVARCHARLENGTH(src) != 0 ) { + int length; + UErrorCode err=0; + + length = u_strToUpper( dst->data, VARSIZE(src) * 2 - MVARCHARHDRSZ, + src->data, UVARCHARLENGTH(src), + NULL, &err ); + + Assert( length <= VARSIZE(src) * 2 - MVARCHARHDRSZ ); + + if ( U_FAILURE(err) ) + elog(ERROR,"ICU u_strToUpper fails and returns %d (%s)", err, u_errorName(err)); + + dst->len += sizeof(UChar) * length; + } + + SET_VARSIZE( dst, dst->len ); + PG_FREE_IF_COPY(src, 0); + PG_RETURN_MVARCHAR( dst ); +} + +PG_FUNCTION_INFO_V1(mvarchar_lower); +Datum mvarchar_lower(PG_FUNCTION_ARGS); +Datum +mvarchar_lower(PG_FUNCTION_ARGS) { + MVarChar *src = PG_GETARG_MVARCHAR(0); + MVarChar *dst = (MVarChar*)palloc( VARSIZE(src) * 2 ); + + dst->len = MVARCHARHDRSZ; + + if ( UVARCHARLENGTH(src) != 0 ) { + int length; + UErrorCode err=0; + + length = u_strToLower( dst->data, VARSIZE(src) * 2 - MVARCHARHDRSZ, + src->data, UVARCHARLENGTH(src), + NULL, &err ); + + Assert( length <= VARSIZE(src) * 2 - MVARCHARHDRSZ ); + + if ( U_FAILURE(err) ) + elog(ERROR,"ICU u_strToLower fails and returns %d (%s)", err, u_errorName(err)); + + dst->len += sizeof(UChar) * length; + } + + SET_VARSIZE( dst, dst->len ); + PG_FREE_IF_COPY(src, 0); + PG_RETURN_MVARCHAR( dst ); +} + + diff --git a/contrib/mchar/mchar_recode.c b/contrib/mchar/mchar_recode.c new file mode 100644 index 0000000..d4f3659 --- /dev/null +++ b/contrib/mchar/mchar_recode.c @@ -0,0 +1,142 @@ +#include "mchar.h" + +#include "unicode/ucol.h" +#include "unicode/ucnv.h" + +static UConverter *cnvDB = NULL; +static UCollator *colCaseInsensitive = NULL; +static UCollator *colCaseSensitive = NULL; + +static void +createUObjs() { + if ( !cnvDB ) { + UErrorCode err = 0; + + if ( GetDatabaseEncoding() == PG_UTF8 ) + cnvDB = ucnv_open("UTF8", &err); + else + cnvDB = ucnv_open(NULL, &err); + if ( U_FAILURE(err) || cnvDB == NULL ) + elog(ERROR,"ICU ucnv_open returns %d (%s)", err, u_errorName(err)); + } + + if ( !colCaseInsensitive ) { + UErrorCode err = 0; + + colCaseInsensitive = ucol_open("", &err); + if ( U_FAILURE(err) || cnvDB == NULL ) { + if ( colCaseSensitive ) + ucol_close( colCaseSensitive ); + colCaseSensitive = NULL; + elog(ERROR,"ICU ucol_open returns %d (%s)", err, u_errorName(err)); + } + + ucol_setStrength( colCaseInsensitive, UCOL_SECONDARY ); + } + + if ( !colCaseSensitive ) { + UErrorCode err = 0; + + colCaseSensitive = ucol_open("", &err); + if ( U_FAILURE(err) || cnvDB == NULL ) { + if ( colCaseSensitive ) + ucol_close( colCaseSensitive ); + colCaseSensitive = NULL; + elog(ERROR,"ICU ucol_open returns %d (%s)", err, u_errorName(err)); + } + + ucol_setAttribute(colCaseSensitive, UCOL_CASE_FIRST, UCOL_UPPER_FIRST, &err); + if (U_FAILURE(err)) { + if ( colCaseSensitive ) + ucol_close( colCaseSensitive ); + colCaseSensitive = NULL; + elog(ERROR,"ICU ucol_setAttribute returns %d (%s)", err, u_errorName(err)); + } + } +} + +int +Char2UChar(const char * src, int srclen, UChar *dst) { + int dstlen=0; + UErrorCode err = 0; + + createUObjs(); + dstlen = ucnv_toUChars( cnvDB, dst, srclen*4, src, srclen, &err ); + if ( U_FAILURE(err)) + elog(ERROR,"ICU ucnv_toUChars returns %d (%s)", err, u_errorName(err)); + + return dstlen; +} + +int +UChar2Char(const UChar * src, int srclen, char *dst) { + int dstlen=0; + UErrorCode err = 0; + + createUObjs(); + dstlen = ucnv_fromUChars( cnvDB, dst, srclen*4, src, srclen, &err ); + if ( U_FAILURE(err) ) + elog(ERROR,"ICU ucnv_fromUChars returns %d (%s)", err, u_errorName(err)); + + return dstlen; +} + +int +UChar2Wchar(UChar * src, int srclen, pg_wchar *dst) { + int dstlen=0; + char *utf = palloc(sizeof(char)*srclen*4); + + dstlen = UChar2Char(src, srclen, utf); + dstlen = pg_mb2wchar_with_len( utf, dst, dstlen ); + pfree(utf); + + return dstlen; +} + +static UChar UCharWhiteSpace = 0; + +void +FillWhiteSpace( UChar *dst, int n ) { + if ( UCharWhiteSpace == 0 ) { + int len; + UErrorCode err = 0; + + u_strFromUTF8( &UCharWhiteSpace, 1, &len, " ", 1, &err); + + Assert( len==1 ); + Assert( !U_FAILURE(err) ); + } + + while( n-- > 0 ) + *dst++ = UCharWhiteSpace; +} + +int +UCharCaseCompare(UChar * a, int alen, UChar *b, int blen) { + int len = Min(alen, blen); + int res; + + createUObjs(); + + res = (int)ucol_strcoll( colCaseInsensitive, + a, len, + b, len); + if ( res == 0 && alen != blen ) + return (alen > blen) ? 1 : - 1; + return res; +} + +int +UCharCompare(UChar * a, int alen, UChar *b, int blen) { + int len = Min(alen, blen); + int res; + + createUObjs(); + + res = (int)ucol_strcoll( colCaseSensitive, + a, len, + b, len); + if ( res == 0 && alen != blen ) + return (alen > blen) ? 1 : - 1; + return res; +} diff --git a/contrib/mchar/sql/compat.sql b/contrib/mchar/sql/compat.sql new file mode 100644 index 0000000..d5b6a98 --- /dev/null +++ b/contrib/mchar/sql/compat.sql @@ -0,0 +1,11 @@ +--- table based checks + +select '<' || ch || '>', '<' || vch || '>' from chvch; +select * from chvch where vch = 'One space'; +select * from chvch where vch = 'One space '; + +select * from ch where chcol = 'abcd' order by chcol; +select * from ch t1 join ch t2 on t1.chcol = t2.chcol order by t1.chcol, t2.chcol; +select * from ch where chcol > 'abcd' and chcol<'ee'; +select * from ch order by chcol; + diff --git a/contrib/mchar/sql/init.sql b/contrib/mchar/sql/init.sql new file mode 100644 index 0000000..a303feb --- /dev/null +++ b/contrib/mchar/sql/init.sql @@ -0,0 +1,33 @@ + +-- +-- first, define the datatype. Turn off echoing so that expected file +-- does not depend on contents of mchar.sql. +-- + +\set ECHO none +\i mchar.sql +--- load for table based checks +SET search_path = public; +\set ECHO all + +create table ch ( + chcol mchar(32) +) without oids; + +insert into ch values('abcd'); +insert into ch values('AbcD'); +insert into ch values('abcz'); +insert into ch values('defg'); +insert into ch values('dEfg'); +insert into ch values('ee'); +insert into ch values('Ee'); + +create table chvch ( + ch mchar(12), + vch mvarchar(12) +) without oids; + +insert into chvch values('No spaces', 'No spaces'); +insert into chvch values('One space ', 'One space '); +insert into chvch values('1 space', '1 space '); + diff --git a/contrib/mchar/sql/like.sql b/contrib/mchar/sql/like.sql new file mode 100644 index 0000000..aebf924 --- /dev/null +++ b/contrib/mchar/sql/like.sql @@ -0,0 +1,216 @@ +-- simplest examples +-- E061-04 like predicate +SELECT 'hawkeye'::mchar LIKE 'h%' AS "true"; +SELECT 'hawkeye'::mchar NOT LIKE 'h%' AS "false"; + +SELECT 'hawkeye'::mchar LIKE 'H%' AS "true"; +SELECT 'hawkeye'::mchar NOT LIKE 'H%' AS "false"; + +SELECT 'hawkeye'::mchar LIKE 'indio%' AS "false"; +SELECT 'hawkeye'::mchar NOT LIKE 'indio%' AS "true"; + +SELECT 'hawkeye'::mchar LIKE 'h%eye' AS "true"; +SELECT 'hawkeye'::mchar NOT LIKE 'h%eye' AS "false"; + +SELECT 'indio'::mchar LIKE '_ndio' AS "true"; +SELECT 'indio'::mchar NOT LIKE '_ndio' AS "false"; + +SELECT 'indio'::mchar LIKE 'in__o' AS "true"; +SELECT 'indio'::mchar NOT LIKE 'in__o' AS "false"; + +SELECT 'indio'::mchar LIKE 'in_o' AS "false"; +SELECT 'indio'::mchar NOT LIKE 'in_o' AS "true"; + +SELECT 'hawkeye'::mvarchar LIKE 'h%' AS "true"; +SELECT 'hawkeye'::mvarchar NOT LIKE 'h%' AS "false"; + +SELECT 'hawkeye'::mvarchar LIKE 'H%' AS "true"; +SELECT 'hawkeye'::mvarchar NOT LIKE 'H%' AS "false"; + +SELECT 'hawkeye'::mvarchar LIKE 'indio%' AS "false"; +SELECT 'hawkeye'::mvarchar NOT LIKE 'indio%' AS "true"; + +SELECT 'hawkeye'::mvarchar LIKE 'h%eye' AS "true"; +SELECT 'hawkeye'::mvarchar NOT LIKE 'h%eye' AS "false"; + +SELECT 'indio'::mvarchar LIKE '_ndio' AS "true"; +SELECT 'indio'::mvarchar NOT LIKE '_ndio' AS "false"; + +SELECT 'indio'::mvarchar LIKE 'in__o' AS "true"; +SELECT 'indio'::mvarchar NOT LIKE 'in__o' AS "false"; + +SELECT 'indio'::mvarchar LIKE 'in_o' AS "false"; +SELECT 'indio'::mvarchar NOT LIKE 'in_o' AS "true"; + +-- unused escape character +SELECT 'hawkeye'::mchar LIKE 'h%'::mchar ESCAPE '#' AS "true"; +SELECT 'hawkeye'::mchar NOT LIKE 'h%'::mchar ESCAPE '#' AS "false"; + +SELECT 'indio'::mchar LIKE 'ind_o'::mchar ESCAPE '$' AS "true"; +SELECT 'indio'::mchar NOT LIKE 'ind_o'::mchar ESCAPE '$' AS "false"; + +-- escape character +-- E061-05 like predicate with escape clause +SELECT 'h%'::mchar LIKE 'h#%'::mchar ESCAPE '#' AS "true"; +SELECT 'h%'::mchar NOT LIKE 'h#%'::mchar ESCAPE '#' AS "false"; + +SELECT 'h%wkeye'::mchar LIKE 'h#%'::mchar ESCAPE '#' AS "false"; +SELECT 'h%wkeye'::mchar NOT LIKE 'h#%'::mchar ESCAPE '#' AS "true"; + +SELECT 'h%wkeye'::mchar LIKE 'h#%%'::mchar ESCAPE '#' AS "true"; +SELECT 'h%wkeye'::mchar NOT LIKE 'h#%%'::mchar ESCAPE '#' AS "false"; + +SELECT 'h%awkeye'::mchar LIKE 'h#%a%k%e'::mchar ESCAPE '#' AS "true"; +SELECT 'h%awkeye'::mchar NOT LIKE 'h#%a%k%e'::mchar ESCAPE '#' AS "false"; + +SELECT 'indio'::mchar LIKE '_ndio'::mchar ESCAPE '$' AS "true"; +SELECT 'indio'::mchar NOT LIKE '_ndio'::mchar ESCAPE '$' AS "false"; + +SELECT 'i_dio'::mchar LIKE 'i$_d_o'::mchar ESCAPE '$' AS "true"; +SELECT 'i_dio'::mchar NOT LIKE 'i$_d_o'::mchar ESCAPE '$' AS "false"; + +SELECT 'i_dio'::mchar LIKE 'i$_nd_o'::mchar ESCAPE '$' AS "false"; +SELECT 'i_dio'::mchar NOT LIKE 'i$_nd_o'::mchar ESCAPE '$' AS "true"; + +SELECT 'i_dio'::mchar LIKE 'i$_d%o'::mchar ESCAPE '$' AS "true"; +SELECT 'i_dio'::mchar NOT LIKE 'i$_d%o'::mchar ESCAPE '$' AS "false"; + +-- escape character same as pattern character +SELECT 'maca'::mchar LIKE 'm%aca' ESCAPE '%'::mchar AS "true"; +SELECT 'maca'::mchar NOT LIKE 'm%aca' ESCAPE '%'::mchar AS "false"; + +SELECT 'ma%a'::mchar LIKE 'm%a%%a' ESCAPE '%'::mchar AS "true"; +SELECT 'ma%a'::mchar NOT LIKE 'm%a%%a' ESCAPE '%'::mchar AS "false"; + +SELECT 'bear'::mchar LIKE 'b_ear' ESCAPE '_'::mchar AS "true"; +SELECT 'bear'::mchar NOT LIKE 'b_ear'::mchar ESCAPE '_' AS "false"; + +SELECT 'be_r'::mchar LIKE 'b_e__r' ESCAPE '_'::mchar AS "true"; +SELECT 'be_r'::mchar NOT LIKE 'b_e__r' ESCAPE '_'::mchar AS "false"; + +SELECT 'be_r'::mchar LIKE '__e__r' ESCAPE '_'::mchar AS "false"; +SELECT 'be_r'::mchar NOT LIKE '__e__r'::mchar ESCAPE '_' AS "true"; + +-- unused escape character +SELECT 'hawkeye'::mvarchar LIKE 'h%'::mvarchar ESCAPE '#' AS "true"; +SELECT 'hawkeye'::mvarchar NOT LIKE 'h%'::mvarchar ESCAPE '#' AS "false"; + +SELECT 'indio'::mvarchar LIKE 'ind_o'::mvarchar ESCAPE '$' AS "true"; +SELECT 'indio'::mvarchar NOT LIKE 'ind_o'::mvarchar ESCAPE '$' AS "false"; + +-- escape character +-- E061-05 like predicate with escape clause +SELECT 'h%'::mvarchar LIKE 'h#%'::mvarchar ESCAPE '#' AS "true"; +SELECT 'h%'::mvarchar NOT LIKE 'h#%'::mvarchar ESCAPE '#' AS "false"; + +SELECT 'h%wkeye'::mvarchar LIKE 'h#%'::mvarchar ESCAPE '#' AS "false"; +SELECT 'h%wkeye'::mvarchar NOT LIKE 'h#%'::mvarchar ESCAPE '#' AS "true"; + +SELECT 'h%wkeye'::mvarchar LIKE 'h#%%'::mvarchar ESCAPE '#' AS "true"; +SELECT 'h%wkeye'::mvarchar NOT LIKE 'h#%%'::mvarchar ESCAPE '#' AS "false"; + +SELECT 'h%awkeye'::mvarchar LIKE 'h#%a%k%e'::mvarchar ESCAPE '#' AS "true"; +SELECT 'h%awkeye'::mvarchar NOT LIKE 'h#%a%k%e'::mvarchar ESCAPE '#' AS "false"; + +SELECT 'indio'::mvarchar LIKE '_ndio'::mvarchar ESCAPE '$' AS "true"; +SELECT 'indio'::mvarchar NOT LIKE '_ndio'::mvarchar ESCAPE '$' AS "false"; + +SELECT 'i_dio'::mvarchar LIKE 'i$_d_o'::mvarchar ESCAPE '$' AS "true"; +SELECT 'i_dio'::mvarchar NOT LIKE 'i$_d_o'::mvarchar ESCAPE '$' AS "false"; + +SELECT 'i_dio'::mvarchar LIKE 'i$_nd_o'::mvarchar ESCAPE '$' AS "false"; +SELECT 'i_dio'::mvarchar NOT LIKE 'i$_nd_o'::mvarchar ESCAPE '$' AS "true"; + +SELECT 'i_dio'::mvarchar LIKE 'i$_d%o'::mvarchar ESCAPE '$' AS "true"; +SELECT 'i_dio'::mvarchar NOT LIKE 'i$_d%o'::mvarchar ESCAPE '$' AS "false"; + +-- escape character same as pattern character +SELECT 'maca'::mvarchar LIKE 'm%aca' ESCAPE '%'::mvarchar AS "true"; +SELECT 'maca'::mvarchar NOT LIKE 'm%aca' ESCAPE '%'::mvarchar AS "false"; + +SELECT 'ma%a'::mvarchar LIKE 'm%a%%a' ESCAPE '%'::mvarchar AS "true"; +SELECT 'ma%a'::mvarchar NOT LIKE 'm%a%%a' ESCAPE '%'::mvarchar AS "false"; + +SELECT 'bear'::mvarchar LIKE 'b_ear' ESCAPE '_'::mvarchar AS "true"; +SELECT 'bear'::mvarchar NOT LIKE 'b_ear'::mvarchar ESCAPE '_' AS "false"; + +SELECT 'be_r'::mvarchar LIKE 'b_e__r' ESCAPE '_'::mvarchar AS "true"; +SELECT 'be_r'::mvarchar NOT LIKE 'b_e__r' ESCAPE '_'::mvarchar AS "false"; + +SELECT 'be_r'::mvarchar LIKE '__e__r' ESCAPE '_'::mvarchar AS "false"; +SELECT 'be_r'::mvarchar NOT LIKE '__e__r'::mvarchar ESCAPE '_' AS "true"; + +-- similar to + +SELECT 'abc'::mchar SIMILAR TO 'abc'::mchar AS "true"; +SELECT 'abc'::mchar SIMILAR TO 'a'::mchar AS "false"; +SELECT 'abc'::mchar SIMILAR TO '%(b|d)%'::mchar AS "true"; +SELECT 'abc'::mchar SIMILAR TO '(b|c)%'::mchar AS "false"; +SELECT 'h%'::mchar SIMILAR TO 'h#%'::mchar AS "false"; +SELECT 'h%'::mchar SIMILAR TO 'h#%'::mchar ESCAPE '#' AS "true"; + +SELECT 'abc'::mvarchar SIMILAR TO 'abc'::mvarchar AS "true"; +SELECT 'abc'::mvarchar SIMILAR TO 'a'::mvarchar AS "false"; +SELECT 'abc'::mvarchar SIMILAR TO '%(b|d)%'::mvarchar AS "true"; +SELECT 'abc'::mvarchar SIMILAR TO '(b|c)%'::mvarchar AS "false"; +SELECT 'h%'::mvarchar SIMILAR TO 'h#%'::mvarchar AS "false"; +SELECT 'h%'::mvarchar SIMILAR TO 'h#%'::mvarchar ESCAPE '#' AS "true"; + +-- index support + +SELECT * from ch where chcol like 'aB_d' order by chcol using &<; +SELECT * from ch where chcol like 'aB%d' order by chcol using &<; +SELECT * from ch where chcol like 'aB%' order by chcol using &<; +SELECT * from ch where chcol like '%BC%' order by chcol using &<; +set enable_seqscan = off; +SELECT * from ch where chcol like 'aB_d' order by chcol using &<; +SELECT * from ch where chcol like 'aB%d' order by chcol using &<; +SELECT * from ch where chcol like 'aB%' order by chcol using &<; +SELECT * from ch where chcol like '%BC%' order by chcol using &<; +set enable_seqscan = on; + + +create table testt (f1 mchar(10)); +insert into testt values ('Abc-000001'); +insert into testt values ('Abc-000002'); +insert into testt values ('0000000001'); +insert into testt values ('0000000002'); + +select f1 from testt where f1::mvarchar like E'Abc\\-%'::mvarchar; +select * from testt where f1::mchar like E'Abc\\-%'::mchar; +create index testindex on testt(f1); +set enable_seqscan=off; +select f1 from testt where f1::mvarchar like E'Abc\\-%'::mvarchar; +select * from testt where f1::mchar like E'Abc\\-%'::mchar; +set enable_seqscan = on; +drop table testt; + +create table testt (f1 mvarchar(10)); +insert into testt values ('Abc-000001'); +insert into testt values ('Abc-000002'); +insert into testt values ('0000000001'); +insert into testt values ('0000000002'); + +select f1 from testt where f1::mvarchar like E'Abc\\-%'::mvarchar; +select * from testt where f1::mchar like E'Abc\\-%'::mchar; +select * from testt where f1::mchar like E'Abc\\- %'::mchar; +select * from testt where f1::mchar like E' %'::mchar; +create index testindex on testt(f1); +set enable_seqscan=off; +select f1 from testt where f1::mvarchar like E'Abc\\-%'::mvarchar; +select * from testt where f1::mchar like E'Abc\\-%'::mchar; +select * from testt where f1::mchar like E'Abc\\- %'::mchar; +select * from testt where f1::mchar like E' %'::mchar; +set enable_seqscan = on; +drop table testt; + + +CREATE TABLE test ( code mchar(5) NOT NULL ); +insert into test values('1111 '); +insert into test values('111 '); +insert into test values('11 '); +insert into test values('1 '); + +SELECT * FROM test WHERE code LIKE ('% '); + + diff --git a/contrib/mchar/sql/mchar.sql b/contrib/mchar/sql/mchar.sql new file mode 100644 index 0000000..640f166 --- /dev/null +++ b/contrib/mchar/sql/mchar.sql @@ -0,0 +1,81 @@ +-- I/O tests + +select '1'::mchar; +select '2 '::mchar; +select '10 '::mchar; + +select '1'::mchar(2); +select '2 '::mchar(2); +select '3 '::mchar(2); +select '10 '::mchar(2); + +select ' '::mchar(10); +select ' '::mchar; + +-- operations & functions + +select length('1'::mchar); +select length('2 '::mchar); +select length('10 '::mchar); + +select length('1'::mchar(2)); +select length('2 '::mchar(2)); +select length('3 '::mchar(2)); +select length('10 '::mchar(2)); + +select length(' '::mchar(10)); +select length(' '::mchar); + +select 'asd'::mchar(10) || '>'::mchar(10); +select length('asd'::mchar(10) || '>'::mchar(10)); +select 'asd'::mchar(2) || '>'::mchar(10); +select length('asd'::mchar(2) || '>'::mchar(10)); + +-- Comparisons + +select 'asdf'::mchar = 'aSdf'::mchar; +select 'asdf'::mchar = 'aSdf '::mchar; +select 'asdf'::mchar = 'aSdf 1'::mchar(4); +select 'asdf'::mchar = 'aSdf 1'::mchar(5); +select 'asdf'::mchar = 'aSdf 1'::mchar(6); +select 'asdf'::mchar(3) = 'aSdf 1'::mchar(5); +select 'asdf'::mchar(3) = 'aSdf 1'::mchar(3); + +select 'asdf'::mchar < 'aSdf'::mchar; +select 'asdf'::mchar < 'aSdf '::mchar; +select 'asdf'::mchar < 'aSdf 1'::mchar(4); +select 'asdf'::mchar < 'aSdf 1'::mchar(5); +select 'asdf'::mchar < 'aSdf 1'::mchar(6); + +select 'asdf'::mchar <= 'aSdf'::mchar; +select 'asdf'::mchar <= 'aSdf '::mchar; +select 'asdf'::mchar <= 'aSdf 1'::mchar(4); +select 'asdf'::mchar <= 'aSdf 1'::mchar(5); +select 'asdf'::mchar <= 'aSdf 1'::mchar(6); + +select 'asdf'::mchar >= 'aSdf'::mchar; +select 'asdf'::mchar >= 'aSdf '::mchar; +select 'asdf'::mchar >= 'aSdf 1'::mchar(4); +select 'asdf'::mchar >= 'aSdf 1'::mchar(5); +select 'asdf'::mchar >= 'aSdf 1'::mchar(6); + +select 'asdf'::mchar > 'aSdf'::mchar; +select 'asdf'::mchar > 'aSdf '::mchar; +select 'asdf'::mchar > 'aSdf 1'::mchar(4); +select 'asdf'::mchar > 'aSdf 1'::mchar(5); +select 'asdf'::mchar > 'aSdf 1'::mchar(6); + +select max(ch) from chvch; +select min(ch) from chvch; + +select substr('1234567890'::mchar, 3) = '34567890' as "34567890"; +select substr('1234567890'::mchar, 4, 3) = '456' as "456"; + +select lower('asdfASDF'::mchar); +select upper('asdfASDF'::mchar); + +select 'asd'::mchar == 'aSd'::mchar; +select 'asd'::mchar == 'aCd'::mchar; +select 'asd'::mchar == NULL; +select NULL == 'aCd'::mchar; +select NULL::mchar == NULL; diff --git a/contrib/mchar/sql/mm.sql b/contrib/mchar/sql/mm.sql new file mode 100644 index 0000000..c16aaa1 --- /dev/null +++ b/contrib/mchar/sql/mm.sql @@ -0,0 +1,185 @@ +select 'asd'::mchar::mvarchar; +select 'asd '::mchar::mvarchar; +select 'asd'::mchar(2)::mvarchar; +select 'asd '::mchar(2)::mvarchar; +select 'asd'::mchar(5)::mvarchar; +select 'asd '::mchar(5)::mvarchar; +select 'asd'::mchar::mvarchar(2); +select 'asd '::mchar::mvarchar(2); +select 'asd'::mchar(2)::mvarchar(2); +select 'asd '::mchar(2)::mvarchar(2); +select 'asd'::mchar(5)::mvarchar(2); +select 'asd '::mchar(5)::mvarchar(2); +select 'asd'::mchar::mvarchar(5); +select 'asd '::mchar::mvarchar(5); +select 'asd'::mchar(2)::mvarchar(5); +select 'asd '::mchar(2)::mvarchar(5); +select 'asd'::mchar(5)::mvarchar(5); +select 'asd '::mchar(5)::mvarchar(5); + +select 'asd'::mvarchar::mchar; +select 'asd '::mvarchar::mchar; +select 'asd'::mvarchar(2)::mchar; +select 'asd '::mvarchar(2)::mchar; +select 'asd'::mvarchar(5)::mchar; +select 'asd '::mvarchar(5)::mchar; +select 'asd'::mvarchar::mchar(2); +select 'asd '::mvarchar::mchar(2); +select 'asd'::mvarchar(2)::mchar(2); +select 'asd '::mvarchar(2)::mchar(2); +select 'asd'::mvarchar(5)::mchar(2); +select 'asd '::mvarchar(5)::mchar(2); +select 'asd'::mvarchar::mchar(5); +select 'asd '::mvarchar::mchar(5); +select 'asd'::mvarchar(2)::mchar(5); +select 'asd '::mvarchar(2)::mchar(5); +select 'asd'::mvarchar(5)::mchar(5); +select 'asd '::mvarchar(5)::mchar(5); + +select 'asd'::mchar || '123'; +select 'asd'::mchar || '123'::mchar; +select 'asd'::mchar || '123'::mvarchar; + +select 'asd '::mchar || '123'; +select 'asd '::mchar || '123'::mchar; +select 'asd '::mchar || '123'::mvarchar; + +select 'asd '::mchar || '123 '; +select 'asd '::mchar || '123 '::mchar; +select 'asd '::mchar || '123 '::mvarchar; + + +select 'asd'::mvarchar || '123'; +select 'asd'::mvarchar || '123'::mchar; +select 'asd'::mvarchar || '123'::mvarchar; + +select 'asd '::mvarchar || '123'; +select 'asd '::mvarchar || '123'::mchar; +select 'asd '::mvarchar || '123'::mvarchar; + +select 'asd '::mvarchar || '123 '; +select 'asd '::mvarchar || '123 '::mchar; +select 'asd '::mvarchar || '123 '::mvarchar; + + +select 'asd'::mchar(2) || '123'; +select 'asd'::mchar(2) || '123'::mchar; +select 'asd'::mchar(2) || '123'::mvarchar; + + +select 'asd '::mchar(2) || '123'; +select 'asd '::mchar(2) || '123'::mchar; +select 'asd '::mchar(2) || '123'::mvarchar; + + +select 'asd '::mchar(2) || '123 '; +select 'asd '::mchar(2) || '123 '::mchar; +select 'asd '::mchar(2) || '123 '::mvarchar; + +select 'asd'::mvarchar(2) || '123'; +select 'asd'::mvarchar(2) || '123'::mchar; +select 'asd'::mvarchar(2) || '123'::mvarchar; + +select 'asd '::mvarchar(2) || '123'; +select 'asd '::mvarchar(2) || '123'::mchar; +select 'asd '::mvarchar(2) || '123'::mvarchar; + +select 'asd '::mvarchar(2) || '123 '; +select 'asd '::mvarchar(2) || '123 '::mchar; +select 'asd '::mvarchar(2) || '123 '::mvarchar; + +select 'asd'::mchar(4) || '143'; +select 'asd'::mchar(4) || '123'::mchar; +select 'asd'::mchar(4) || '123'::mvarchar; + +select 'asd '::mchar(4) || '123'; +select 'asd '::mchar(4) || '123'::mchar; +select 'asd '::mchar(4) || '123'::mvarchar; + +select 'asd '::mchar(4) || '123 '; +select 'asd '::mchar(4) || '123 '::mchar; +select 'asd '::mchar(4) || '123 '::mvarchar; + +select 'asd'::mvarchar(4) || '123'; +select 'asd'::mvarchar(4) || '123'::mchar; +select 'asd'::mvarchar(4) || '123'::mvarchar; + +select 'asd '::mvarchar(4) || '123'; +select 'asd '::mvarchar(4) || '123'::mchar; +select 'asd '::mvarchar(4) || '123'::mvarchar; + +select 'asd '::mvarchar(4) || '123 '; +select 'asd '::mvarchar(4) || '123 '::mchar; +select 'asd '::mvarchar(4) || '123 '::mvarchar; + + +select 'asd '::mvarchar(4) || '123 '::mchar(4); +select 'asd '::mvarchar(4) || '123 '::mvarchar(4); +select 'asd '::mvarchar(4) || '123'::mchar(4); +select 'asd '::mvarchar(4) || '123'::mvarchar(4); + + +select 1 where 'f'::mchar='F'::mvarchar; +select 1 where 'f'::mchar='F '::mvarchar; +select 1 where 'f '::mchar='F'::mvarchar; +select 1 where 'f '::mchar='F '::mvarchar; + +select 1 where 'f'::mchar='F'::mvarchar(2); +select 1 where 'f'::mchar='F '::mvarchar(2); +select 1 where 'f '::mchar='F'::mvarchar(2); +select 1 where 'f '::mchar='F '::mvarchar(2); + +select 1 where 'f'::mchar(2)='F'::mvarchar; +select 1 where 'f'::mchar(2)='F '::mvarchar; +select 1 where 'f '::mchar(2)='F'::mvarchar; +select 1 where 'f '::mchar(2)='F '::mvarchar; + +select 1 where 'f'::mchar(2)='F'::mvarchar(2); +select 1 where 'f'::mchar(2)='F '::mvarchar(2); +select 1 where 'f '::mchar(2)='F'::mvarchar(2); +select 1 where 'f '::mchar(2)='F '::mvarchar(2); + +select 1 where 'foo'::mchar='FOO'::mvarchar; +select 1 where 'foo'::mchar='FOO '::mvarchar; +select 1 where 'foo '::mchar='FOO'::mvarchar; +select 1 where 'foo '::mchar='FOO '::mvarchar; + +select 1 where 'foo'::mchar='FOO'::mvarchar(2); +select 1 where 'foo'::mchar='FOO '::mvarchar(2); +select 1 where 'foo '::mchar='FOO'::mvarchar(2); +select 1 where 'foo '::mchar='FOO '::mvarchar(2); + +select 1 where 'foo'::mchar(2)='FOO'::mvarchar; +select 1 where 'foo'::mchar(2)='FOO '::mvarchar; +select 1 where 'foo '::mchar(2)='FOO'::mvarchar; +select 1 where 'foo '::mchar(2)='FOO '::mvarchar; + +select 1 where 'foo'::mchar(2)='FOO'::mvarchar(2); +select 1 where 'foo'::mchar(2)='FOO '::mvarchar(2); +select 1 where 'foo '::mchar(2)='FOO'::mvarchar(2); +select 1 where 'foo '::mchar(2)='FOO '::mvarchar(2); + +Select 'f'::mchar(1) Union Select 'o'::mvarchar(1); +Select 'f'::mvarchar(1) Union Select 'o'::mchar(1); + +select * from chvch where ch=vch; + +select ch.* from ch, (select 'dEfg'::mvarchar as q) as p where chcol > p.q; +create index qq on ch (chcol); +set enable_seqscan=off; +select ch.* from ch, (select 'dEfg'::mvarchar as q) as p where chcol > p.q; +set enable_seqscan=on; + + +--\copy chvch to 'results/chvch.dump' binary +--truncate table chvch; +--\copy chvch from 'results/chvch.dump' binary + +--test joins +CREATE TABLE a (mchar2 MCHAR(2) NOT NULL); +CREATE TABLE c (mvarchar255 mvarchar NOT NULL); +SELECT * FROM a, c WHERE mchar2 = mvarchar255; +SELECT * FROM a, c WHERE mvarchar255 = mchar2; +DROP TABLE a; +DROP TABLE c; + diff --git a/contrib/mchar/sql/mvarchar.sql b/contrib/mchar/sql/mvarchar.sql new file mode 100644 index 0000000..91b0981 --- /dev/null +++ b/contrib/mchar/sql/mvarchar.sql @@ -0,0 +1,82 @@ +-- I/O tests + +select '1'::mvarchar; +select '2 '::mvarchar; +select '10 '::mvarchar; + +select '1'::mvarchar(2); +select '2 '::mvarchar(2); +select '3 '::mvarchar(2); +select '10 '::mvarchar(2); + +select ' '::mvarchar(10); +select ' '::mvarchar; + +-- operations & functions + +select length('1'::mvarchar); +select length('2 '::mvarchar); +select length('10 '::mvarchar); + +select length('1'::mvarchar(2)); +select length('2 '::mvarchar(2)); +select length('3 '::mvarchar(2)); +select length('10 '::mvarchar(2)); + +select length(' '::mvarchar(10)); +select length(' '::mvarchar); + +select 'asd'::mvarchar(10) || '>'::mvarchar(10); +select length('asd'::mvarchar(10) || '>'::mvarchar(10)); +select 'asd'::mvarchar(2) || '>'::mvarchar(10); +select length('asd'::mvarchar(2) || '>'::mvarchar(10)); + +-- Comparisons + +select 'asdf'::mvarchar = 'aSdf'::mvarchar; +select 'asdf'::mvarchar = 'aSdf '::mvarchar; +select 'asdf'::mvarchar = 'aSdf 1'::mvarchar(4); +select 'asdf'::mvarchar = 'aSdf 1'::mvarchar(5); +select 'asdf'::mvarchar = 'aSdf 1'::mvarchar(6); +select 'asdf'::mvarchar(3) = 'aSdf 1'::mvarchar(5); +select 'asdf'::mvarchar(3) = 'aSdf 1'::mvarchar(3); + +select 'asdf'::mvarchar < 'aSdf'::mvarchar; +select 'asdf'::mvarchar < 'aSdf '::mvarchar; +select 'asdf'::mvarchar < 'aSdf 1'::mvarchar(4); +select 'asdf'::mvarchar < 'aSdf 1'::mvarchar(5); +select 'asdf'::mvarchar < 'aSdf 1'::mvarchar(6); + +select 'asdf'::mvarchar <= 'aSdf'::mvarchar; +select 'asdf'::mvarchar <= 'aSdf '::mvarchar; +select 'asdf'::mvarchar <= 'aSdf 1'::mvarchar(4); +select 'asdf'::mvarchar <= 'aSdf 1'::mvarchar(5); +select 'asdf'::mvarchar <= 'aSdf 1'::mvarchar(6); + +select 'asdf'::mvarchar >= 'aSdf'::mvarchar; +select 'asdf'::mvarchar >= 'aSdf '::mvarchar; +select 'asdf'::mvarchar >= 'aSdf 1'::mvarchar(4); +select 'asdf'::mvarchar >= 'aSdf 1'::mvarchar(5); +select 'asdf'::mvarchar >= 'aSdf 1'::mvarchar(6); + +select 'asdf'::mvarchar > 'aSdf'::mvarchar; +select 'asdf'::mvarchar > 'aSdf '::mvarchar; +select 'asdf'::mvarchar > 'aSdf 1'::mvarchar(4); +select 'asdf'::mvarchar > 'aSdf 1'::mvarchar(5); +select 'asdf'::mvarchar > 'aSdf 1'::mvarchar(6); + +select max(vch) from chvch; +select min(vch) from chvch; + +select substr('1234567890'::mvarchar, 3) = '34567890' as "34567890"; +select substr('1234567890'::mvarchar, 4, 3) = '456' as "456"; + +select lower('asdfASDF'::mvarchar); +select upper('asdfASDF'::mvarchar); + +select 'asd'::mvarchar == 'aSd'::mvarchar; +select 'asd'::mvarchar == 'aCd'::mvarchar; +select 'asd'::mvarchar == NULL; +select NULL == 'aCd'::mvarchar; +select NULL::mvarchar == NULL; + diff --git a/contrib/mchar/uninstall_mchar.sql b/contrib/mchar/uninstall_mchar.sql new file mode 100644 index 0000000..59f61e2 --- /dev/null +++ b/contrib/mchar/uninstall_mchar.sql @@ -0,0 +1,9 @@ +SET search_path = public; +BEGIN; + +DROP FUNCTION mchar_pattern_fixed_prefix(internal, internal, internal); +DROP FUNCTION mchar_greaterstring(internal); +DROP TYPE MCHAR CASCADE; +DROP TYPE MVARCHAR CASCADE; + +COMMIT; diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index f6e2114..19ad0eb 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -1585,6 +1585,7 @@ _outAppendPath(StringInfo str, const AppendPath *node) _outPathInfo(str, (const Path *) node); WRITE_NODE_FIELD(subpaths); + WRITE_BOOL_FIELD(pull_tlist); } static void diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index bfd3809..a7cab3b 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -396,6 +396,9 @@ set_plain_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte) /* Consider index scans */ create_index_paths(root, rel); + /* Consider index scans with rewrited quals */ + keybased_rewrite_index_paths(root, rel); + /* Consider TID scans */ create_tidscan_paths(root, rel); @@ -799,7 +802,7 @@ set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, * if we have zero or one live subpath due to constraint exclusion.) */ if (subpaths_valid) - add_path(rel, (Path *) create_append_path(rel, subpaths, NULL)); + add_path(rel, (Path *) create_append_path(rel, subpaths, NULL, false, NIL)); /* * Also build unparameterized MergeAppend paths based on the collected @@ -849,7 +852,7 @@ set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, if (subpaths_valid) add_path(rel, (Path *) - create_append_path(rel, subpaths, required_outer)); + create_append_path(rel, subpaths, required_outer, false, NIL)); } /* Select cheapest paths */ @@ -1067,7 +1070,7 @@ set_dummy_rel_pathlist(RelOptInfo *rel) /* Discard any pre-existing paths; no further need for them */ rel->pathlist = NIL; - add_path(rel, (Path *) create_append_path(rel, NIL, NULL)); + add_path(rel, (Path *) create_append_path(rel, NIL, NULL, false, NIL)); /* Select cheapest path (pretty easy in this case...) */ set_cheapest(rel); diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c index 606734a..56d240c 100644 --- a/src/backend/optimizer/path/indxpath.c +++ b/src/backend/optimizer/path/indxpath.c @@ -38,6 +38,14 @@ #include "utils/pg_locale.h" #include "utils/selfuncs.h" +/* + * index support for LIKE mchar + */ +#include "fmgr.h" +#include "access/htup_details.h" +#include "utils/catcache.h" +#include "utils/syscache.h" +#include "parser/parse_type.h" #define IsBooleanOpfamily(opfamily) \ ((opfamily) == BOOL_BTREE_FAM_OID || (opfamily) == BOOL_HASH_FAM_OID) @@ -2968,6 +2976,208 @@ match_index_to_operand(Node *operand, } /**************************************************************************** + * ---- ROUTINES FOR "SPECIAL" INDEXABLE OPERATORS FOR + * SPECIAL USER_DEFINED TYPES ---- + * -- teodor + ****************************************************************************/ + +static Oid mmPFPOid = InvalidOid; +static Oid mmGTOid = InvalidOid; +static Oid mcharOid = InvalidOid; +static Oid mvarcharOid = InvalidOid; + +static bool +fillMCharOIDS() { + CatCList *catlist; + HeapTuple tup; + char *funcname = "mchar_pattern_fixed_prefix"; + int n_members; + + catlist = SearchSysCacheList(PROCNAMEARGSNSP, 1, + CStringGetDatum(funcname), + 0, 0, 0); + n_members = catlist->n_members; + + if ( n_members != 1 ) { + ReleaseSysCacheList(catlist); + if ( n_members > 1 ) + elog(ERROR,"There are %d candidates for '%s' function'", n_members, funcname); + return false; + } + + tup = &catlist->members[0]->tuple; + + if ( HeapTupleGetOid(tup) != mmPFPOid ) { + TypeName *typename; + Type typtup; + char *quals_funcname = "mchar_greaterstring"; + Oid tmp_mmPFPOid = HeapTupleGetOid(tup); + + ReleaseSysCacheList(catlist); + + typename = makeTypeName("mchar"); + typtup = LookupTypeName(NULL, typename, NULL); + if ( typtup ) { + mcharOid = typeTypeId(typtup); + ReleaseSysCache(typtup); + } + + typename = makeTypeName("mvarchar"); + typtup = LookupTypeName(NULL, typename, NULL); + if ( typtup ) { + mvarcharOid = typeTypeId(typtup); + ReleaseSysCache(typtup); + } + + + if ( mcharOid == InvalidOid || mvarcharOid == InvalidOid ) { + elog(LOG,"Can't find mchar/mvarvarchar types: mchar=%d mvarchar=%d", + mcharOid, mvarcharOid); + return false; + } + + catlist = SearchSysCacheList(PROCNAMEARGSNSP, 1, + CStringGetDatum(quals_funcname), + 0, 0, 0); + n_members = catlist->n_members; + + if ( n_members != 1 ) { + ReleaseSysCacheList(catlist); + if ( n_members > 1 ) + elog(ERROR,"There are %d candidates for '%s' function'", n_members, quals_funcname); + return false; + } + + tup = &catlist->members[0]->tuple; + mmGTOid = HeapTupleGetOid(tup); + mmPFPOid = tmp_mmPFPOid; + } + + ReleaseSysCacheList(catlist); + + return true; +} + +static Pattern_Prefix_Status +mchar_pattern_fixed_prefix(Oid opOid, Oid opfamilyOid, Const *patt, Pattern_Type ptype, + Const **prefix, Oid *leftTypeOid) { + HeapTuple tup; + Form_pg_operator oprForm; + bool isMCharLike = true; + + if ( !fillMCharOIDS() ) + return Pattern_Prefix_None; + + tup = SearchSysCache(OPEROID, opOid, 0, 0, 0); + oprForm = (Form_pg_operator) GETSTRUCT(tup); + + if ( strncmp(oprForm->oprname.data, "~~", 2) != 0 ) + isMCharLike = false; + + if ( oprForm->oprright != mvarcharOid ) + isMCharLike = false; + + if ( !( oprForm->oprleft == mcharOid || oprForm->oprleft == mvarcharOid ) ) + isMCharLike = false; + + if ( patt->consttype != mvarcharOid ) + isMCharLike = false; + + if (leftTypeOid) + *leftTypeOid = oprForm->oprleft; + + ReleaseSysCache(tup); + + if ( !isMCharLike ) + return Pattern_Prefix_None; + + if ( opfamilyOid != InvalidOid ) { + Form_pg_opfamily claForm; + + tup = SearchSysCache(OPFAMILYOID, opfamilyOid, 0, 0, 0); + claForm = (Form_pg_opfamily) GETSTRUCT(tup); + + if ( claForm->opfmethod != BTREE_AM_OID ) + isMCharLike = false; + + if ( mcharOid && strncmp(claForm->opfname.data, "icase_ops", 9 /* strlen(icase_ops) */ ) != 0 ) + isMCharLike = false; + + ReleaseSysCache(tup); + } + + if ( !isMCharLike ) + return Pattern_Prefix_None; + + return (Pattern_Prefix_Status)DatumGetInt32( OidFunctionCall3( + mmPFPOid, + PointerGetDatum( patt ), + Int32GetDatum( ptype ), + PointerGetDatum( prefix ) + ) ); +} + +static Oid +get_opclass_member_mchar(Oid opclass, Oid leftTypeOid, int strategy) { + Oid oproid; + + oproid = get_opfamily_member(opclass, leftTypeOid, mvarcharOid, strategy); + + if ( oproid == InvalidOid ) + elog(ERROR, "no operator for opclass %u for strategy %u for left type %u", opclass, strategy, leftTypeOid); + + return oproid; +} + +static List * +mchar_prefix_quals(Node *leftop, Oid leftTypeOid, Oid opclass, + Const *prefix_const, Pattern_Prefix_Status pstatus) { + Oid oproid; + Expr *expr; + List *result; + Const *greaterstr; + + Assert(pstatus != Pattern_Prefix_None); + if ( pstatus == Pattern_Prefix_Exact ) { + oproid = get_opclass_member_mchar(opclass, leftTypeOid, BTEqualStrategyNumber); + + expr = make_opclause(oproid, BOOLOID, false, + (Expr *) leftop, (Expr *) prefix_const, + InvalidOid, InvalidOid); + result = list_make1(make_simple_restrictinfo(expr)); + return result; + } + + /* We can always say "x >= prefix". */ + oproid = get_opclass_member_mchar(opclass, leftTypeOid, BTGreaterEqualStrategyNumber); + + expr = make_opclause(oproid, BOOLOID, false, + (Expr *) leftop, (Expr *) prefix_const, + InvalidOid, InvalidOid); + result = list_make1(make_simple_restrictinfo(expr)); + + /* If we can create a string larger than the prefix, we can say + * "x < greaterstr". */ + + greaterstr = (Const*)DatumGetPointer( OidFunctionCall1( + mmGTOid, + PointerGetDatum( prefix_const ) + ) ); + + if (greaterstr) { + oproid = get_opclass_member_mchar(opclass, leftTypeOid, BTLessStrategyNumber); + + expr = make_opclause(oproid, BOOLOID, false, + (Expr *) leftop, (Expr *) greaterstr, + InvalidOid, InvalidOid); + result = lappend(result, make_simple_restrictinfo(expr)); + } + + return result; +} + + +/**************************************************************************** * ---- ROUTINES FOR "SPECIAL" INDEXABLE OPERATORS ---- ****************************************************************************/ @@ -3159,9 +3369,16 @@ match_special_index_operator(Expr *clause, Oid opfamily, Oid idxcollation, pfree(prefix); } - /* done if the expression doesn't look indexable */ - if (!isIndexable) + if ( !isIndexable ) { + /* done if the expression doesn't look indexable, + but we should previously check it for mchar/mvarchar types */ + if ( mchar_pattern_fixed_prefix(expr_op, InvalidOid, + patt, Pattern_Type_Like, + &prefix, NULL) != Pattern_Prefix_None ) { + return true; + } return false; + } /* * Must also check that index's opfamily supports the operators we will @@ -3412,6 +3629,14 @@ expand_indexqual_opclause(RestrictInfo *rinfo, Oid opfamily, Oid idxcollation) Const *patt = (Const *) rightop; Const *prefix = NULL; Pattern_Prefix_Status pstatus; + Oid leftTypeOid; + + pstatus = mchar_pattern_fixed_prefix(expr_op, opfamily, + patt, Pattern_Type_Like, + &prefix, &leftTypeOid); + + if ( pstatus != Pattern_Prefix_None ) + return mchar_prefix_quals(leftop, leftTypeOid, opfamily, prefix, pstatus); /* * LIKE and regex operators are not members of any btree index opfamily, diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c index d627f9e..589b728 100644 --- a/src/backend/optimizer/path/joinrels.c +++ b/src/backend/optimizer/path/joinrels.c @@ -1053,7 +1053,7 @@ mark_dummy_rel(RelOptInfo *rel) rel->pathlist = NIL; /* Set up the dummy path */ - add_path(rel, (Path *) create_append_path(rel, NIL, NULL)); + add_path(rel, (Path *) create_append_path(rel, NIL, NULL, false, NIL)); /* Set or update cheapest_total_path and related fields */ set_cheapest(rel); diff --git a/src/backend/optimizer/path/orindxpath.c b/src/backend/optimizer/path/orindxpath.c index 16f29d3..a0b6c53 100644 --- a/src/backend/optimizer/path/orindxpath.c +++ b/src/backend/optimizer/path/orindxpath.c @@ -15,10 +15,58 @@ #include "postgres.h" +#include "access/skey.h" +#include "catalog/pg_am.h" #include "optimizer/cost.h" +#include "optimizer/clauses.h" #include "optimizer/paths.h" +#include "optimizer/pathnode.h" +#include "optimizer/planmain.h" +#include "optimizer/predtest.h" #include "optimizer/restrictinfo.h" +#include "utils/lsyscache.h" +typedef struct CKey { + RestrictInfo *rinfo; /* original rinfo */ + int n; /* IndexPath's number in bitmapquals */ + OpExpr *normalizedexpr; /* expression with Var on left */ + Var *var; + Node *value; + Oid opfamily; + int strategy; + uint8 strategyMask; +} CKey; +#define BTMASK(x) ( 1<<(x) ) + +static List* find_common_quals( BitmapOrPath *path ); +static RestrictInfo* unionOperation(CKey *key); +static BitmapOrPath* cleanup_nested_quals( PlannerInfo *root, RelOptInfo *rel, BitmapOrPath *path ); +static List* sortIndexScans( List* ipaths ); +static List* reverseScanDirIdxPaths(List *indexPaths); +static IndexPath* reverseScanDirIdxPath(IndexPath *ipath); + +#define IS_LESS(a) ( (a) == BTLessStrategyNumber || (a)== BTLessEqualStrategyNumber ) +#define IS_GREATER(a) ( (a) == BTGreaterStrategyNumber || (a) == BTGreaterEqualStrategyNumber ) +#define IS_ONE_DIRECTION(a,b) ( \ + ( IS_LESS(a) && IS_LESS(b) ) \ + || \ + ( IS_GREATER(a) && IS_GREATER(b) ) \ +) + +typedef struct ExExpr { + OpExpr *expr; + Oid opfamily; + Oid lefttype; + Oid righttype; + int strategy; + int attno; +} ExExpr; + + +typedef struct IndexPathEx { + IndexPath *path; + List *preparedquals; /* list of ExExpr */ +} IndexPathEx; /*---------- * create_or_index_quals @@ -185,3 +233,912 @@ create_or_index_quals(PlannerInfo *root, RelOptInfo *rel) /* Tell caller to recompute partial index status and rowcount estimate */ return true; } + + +/*---------- + * keybased_rewrite_or_index_quals + * Examine join OR-of-AND quals to see if any useful common restriction + * clauses can be extracted. If so, try to use for creating new index paths. + * + * For example consider + * WHERE ( a.x=5 and a.y>10 ) OR a.x>5 + * and there is an index on a.x or (a.x, a.y). So, plan + * will be seqscan or BitmapOr(IndexPath,IndexPath) + * So, we can add some restriction: + * WHERE (( a.x=5 and a.y>10 ) OR a.x>5) AND a.x>=5 + * and plan may be so + * Index Scan (a.x>=5) + * Filter( (( a.x=5 and a.y>10 ) OR a.x>5) ) + * + * We don't want to add new clauses to baserestrictinfo, just + * use it as index quals. + * + * Next thing which it possible to test is use append of + * searches instead of OR. + * For example consider + * WHERE ( a.x=5 and a.y>10 ) OR a.x>6 + * and there is an index on (a.x) (a.x, a.y) + * So, we can suggest follow plan: + * Append + * Filter ( a.x=5 and a.y>10 ) OR (a.x>6) + * Index Scan (a.x=5) --in case of index on (a.x) + * Index Scan (a.x>6) + * For that we should proof that index quals isn't overlapped, + * also, some index quals may be containedi in other, so it can be eliminated + */ + +void +keybased_rewrite_index_paths(PlannerInfo *root, RelOptInfo *rel) +{ + BitmapOrPath *bestpath = NULL; + ListCell *i; + List *commonquals; + AppendPath *appendidxpath; + List *indexPaths; + IndexOptInfo *index; + + foreach(i, rel->baserestrictinfo) + { + RestrictInfo *rinfo = (RestrictInfo *) lfirst(i); + + if (restriction_is_or_clause(rinfo) && + !rinfo->outerjoin_delayed) + { + /* + * Use the generate_bitmap_or_paths() machinery to estimate the + * value of each OR clause. We can use regular restriction + * clauses along with the OR clause contents to generate + * indexquals. We pass outer_rel = NULL so that sub-clauses + * that are actually joins will be ignored. + */ + List *orpaths; + ListCell *k; + + orpaths = generate_bitmap_or_paths(root, rel, + list_make1(rinfo), + rel->baserestrictinfo, + true); + + /* Locate the cheapest OR path */ + foreach(k, orpaths) + { + BitmapOrPath *path = (BitmapOrPath *) lfirst(k); + + Assert(IsA(path, BitmapOrPath)); + if (bestpath == NULL || + path->path.total_cost < bestpath->path.total_cost) + { + bestpath = path; + } + } + } + } + + /* Fail if no suitable clauses found */ + if (bestpath == NULL) + return; + + commonquals = find_common_quals(bestpath); + /* Found quals with the same args, but with, may be, different + operations */ + if ( commonquals != NULL ) { + List *addon=NIL; + + foreach(i, commonquals) { + CKey *key = (CKey*)lfirst(i); + RestrictInfo *rinfo; + + /* + * get 'union' of operation for key + */ + rinfo = unionOperation(key); + if ( rinfo ) + addon = lappend(addon, rinfo); + } + + /* + * Ok, we found common quals and union it, so we will try to + * create new possible index paths + */ + if ( addon ) { + List *origbaserestrictinfo = list_copy(rel->baserestrictinfo); + + rel->baserestrictinfo = list_concat(rel->baserestrictinfo, addon); + + create_index_paths(root, rel); + + rel->baserestrictinfo = origbaserestrictinfo; + } + } + + /* + * Check if indexquals isn't overlapped and all index scan + * are on the same index. + */ + if ( (bestpath = cleanup_nested_quals( root, rel, bestpath )) == NULL ) + return; + + if (IsA(bestpath, IndexPath)) { + IndexPath *ipath = (IndexPath*)bestpath; + + Assert(list_length(ipath->indexquals) == list_length(ipath->indexqualcols)); + /* + * It's possible to do only one index scan :) + */ + index = ipath->indexinfo; + + if ( root->query_pathkeys != NIL && index->sortopfamily && OidIsValid(index->sortopfamily[0]) ) + { + List *pathkeys; + + pathkeys = build_index_pathkeys(root, index, + ForwardScanDirection); + pathkeys = truncate_useless_pathkeys(root, rel, + pathkeys); + + ipath->path.pathkeys = pathkeys; + add_path(rel, (Path *) ipath); + + /* + * add path ordered in backward direction if our pathkeys + * is still unusable... + */ + if ( pathkeys == NULL || pathkeys_useful_for_ordering(root, pathkeys) == 0 ) + { + pathkeys = build_index_pathkeys(root, index, + BackwardScanDirection); + pathkeys = truncate_useless_pathkeys(root, rel, + pathkeys); + + ipath = reverseScanDirIdxPath( ipath ); + + ipath->path.pathkeys = pathkeys; + add_path(rel, (Path *) ipath); + } + } else + add_path(rel, (Path *) ipath); + return; + } + + /* recount costs */ + foreach(i, bestpath->bitmapquals ) { + IndexPath *ipath = (IndexPath*)lfirst(i); + + Assert( IsA(ipath, IndexPath) ); + Assert(list_length(ipath->indexquals) == list_length(ipath->indexqualcols)); + ipath->path.rows = rel->tuples * clauselist_selectivity(root, + ipath->indexquals, + rel->relid, + JOIN_INNER, + NULL); + ipath->path.rows = clamp_row_est(ipath->path.rows); + cost_index(ipath, root, 1); + } + + /* + * Check if append index can suggest ordering of result + * + * Also, we should say to AppendPath about targetlist: + * target list will be taked from indexscan + */ + index = ((IndexPath*)linitial(bestpath->bitmapquals))->indexinfo; + if ( root->query_pathkeys != NIL && index->sortopfamily && OidIsValid(index->sortopfamily[0]) && + (indexPaths = sortIndexScans( bestpath->bitmapquals )) !=NULL ) { + List *pathkeys; + + pathkeys = build_index_pathkeys(root, index, + ForwardScanDirection); + pathkeys = truncate_useless_pathkeys(root, rel, + pathkeys); + + appendidxpath = create_append_path(rel, indexPaths, NULL, true, pathkeys); + add_path(rel, (Path *) appendidxpath); + + /* + * add path ordered in backward direction if our pathkeys + * is still unusable... + */ + if ( pathkeys == NULL || pathkeys_useful_for_ordering(root, pathkeys) == 0 ) { + + pathkeys = build_index_pathkeys(root, index, + BackwardScanDirection); + pathkeys = truncate_useless_pathkeys(root, rel, + pathkeys); + + indexPaths = reverseScanDirIdxPaths(indexPaths); + appendidxpath = create_append_path(rel, indexPaths, NULL, true, pathkeys); + add_path(rel, (Path *) appendidxpath); + } + } else { + appendidxpath = create_append_path(rel, bestpath->bitmapquals, NULL, true, NIL); + add_path(rel, (Path *) appendidxpath); + } +} + +/* + * transformToCkey - transform RestrictionInfo + * to CKey struct. Fucntion checks possibility and correctness of + * RestrictionInfo to use it as common key, normalizes + * expression and "caches" some information. Note, + * original RestrictInfo isn't touched + */ + +static CKey* +transformToCkey( IndexOptInfo *index, RestrictInfo* rinfo, int indexcol) { + CKey *key; + OpExpr *expr = (OpExpr*)rinfo->clause; + + if ( rinfo->outerjoin_delayed ) + return NULL; + + if ( !IsA(expr, OpExpr) ) + return NULL; + + if ( contain_mutable_functions((Node*)expr) ) + return NULL; + + if ( list_length( expr->args ) != 2 ) + return NULL; + + key = (CKey*)palloc(sizeof(CKey)); + key->rinfo = rinfo; + + key->normalizedexpr = (OpExpr*)copyObject( expr ); + if (!bms_equal(rinfo->left_relids, index->rel->relids)) + CommuteOpExpr(key->normalizedexpr); + + /* + * fix_indexqual_operand returns copy of object + */ + key->var = (Var*)fix_indexqual_operand(linitial(key->normalizedexpr->args), index, indexcol); + Assert( IsA(key->var, Var) ); + + key->opfamily = index->opfamily[ key->var->varattno - 1 ]; + + /* restore varattno, because it may be different in different index */ + key->var->varattno = key->var->varoattno; + + key->value = (Node*)lsecond(key->normalizedexpr->args); + + key->strategy = get_op_opfamily_strategy( key->normalizedexpr->opno, key->opfamily); + Assert( key->strategy != InvalidStrategy ); + + key->strategyMask = BTMASK(key->strategy); + + return key; +} + +/* + * get_index_quals - get list of quals in + * CKeys form + */ + +static List* +get_index_quals(IndexPath *path, int cnt) { + ListCell *i, *c; + List *quals = NIL; + + Assert(list_length(path->indexquals) == list_length(path->indexqualcols)); + forboth(i, path->indexquals, c, path->indexqualcols) { + CKey *k = transformToCkey( path->indexinfo, (RestrictInfo*)lfirst(i), lfirst_int(c) ); + if ( k ) { + k->n = cnt; + quals = lappend(quals, k); + } + } + return quals; +} + +/* + * extract all quals from bitmapquals->indexquals for + */ +static List* +find_all_quals( BitmapOrPath *path, int *counter ) { + ListCell *i,*j; + List *allquals = NIL; + + *counter = 0; + + foreach(i, path->bitmapquals ) + { + Path *subpath = (Path *) lfirst(i); + + if ( IsA(subpath, BitmapAndPath) ) { + foreach(j, ((BitmapAndPath*)subpath)->bitmapquals) { + Path *subsubpath = (Path *) lfirst(i); + + if ( IsA(subsubpath, IndexPath) ) { + if ( ((IndexPath*)subsubpath)->indexinfo->relam != BTREE_AM_OID ) + return NIL; + allquals = list_concat(allquals, get_index_quals( (IndexPath*)subsubpath, *counter )); + } else + return NIL; + } + } else if ( IsA(subpath, IndexPath) ) { + if ( ((IndexPath*)subpath)->indexinfo->relam != BTREE_AM_OID ) + return NIL; + allquals = list_concat(allquals, get_index_quals( (IndexPath*)subpath, *counter )); + } else + return NIL; + + (*counter)++; + } + + return allquals; +} + +/* + * Compares aruments of operation + */ +static bool +iseqCKeyArgs( CKey *a, CKey *b ) { + if ( a->opfamily != b->opfamily ) + return false; + + if ( !equal( a->value, b->value ) ) + return false; + + if ( !equal( a->var, b->var ) ) + return false; + + return true; +} + +/* + * Count entries of CKey with the same arguments + */ +static int +count_entry( List *allquals, CKey *tocmp ) { + ListCell *i; + int curcnt=0; + + foreach(i, allquals) { + CKey *key = lfirst(i); + + if ( key->n == curcnt ) { + continue; + } else if ( key->n == curcnt+1 ) { + if ( iseqCKeyArgs( key, tocmp ) ) { + tocmp->strategyMask |= key->strategyMask; + curcnt++; + } + } else + return -1; + } + + return curcnt+1; +} + +/* + * Finds all CKey with the same arguments + */ +static List* +find_common_quals( BitmapOrPath *path ) { + List *allquals; + List *commonquals = NIL; + ListCell *i; + int counter; + + if ( (allquals = find_all_quals( path, &counter ))==NIL ) + return NIL; + + foreach(i, allquals) { + CKey *key = lfirst(i); + + if ( key->n != 0 ) + break; + + if ( counter == count_entry(allquals, key) ) + commonquals = lappend( commonquals, key ); + } + + return commonquals; +} + +/* + * unionOperation - make RestrictInfo with combined operation + */ + +static RestrictInfo* +unionOperation(CKey *key) { + RestrictInfo *rinfo; + Oid lefttype, righttype; + int strategy; + + switch( key->strategyMask ) { + case BTMASK(BTLessStrategyNumber): + case BTMASK(BTLessEqualStrategyNumber): + case BTMASK(BTEqualStrategyNumber): + case BTMASK(BTGreaterEqualStrategyNumber): + case BTMASK(BTGreaterStrategyNumber): + /* trivial case */ + break; + case BTMASK(BTLessStrategyNumber) | BTMASK(BTLessEqualStrategyNumber): + case BTMASK(BTLessStrategyNumber) | BTMASK(BTLessEqualStrategyNumber) | BTMASK(BTEqualStrategyNumber): + case BTMASK(BTLessStrategyNumber) | BTMASK(BTEqualStrategyNumber): + case BTMASK(BTLessEqualStrategyNumber) | BTMASK(BTEqualStrategyNumber): + /* any subset of <, <=, = can be unioned with <= */ + key->strategy = BTLessEqualStrategyNumber; + break; + case BTMASK(BTGreaterEqualStrategyNumber) | BTMASK(BTGreaterStrategyNumber): + case BTMASK(BTEqualStrategyNumber) | BTMASK(BTGreaterEqualStrategyNumber) | BTMASK(BTGreaterStrategyNumber): + case BTMASK(BTEqualStrategyNumber) | BTMASK(BTGreaterStrategyNumber): + case BTMASK(BTEqualStrategyNumber) | BTMASK(BTGreaterEqualStrategyNumber): + /* any subset of >, >=, = can be unioned with >= */ + key->strategy = BTGreaterEqualStrategyNumber; + break; + default: + /* + * Can't make common restrict qual + */ + return NULL; + } + + get_op_opfamily_properties(key->normalizedexpr->opno, key->opfamily, false, + &strategy, &lefttype, &righttype); + + if ( strategy != key->strategy ) { + /* + * We should check because it's possible to have "strange" + * opfamilies - without some strategies... + */ + key->normalizedexpr->opno = get_opfamily_member(key->opfamily, lefttype, righttype, key->strategy); + + if ( key->normalizedexpr->opno == InvalidOid ) + return NULL; + + key->normalizedexpr->opfuncid = get_opcode( key->normalizedexpr->opno ); + Assert ( key->normalizedexpr->opfuncid != InvalidOid ); + } + + rinfo = make_simple_restrictinfo((Expr*)key->normalizedexpr); + + return rinfo; +} + +/* + * Remove unneeded RestrioctionInfo nodes as it + * needed by predicate_*_by() + */ +static void +make_predicate(List *indexquals, List *indexqualcols, List **preds, List **predcols) { + ListCell *i, *c; + + *preds = NIL; + *predcols = NIL; + + forboth(i, indexquals, c, indexqualcols) + { + RestrictInfo *rinfo = lfirst(i); + OpExpr *expr = (OpExpr*)rinfo->clause; + + if ( rinfo->outerjoin_delayed ) + continue; + + if ( !IsA(expr, OpExpr) ) + continue; + + if ( list_length( expr->args ) != 2 ) + continue; + + *preds = lappend(*preds, rinfo); + *predcols = lappend(*predcols, lfirst(c)); + } +} + +#define CELL_GET_QUALS(x) ( ((IndexPath*)lfirst(x))->indexquals ) +#define CELL_GET_CLAUSES(x) ( ((IndexPath*)lfirst(x))->indexclauses ) + +static List* +listRInfo2OpExpr(List *listRInfo) { + ListCell *i; + List *listOpExpr=NULL; + + foreach(i, listRInfo) + { + RestrictInfo *rinfo = lfirst(i); + OpExpr *expr = (OpExpr*)rinfo->clause; + + listOpExpr = lappend(listOpExpr, expr); + } + + return listOpExpr; +} + +/* + * returns list of all nested quals + */ +static List* +contained_quals(List *nested, List* quals, ListCell *check) { + ListCell *i; + List *checkpred; + + if ( list_member_ptr( nested, lfirst(check) ) ) + return nested; + + if (equal(CELL_GET_QUALS(check), CELL_GET_CLAUSES(check)) == false) + return nested; + + checkpred = listRInfo2OpExpr(CELL_GET_QUALS(check)); + + if ( contain_mutable_functions((Node*)checkpred) ) + return nested; + + foreach(i, quals ) + { + if ( check == i ) + continue; + + if ( list_member_ptr( nested, lfirst(i) ) ) + continue; + + if ( equal(CELL_GET_QUALS(i), CELL_GET_CLAUSES(i)) && + predicate_implied_by( checkpred, CELL_GET_QUALS(i) ) ) + nested = lappend( nested, lfirst(i) ); + } + return nested; +} + +/* + * Checks that one row can be in several quals. + * It's guaranteed by predicate_refuted_by() + */ +static bool +is_intersect(ListCell *check) { + ListCell *i; + List *checkpred=NULL; + + checkpred=listRInfo2OpExpr(CELL_GET_QUALS(check)); + Assert( checkpred != NULL ); + + for_each_cell(i, check) { + if ( i==check ) + continue; + + if ( predicate_refuted_by( checkpred, CELL_GET_QUALS(i) ) == false ) + return true; + } + + return false; +} + +/* + * Removes nested quals and gurantees that quals are not intersected, + * ie one row can't satisfy to several quals. It's open a possibility of + * Append node using instead of BitmapOr + */ +static BitmapOrPath* +cleanup_nested_quals( PlannerInfo *root, RelOptInfo *rel, BitmapOrPath *path ) { + ListCell *i; + IndexOptInfo *index=NULL; + List *nested = NULL; + + /* + * check all path to use only one index + */ + foreach(i, path->bitmapquals ) + { + + if ( IsA(lfirst(i), IndexPath) ) { + List *preds, *predcols; + IndexPath *subpath = (IndexPath *) lfirst(i); + + if ( subpath->indexinfo->relam != BTREE_AM_OID ) + return NULL; + + if ( index == NULL ) + index = subpath->indexinfo; + else if ( index->indexoid != subpath->indexinfo->indexoid ) + return NULL; + + /* + * work only with optimizable quals + */ + Assert(list_length(subpath->indexquals) == list_length(subpath->indexqualcols)); + make_predicate(subpath->indexquals, subpath->indexqualcols, &preds, &predcols); + if (preds == NIL) + return NULL; + subpath->indexquals = preds; + subpath->indexqualcols = predcols; + Assert(list_length(subpath->indexquals) == list_length(subpath->indexqualcols)); + } else + return NULL; + } + + /* + * eliminate nested quals + */ + foreach(i, path->bitmapquals ) { + nested = contained_quals(nested, path->bitmapquals, i); + } + + if ( nested != NIL ) { + path->bitmapquals = list_difference_ptr( path->bitmapquals, nested ); + + Assert( list_length( path->bitmapquals )>0 ); + + /* + * All quals becomes only one after eliminating nested quals + */ + if (list_length( path->bitmapquals ) == 1) + return (BitmapOrPath*)linitial(path->bitmapquals); + } + + /* + * Checks for intersection + */ + foreach(i, path->bitmapquals ) { + if ( is_intersect( i ) ) + return NULL; + } + + return path; +} + +/* + * Checks if whole result of one simple operation is contained + * in another + */ +static int +simpleCmpExpr( ExExpr *a, ExExpr *b ) { + if ( predicate_implied_by((List*)a->expr, (List*)b->expr) ) + /* + * a:( Var < 15 ) > b:( Var <= 10 ) + */ + return 1; + else if ( predicate_implied_by((List*)b->expr, (List*)a->expr) ) + /* + * a:( Var <= 10 ) < b:( Var < 15 ) + */ + return -1; + else + return 0; +} + +/* + * Trys to define where is equation - on left or right side + * a(< 10) b(=11) - on right + * a(> 10) b(=9) - on left + * a(= 10) b(=11) - on right + * a(= 10) b(=9) - on left + * Any other - result is 0; + */ +static int +cmpEqExpr( ExExpr *a, ExExpr *b ) { + Oid oldop = b->expr->opno; + int res=0; + + b->expr->opno = get_opfamily_member(b->opfamily, b->lefttype, b->righttype, BTLessStrategyNumber); + if ( b->expr->opno != InvalidOid ) { + b->expr->opfuncid = get_opcode( b->expr->opno ); + res = simpleCmpExpr(a,b); + } + + if ( res == 0 ) { + b->expr->opno = get_opfamily_member(b->opfamily, b->lefttype, b->righttype, BTGreaterStrategyNumber); + if ( b->expr->opno != InvalidOid ) { + b->expr->opfuncid = get_opcode( b->expr->opno ); + res = -simpleCmpExpr(a,b); + } + } + + b->expr->opno = oldop; + b->expr->opfuncid = get_opcode( b->expr->opno ); + + return res; +} + +/* + * Is result of a contained in result of b or on the contrary? + */ +static int +cmpNegCmp( ExExpr *a, ExExpr *b ) { + Oid oldop = b->expr->opno; + int res = 0; + + b->expr->opno = get_negator( b->expr->opno ); + if ( b->expr->opno != InvalidOid ) { + b->expr->opfuncid = get_opcode( b->expr->opno ); + res = simpleCmpExpr(a,b); + } + + b->expr->opno = oldop; + b->expr->opfuncid = get_opcode( b->expr->opno ); + + return ( IS_LESS(a->strategy) ) ? res : -res; +} + +/* + * Returns 1 if whole result of a is on left comparing with result of b + * Returns -1 if whole result of a is on right comparing with result of b + * Return 0 if it's impossible to define or results is overlapped + * Expressions should use the same attribute of index and should be + * a simple: just one operation with index. + */ +static int +cmpExpr( ExExpr *a, ExExpr *b ) { + int res; + + /* + * If a and b are overlapped, we can't decide which one is + * lefter or righter + */ + if ( IS_ONE_DIRECTION(a->strategy, b->strategy) || predicate_refuted_by((List*)a->expr, (List*)b->expr) == false ) + return 0; + + /* + * In this place it's impossible to have a row which satisfies + * a and b expressions, so we will try to find relatiove position of that results + */ + if ( b->strategy == BTEqualStrategyNumber ) { + return -cmpEqExpr(a, b); /* Covers cases with any operations in a */ + } else if ( a->strategy == BTEqualStrategyNumber ) { + return cmpEqExpr(b, a); + } else if ( (res = cmpNegCmp(a, b)) == 0 ) { /* so, a(<10) b(>20) */ + res = -cmpNegCmp(b, a); + } + + return res; +} + +/* + * Try to define positions of result which satisfy indexquals a and b per + * one index's attribute. + */ +static int +cmpColumnQuals( List *a, List *b, int attno ) { + int res = 0; + ListCell *ai, *bi; + + foreach(ai, a) { + ExExpr *ae = (ExExpr*)lfirst(ai); + + if ( attno != ae->attno ) + continue; + + foreach(bi, b) { + ExExpr *be = (ExExpr*)lfirst(bi); + + if ( attno != be->attno ) + continue; + + if ((res=cmpExpr(ae, be))!=0) + return res; + } + } + + return 0; +} + +static IndexOptInfo *sortingIndex = NULL; +static bool volatile unableToDefine = false; + +/* + * Compare result of two indexquals. + * Warinig: it use PG_RE_THROW(), so any call should be wrapped with + * PG_TRY(). Try/catch construction is used here for minimize unneeded + * actions when sorting is impossible + */ +static int +cmpIndexPathEx(const void *a, const void *b) { + IndexPathEx *aipe = (IndexPathEx*)a; + IndexPathEx *bipe = (IndexPathEx*)b; + int attno, res = 0; + + for(attno=1; res==0 && attno<=sortingIndex->ncolumns; attno++) + res=cmpColumnQuals(aipe->preparedquals, bipe->preparedquals, attno); + + if ( res==0 ) { + unableToDefine = true; + PG_RE_THROW(); /* it should be PG_THROW(), but it's the same */ + } + + return res; +} + +/* + * Initialize lists of operation in useful form + */ +static List* +prepareQuals(IndexOptInfo *index, List *indexquals, List *indexqualcols) { + ListCell *i, *c; + List *res=NULL; + ExExpr *ex; + + Assert(list_length(indexquals) == list_length(indexqualcols)); + forboth(i, indexquals, c, indexqualcols) + { + RestrictInfo *rinfo = lfirst(i); + OpExpr *expr = (OpExpr*)rinfo->clause; + + if ( rinfo->outerjoin_delayed ) + return NULL; + + if ( !IsA(expr, OpExpr) ) + return NULL; + + if ( list_length( expr->args ) != 2 ) + return NULL; + + if ( contain_mutable_functions((Node*)expr) ) + return NULL; + + ex = (ExExpr*)palloc(sizeof(ExExpr)); + ex->expr = (OpExpr*)copyObject( expr ); + if (!bms_equal(rinfo->left_relids, index->rel->relids)) + CommuteOpExpr(ex->expr); + linitial(ex->expr->args) = fix_indexqual_operand(linitial(ex->expr->args), index, lfirst_int(c)); + ex->attno = ((Var*)linitial(ex->expr->args))->varattno; + ex->opfamily = index->opfamily[ ex->attno - 1 ]; + get_op_opfamily_properties( ex->expr->opno, ex->opfamily, false, + &ex->strategy, &ex->lefttype, &ex->righttype); + + + res = lappend(res, ex); + } + + return res; +} + +/* + * sortIndexScans - sorts index scans to get sorted results. + * Function supposed that index is the same for all + * index scans + */ +static List* +sortIndexScans( List* ipaths ) { + ListCell *i; + int j=0; + IndexPathEx *ipe = (IndexPathEx*)palloc( sizeof(IndexPathEx)*list_length(ipaths) ); + List *orderedPaths = NIL; + IndexOptInfo *index = ((IndexPath*)linitial(ipaths))->indexinfo; + + foreach(i, ipaths) { + ipe[j].path = (IndexPath*)lfirst(i); + ipe[j].preparedquals = prepareQuals( index, ipe[j].path->indexquals, ipe[j].path->indexqualcols ); + + if (ipe[j].preparedquals == NULL) + return NULL; + j++; + } + + sortingIndex = index; + unableToDefine = false; + PG_TRY(); { + qsort(ipe, list_length(ipaths), sizeof(IndexPathEx), cmpIndexPathEx); + } PG_CATCH(); { + if ( unableToDefine == false ) + PG_RE_THROW(); /* not our problem */ + } PG_END_TRY(); + + if ( unableToDefine == true ) + return NULL; + + for(j=0;jindexscandir = BackwardScanDirection; + + return n; +} + +static List* +reverseScanDirIdxPaths(List *indexPaths) { + List *idxpath = NIL; + ListCell *i; + + foreach(i, indexPaths) { + idxpath = lcons(reverseScanDirIdxPath( (IndexPath*)lfirst(i) ), idxpath); + } + + return idxpath; +} diff --git a/src/backend/optimizer/path/pathkeys.c b/src/backend/optimizer/path/pathkeys.c index 6724996..8bb74cc 100644 --- a/src/backend/optimizer/path/pathkeys.c +++ b/src/backend/optimizer/path/pathkeys.c @@ -1379,7 +1379,7 @@ right_merge_direction(PlannerInfo *root, PathKey *pathkey) * no good to order by just the first key(s) of the requested ordering. * So the result is always either 0 or list_length(root->query_pathkeys). */ -static int +int pathkeys_useful_for_ordering(PlannerInfo *root, List *pathkeys) { if (root->query_pathkeys == NIL) diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index c501737..3745e10 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -89,7 +89,6 @@ static void process_subquery_nestloop_params(PlannerInfo *root, List *subplan_params); static List *fix_indexqual_references(PlannerInfo *root, IndexPath *index_path); static List *fix_indexorderby_references(PlannerInfo *root, IndexPath *index_path); -static Node *fix_indexqual_operand(Node *node, IndexOptInfo *index, int indexcol); static List *get_switched_clauses(List *clauses, Relids outerrelids); static List *order_qual_clauses(PlannerInfo *root, List *clauses); static void copy_path_costsize(Plan *dest, Path *src); @@ -679,7 +678,7 @@ static Plan * create_append_plan(PlannerInfo *root, AppendPath *best_path) { Append *plan; - List *tlist = build_path_tlist(root, &best_path->path); + List *tlist; List *subplans = NIL; ListCell *subpaths; @@ -695,6 +694,7 @@ create_append_plan(PlannerInfo *root, AppendPath *best_path) if (best_path->subpaths == NIL) { /* Generate a Result plan with constant-FALSE gating qual */ + tlist = build_path_tlist(root, &best_path->path); return (Plan *) make_result(root, tlist, (Node *) list_make1(makeBoolConst(false, @@ -717,6 +717,11 @@ create_append_plan(PlannerInfo *root, AppendPath *best_path) * parent-rel Vars it'll be asked to emit. */ + if ( best_path->pull_tlist ) + tlist = copyObject( ((Plan*)linitial(subplans))->targetlist ); + else + tlist = build_path_tlist(root, &best_path->path); + plan = make_append(subplans, tlist); return (Plan *) plan; @@ -2957,7 +2962,7 @@ fix_indexorderby_references(PlannerInfo *root, IndexPath *index_path) * Most of the code here is just for sanity cross-checking that the given * expression actually matches the index column it's claimed to. */ -static Node * +Node * fix_indexqual_operand(Node *node, IndexOptInfo *index, int indexcol) { Var *result; diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c index b78d727..3a6b826 100644 --- a/src/backend/optimizer/plan/setrefs.c +++ b/src/backend/optimizer/plan/setrefs.c @@ -1692,6 +1692,10 @@ fix_join_expr_mutator(Node *node, fix_join_expr_context *context) { Var *var = (Var *) node; + /* join_references_mutator already checks this node */ + if ( var->varno == OUTER_VAR ) + return (Node*)copyObject(var); + /* First look for the var in the input tlists */ newvar = search_indexed_tlist_for_var(var, context->outer_itlist, @@ -1701,6 +1705,9 @@ fix_join_expr_mutator(Node *node, fix_join_expr_context *context) return (Node *) newvar; if (context->inner_itlist) { + if ( var->varno == INNER_VAR ) + return (Node*)copyObject(var); + newvar = search_indexed_tlist_for_var(var, context->inner_itlist, INNER_VAR, diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c index 64b17051..8ed8f50 100644 --- a/src/backend/optimizer/util/pathnode.c +++ b/src/backend/optimizer/util/pathnode.c @@ -888,7 +888,7 @@ create_tidscan_path(PlannerInfo *root, RelOptInfo *rel, List *tidquals, * Note that we must handle subpaths = NIL, representing a dummy access path. */ AppendPath * -create_append_path(RelOptInfo *rel, List *subpaths, Relids required_outer) +create_append_path(RelOptInfo *rel, List *subpaths, Relids required_outer, bool pull_tlist, List *pathkeys) { AppendPath *pathnode = makeNode(AppendPath); ListCell *l; @@ -897,9 +897,10 @@ create_append_path(RelOptInfo *rel, List *subpaths, Relids required_outer) pathnode->path.parent = rel; pathnode->path.param_info = get_appendrel_parampathinfo(rel, required_outer); - pathnode->path.pathkeys = NIL; /* result is always considered - * unsorted */ + pathnode->path.pathkeys = pathkeys; /* !=NIL in case of append OR index scans */ + pathnode->subpaths = subpaths; + pathnode->pull_tlist = pull_tlist; /* * We don't bother with inventing a cost_append(), but just do it here. diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 5094226..048a790 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -10549,7 +10549,7 @@ a_expr: c_expr { $$ = $1; } | a_expr LIKE a_expr ESCAPE a_expr { FuncCall *n = makeNode(FuncCall); - n->funcname = SystemFuncName("like_escape"); + n->funcname = list_make1(makeString("like_escape")); n->args = list_make2($3, $5); n->agg_order = NIL; n->agg_star = FALSE; @@ -10564,7 +10564,7 @@ a_expr: c_expr { $$ = $1; } | a_expr NOT LIKE a_expr ESCAPE a_expr { FuncCall *n = makeNode(FuncCall); - n->funcname = SystemFuncName("like_escape"); + n->funcname = list_make1(makeString("like_escape")); n->args = list_make2($4, $6); n->agg_order = NIL; n->agg_star = FALSE; @@ -10579,7 +10579,7 @@ a_expr: c_expr { $$ = $1; } | a_expr ILIKE a_expr ESCAPE a_expr { FuncCall *n = makeNode(FuncCall); - n->funcname = SystemFuncName("like_escape"); + n->funcname = list_make1(makeString("like_escape")); n->args = list_make2($3, $5); n->agg_order = NIL; n->agg_star = FALSE; @@ -10594,7 +10594,7 @@ a_expr: c_expr { $$ = $1; } | a_expr NOT ILIKE a_expr ESCAPE a_expr { FuncCall *n = makeNode(FuncCall); - n->funcname = SystemFuncName("like_escape"); + n->funcname = list_make1(makeString("like_escape")); n->args = list_make2($4, $6); n->agg_order = NIL; n->agg_star = FALSE; @@ -10608,7 +10608,7 @@ a_expr: c_expr { $$ = $1; } | a_expr SIMILAR TO a_expr %prec SIMILAR { FuncCall *n = makeNode(FuncCall); - n->funcname = SystemFuncName("similar_escape"); + n->funcname = list_make1(makeString("similar_escape")); n->args = list_make2($4, makeNullAConst(-1)); n->agg_order = NIL; n->agg_star = FALSE; @@ -10621,7 +10621,7 @@ a_expr: c_expr { $$ = $1; } | a_expr SIMILAR TO a_expr ESCAPE a_expr { FuncCall *n = makeNode(FuncCall); - n->funcname = SystemFuncName("similar_escape"); + n->funcname = list_make1(makeString("similar_escape")); n->args = list_make2($4, $6); n->agg_order = NIL; n->agg_star = FALSE; @@ -10634,7 +10634,7 @@ a_expr: c_expr { $$ = $1; } | a_expr NOT SIMILAR TO a_expr %prec SIMILAR { FuncCall *n = makeNode(FuncCall); - n->funcname = SystemFuncName("similar_escape"); + n->funcname = list_make1(makeString("similar_escape")); n->args = list_make2($5, makeNullAConst(-1)); n->agg_order = NIL; n->agg_star = FALSE; @@ -10647,7 +10647,7 @@ a_expr: c_expr { $$ = $1; } | a_expr NOT SIMILAR TO a_expr ESCAPE a_expr { FuncCall *n = makeNode(FuncCall); - n->funcname = SystemFuncName("similar_escape"); + n->funcname = list_make1(makeString("similar_escape")); n->args = list_make2($5, $7); n->agg_order = NIL; n->agg_star = FALSE; diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index a2853fb..e67ee63 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -879,6 +879,11 @@ typedef struct AppendPath { Path path; List *subpaths; /* list of component Paths */ + bool pull_tlist; /* if = true, create_append_plan() + should get targetlist from any + subpath - they are the same, + because the only place - append + index scan for range OR */ } AppendPath; #define IS_DUMMY_PATH(p) \ diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h index bc68789..5108fa1 100644 --- a/src/include/optimizer/pathnode.h +++ b/src/include/optimizer/pathnode.h @@ -57,7 +57,7 @@ extern BitmapOrPath *create_bitmap_or_path(PlannerInfo *root, extern TidPath *create_tidscan_path(PlannerInfo *root, RelOptInfo *rel, List *tidquals, Relids required_outer); extern AppendPath *create_append_path(RelOptInfo *rel, List *subpaths, - Relids required_outer); + Relids required_outer, bool pull_tlist, List *pathkeys); extern MergeAppendPath *create_merge_append_path(PlannerInfo *root, RelOptInfo *rel, List *subpaths, diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h index 9ef93c7..eda1ae1 100644 --- a/src/include/optimizer/paths.h +++ b/src/include/optimizer/paths.h @@ -66,6 +66,7 @@ extern Expr *adjust_rowcompare_for_index(RowCompareExpr *clause, * additional routines for indexable OR clauses */ extern bool create_or_index_quals(PlannerInfo *root, RelOptInfo *rel); +extern void keybased_rewrite_index_paths(PlannerInfo *root, RelOptInfo *rel); /* * tidpath.h @@ -188,6 +189,7 @@ extern List *select_outer_pathkeys_for_merge(PlannerInfo *root, extern List *make_inner_pathkeys_for_merge(PlannerInfo *root, List *mergeclauses, List *outer_pathkeys); +extern int pathkeys_useful_for_ordering(PlannerInfo *root, List *pathkeys); extern List *truncate_useless_pathkeys(PlannerInfo *root, RelOptInfo *rel, List *pathkeys); diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h index 33eaf32..0379141 100644 --- a/src/include/optimizer/planmain.h +++ b/src/include/optimizer/planmain.h @@ -88,7 +88,7 @@ extern ModifyTable *make_modifytable(PlannerInfo *root, List *resultRelations, List *subplans, List *returningLists, List *rowMarks, int epqParam); extern bool is_projection_capable_plan(Plan *plan); - +extern Node * fix_indexqual_operand(Node *node, IndexOptInfo *index, int indexcol); /* * prototypes for plan/initsplan.c */ diff --git a/src/test/regress/expected/create_index.out b/src/test/regress/expected/create_index.out index 81c64e5..43f0340 100644 --- a/src/test/regress/expected/create_index.out +++ b/src/test/regress/expected/create_index.out @@ -2625,18 +2625,12 @@ DROP TABLE onek_with_null; EXPLAIN (COSTS OFF) SELECT * FROM tenk1 WHERE thousand = 42 AND (tenthous = 1 OR tenthous = 3 OR tenthous = 42); - QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------ - Bitmap Heap Scan on tenk1 - Recheck Cond: (((thousand = 42) AND (tenthous = 1)) OR ((thousand = 42) AND (tenthous = 3)) OR ((thousand = 42) AND (tenthous = 42))) - -> BitmapOr - -> Bitmap Index Scan on tenk1_thous_tenthous - Index Cond: ((thousand = 42) AND (tenthous = 1)) - -> Bitmap Index Scan on tenk1_thous_tenthous - Index Cond: ((thousand = 42) AND (tenthous = 3)) - -> Bitmap Index Scan on tenk1_thous_tenthous - Index Cond: ((thousand = 42) AND (tenthous = 42)) -(9 rows) + QUERY PLAN +----------------------------------------------------------------- + Index Scan using tenk1_thous_tenthous on tenk1 + Index Cond: ((thousand = 42) AND (thousand = 42)) + Filter: ((tenthous = 1) OR (tenthous = 3) OR (tenthous = 42)) +(3 rows) SELECT * FROM tenk1 WHERE thousand = 42 AND (tenthous = 1 OR tenthous = 3 OR tenthous = 42); diff --git a/src/test/regress/expected/select.out b/src/test/regress/expected/select.out index c376523..185d177 100644 --- a/src/test/regress/expected/select.out +++ b/src/test/regress/expected/select.out @@ -518,6 +518,124 @@ TABLE int8_tbl; (9 rows) -- +-- test order by NULLS (FIRST|LAST) +-- +select unique1, unique2 into onek_with_null from onek; +insert into onek_with_null (unique1,unique2) values (NULL, -1), (NULL, NULL); +select * from onek_with_null order by unique1 nulls first , unique2 limit 3; + unique1 | unique2 +---------+--------- + | -1 + | + 0 | 998 +(3 rows) + +select * from onek_with_null order by unique1 nulls last , unique2 limit 3; + unique1 | unique2 +---------+--------- + 0 | 998 + 1 | 214 + 2 | 326 +(3 rows) + +select * from onek_with_null order by unique1 nulls first , unique2 nulls first limit 3; + unique1 | unique2 +---------+--------- + | + | -1 + 0 | 998 +(3 rows) + +select * from onek_with_null order by unique1 nulls last , unique2 nulls first limit 3; + unique1 | unique2 +---------+--------- + 0 | 998 + 1 | 214 + 2 | 326 +(3 rows) + +select * from onek_with_null order by unique1 nulls first , unique2 nulls last limit 3; + unique1 | unique2 +---------+--------- + | -1 + | + 0 | 998 +(3 rows) + +select * from onek_with_null order by unique1 nulls last , unique2 nulls last limit 3; + unique1 | unique2 +---------+--------- + 0 | 998 + 1 | 214 + 2 | 326 +(3 rows) + +select * from onek_with_null order by unique1 desc nulls first , unique2 desc limit 3; + unique1 | unique2 +---------+--------- + | + | -1 + 999 | 152 +(3 rows) + +select * from onek_with_null order by unique1 desc nulls last , unique2 desc limit 3; + unique1 | unique2 +---------+--------- + 999 | 152 + 998 | 549 + 997 | 21 +(3 rows) + +select * from onek_with_null order by unique1 desc nulls first , unique2 desc nulls first limit 3; + unique1 | unique2 +---------+--------- + | + | -1 + 999 | 152 +(3 rows) + +select * from onek_with_null order by unique1 desc nulls last , unique2 desc nulls first limit 3; + unique1 | unique2 +---------+--------- + 999 | 152 + 998 | 549 + 997 | 21 +(3 rows) + +select * from onek_with_null order by unique1 desc nulls first , unique2 desc nulls last limit 3; + unique1 | unique2 +---------+--------- + | -1 + | + 999 | 152 +(3 rows) + +select * from onek_with_null order by unique1 desc nulls last , unique2 desc nulls last limit 3; + unique1 | unique2 +---------+--------- + 999 | 152 + 998 | 549 + 997 | 21 +(3 rows) + +select unique1 as u1, unique2 as u2 from onek_with_null order by u1 nulls first , u2 nulls first limit 3; + u1 | u2 +----+----- + | + | -1 + 0 | 998 +(3 rows) + +select unique1 as u1, unique2 as u2 from onek_with_null order by u1 asc nulls first , u2 desc nulls first limit 3; + u1 | u2 +----+----- + | + | -1 + 0 | 998 +(3 rows) + +drop table onek_with_null; +-- -- Test ORDER BY options -- CREATE TEMP TABLE foo (f1 int); diff --git a/src/test/regress/sql/select.sql b/src/test/regress/sql/select.sql index b99fb13..582e5d6 100644 --- a/src/test/regress/sql/select.sql +++ b/src/test/regress/sql/select.sql @@ -149,6 +149,33 @@ UNION ALL TABLE int8_tbl; -- +-- test order by NULLS (FIRST|LAST) +-- + +select unique1, unique2 into onek_with_null from onek; +insert into onek_with_null (unique1,unique2) values (NULL, -1), (NULL, NULL); + + +select * from onek_with_null order by unique1 nulls first , unique2 limit 3; +select * from onek_with_null order by unique1 nulls last , unique2 limit 3; +select * from onek_with_null order by unique1 nulls first , unique2 nulls first limit 3; +select * from onek_with_null order by unique1 nulls last , unique2 nulls first limit 3; +select * from onek_with_null order by unique1 nulls first , unique2 nulls last limit 3; +select * from onek_with_null order by unique1 nulls last , unique2 nulls last limit 3; + +select * from onek_with_null order by unique1 desc nulls first , unique2 desc limit 3; +select * from onek_with_null order by unique1 desc nulls last , unique2 desc limit 3; +select * from onek_with_null order by unique1 desc nulls first , unique2 desc nulls first limit 3; +select * from onek_with_null order by unique1 desc nulls last , unique2 desc nulls first limit 3; +select * from onek_with_null order by unique1 desc nulls first , unique2 desc nulls last limit 3; +select * from onek_with_null order by unique1 desc nulls last , unique2 desc nulls last limit 3; + +select unique1 as u1, unique2 as u2 from onek_with_null order by u1 nulls first , u2 nulls first limit 3; +select unique1 as u1, unique2 as u2 from onek_with_null order by u1 asc nulls first , u2 desc nulls first limit 3; + +drop table onek_with_null; + +-- -- Test ORDER BY options -- diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm index 7964c01..dc38388 100644 --- a/src/tools/msvc/Mkvcbuild.pm +++ b/src/tools/msvc/Mkvcbuild.pm @@ -44,7 +44,7 @@ my @contrib_uselibpgcommon = ( 'pg_test_fsync', 'pg_test_timing', 'pg_upgrade', 'pg_xlogdump', 'vacuumlo'); -my $contrib_extralibs = { 'pgbench' => ['wsock32.lib'] }; +my $contrib_extralibs = { 'pgbench' => ['wsock32.lib'], 'mchar' => ['icuin.lib', 'icuuc.lib'] }; my $contrib_extraincludes = { 'tsearch2' => ['contrib/tsearch2'], 'dblink' => ['src/backend'] }; my $contrib_extrasource = {