postgresql94/1c_FULL_94-0.23

9150 lines
222 KiB
Text
Executable file

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 <fmgr.h>
+#include <funcapi.h>
+#include <access/heapam.h>
+#include <catalog/pg_type.h>
+#include <catalog/heap.h>
+#include <commands/vacuum.h>
+
+#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 <oleg@sai.msu.ru>
+ Teodor Sigaev <teodor@sigaev.ru>
+
+
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?
+----------------+--------------
+ <No spaces > | <No spaces>
+ <One space > | <One space >
+ <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 <utils/array.h>
+
+#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 <rsalz@bbn.com>.
+** Special thanks to Lars Mathiesen <thorinn@diku.dk> 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. <keith@mtcc.demon.co.uk>
+**
+** SQL92 lets you specify the escape character by saying
+** LIKE <pattern> ESCAPE <escape character>. 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(;i<slen;i++)
+ srest->data[ 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 fc0b5fc..155f5f5 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1588,6 +1588,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/Makefile b/src/backend/optimizer/path/Makefile
index 6864a62..ef250e0 100644
--- a/src/backend/optimizer/path/Makefile
+++ b/src/backend/optimizer/path/Makefile
@@ -12,7 +12,7 @@ subdir = src/backend/optimizer/path
top_builddir = ../../../..
include $(top_builddir)/src/Makefile.global
-OBJS = allpaths.o clausesel.o costsize.o equivclass.o indxpath.o \
- joinpath.o joinrels.o pathkeys.o tidpath.o
+OBJS = allpaths.o appendorpath.o clausesel.o costsize.o equivclass.o \
+ indxpath.o joinpath.o joinrels.o pathkeys.o tidpath.o
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 085356d..f0673fa 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -395,6 +395,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);
@@ -798,7 +801,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
@@ -848,7 +851,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 */
@@ -1078,7 +1081,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/appendorpath.c b/src/backend/optimizer/path/appendorpath.c
new file mode 100644
index 0000000..17950e5
--- /dev/null
+++ b/src/backend/optimizer/path/appendorpath.c
@@ -0,0 +1,966 @@
+/*
+ * support Append plan for ORed clauses
+ * Teodor Sigaev <teodor@sigaev.ru>
+ */
+#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;
+
+
+/*----------
+ * 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);
+
+ /* 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;j<list_length(ipaths);j++)
+ orderedPaths = lappend(orderedPaths, ipe[j].path);
+
+ return orderedPaths;
+}
+
+static IndexPath*
+reverseScanDirIdxPath(IndexPath *ipath) {
+ IndexPath *n = makeNode(IndexPath);
+
+ *n = *ipath;
+
+ n->indexscandir = 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/indxpath.c b/src/backend/optimizer/path/indxpath.c
index 42dcb11..8d19ea0 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)
@@ -115,8 +123,6 @@
bool *skip_lower_saop);
static List *build_paths_for_OR(PlannerInfo *root, RelOptInfo *rel,
List *clauses, List *other_clauses);
-static List *generate_bitmap_or_paths(PlannerInfo *root, RelOptInfo *rel,
- List *clauses, List *other_clauses);
static Path *choose_bitmap_and(PlannerInfo *root, RelOptInfo *rel,
List *paths);
static int path_usage_comparator(const void *a, const void *b);
@@ -1190,7 +1196,7 @@
* for the purpose of generating indexquals, but are not to be searched for
* ORs. (See build_paths_for_OR() for motivation.)
*/
-static List *
+List *
generate_bitmap_or_paths(PlannerInfo *root, RelOptInfo *rel,
List *clauses, List *other_clauses)
{
@@ -2957,6 +2963,208 @@
}
/****************************************************************************
+ * ---- 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, true);
+ if ( typtup ) {
+ mcharOid = typeTypeId(typtup);
+ ReleaseSysCache(typtup);
+ }
+
+ typename = makeTypeName("mvarchar");
+ typtup = LookupTypeName(NULL, typename, NULL, true);
+ 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 ----
****************************************************************************/
@@ -3148,9 +3356,16 @@
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
@@ -3401,6 +3616,14 @@
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 6108928..7dbbc6a 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/pathkeys.c b/src/backend/optimizer/path/pathkeys.c
index 5d953df..0a92f2d 100644
--- a/src/backend/optimizer/path/pathkeys.c
+++ b/src/backend/optimizer/path/pathkeys.c
@@ -1454,7 +1454,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 4b641a2..3d67c46 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);
@@ -677,7 +676,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;
@@ -693,6 +692,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,
@@ -715,6 +715,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;
@@ -2948,7 +2953,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 768c5c7..6e7c7cd 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -1690,6 +1690,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,
@@ -1699,6 +1703,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 d129f8d..d43d508 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 2e9bbe2..a25fb10 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -10830,7 +10830,7 @@ a_expr: c_expr { $$ = $1; }
{ $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "~~", $1, $3, @2); }
| a_expr LIKE a_expr ESCAPE a_expr
{
- FuncCall *n = makeFuncCall(SystemFuncName("like_escape"),
+ FuncCall *n = makeFuncCall(list_make1(makeString("like_escape")),
list_make2($3, $5),
@2);
$$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "~~", $1, (Node *) n, @2);
@@ -10839,7 +10839,7 @@ a_expr: c_expr { $$ = $1; }
{ $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "!~~", $1, $4, @2); }
| a_expr NOT LIKE a_expr ESCAPE a_expr
{
- FuncCall *n = makeFuncCall(SystemFuncName("like_escape"),
+ FuncCall *n = makeFuncCall(list_make1(makeString("like_escape")),
list_make2($4, $6),
@2);
$$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "!~~", $1, (Node *) n, @2);
@@ -10848,7 +10848,7 @@ a_expr: c_expr { $$ = $1; }
{ $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "~~*", $1, $3, @2); }
| a_expr ILIKE a_expr ESCAPE a_expr
{
- FuncCall *n = makeFuncCall(SystemFuncName("like_escape"),
+ FuncCall *n = makeFuncCall(list_make1(makeString("like_escape")),
list_make2($3, $5),
@2);
$$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "~~*", $1, (Node *) n, @2);
@@ -10857,7 +10857,7 @@ a_expr: c_expr { $$ = $1; }
{ $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "!~~*", $1, $4, @2); }
| a_expr NOT ILIKE a_expr ESCAPE a_expr
{
- FuncCall *n = makeFuncCall(SystemFuncName("like_escape"),
+ FuncCall *n = makeFuncCall(list_make1(makeString("like_escape")),
list_make2($4, $6),
@2);
$$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "!~~*", $1, (Node *) n, @2);
@@ -10865,28 +10865,28 @@ a_expr: c_expr { $$ = $1; }
| a_expr SIMILAR TO a_expr %prec SIMILAR
{
- FuncCall *n = makeFuncCall(SystemFuncName("similar_escape"),
+ FuncCall *n = makeFuncCall(list_make1(makeString("similar_escape")),
list_make2($4, makeNullAConst(-1)),
@2);
$$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "~", $1, (Node *) n, @2);
}
| a_expr SIMILAR TO a_expr ESCAPE a_expr
{
- FuncCall *n = makeFuncCall(SystemFuncName("similar_escape"),
+ FuncCall *n = makeFuncCall(list_make1(makeString("similar_escape")),
list_make2($4, $6),
@2);
$$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "~", $1, (Node *) n, @2);
}
| a_expr NOT SIMILAR TO a_expr %prec SIMILAR
{
- FuncCall *n = makeFuncCall(SystemFuncName("similar_escape"),
+ FuncCall *n = makeFuncCall(list_make1(makeString("similar_escape")),
list_make2($5, makeNullAConst(-1)),
@2);
$$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "!~", $1, (Node *) n, @2);
}
| a_expr NOT SIMILAR TO a_expr ESCAPE a_expr
{
- FuncCall *n = makeFuncCall(SystemFuncName("similar_escape"),
+ FuncCall *n = makeFuncCall(list_make1(makeString("similar_escape")),
list_make2($5, $7),
@2);
$$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "!~", $1, (Node *) n, @2);
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index 300136e..e70c2b2 100644
--- a/src/include/nodes/relation.h
+++ b/src/include/nodes/relation.h
@@ -890,6 +890,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 a0bcc82..d38ecd9 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 9b22fda..089a6e3 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -43,6 +43,8 @@ extern void debug_print_rel(PlannerInfo *root, RelOptInfo *rel);
* routines to generate index paths
*/
extern void create_index_paths(PlannerInfo *root, RelOptInfo *rel);
+extern List *generate_bitmap_or_paths(PlannerInfo *root, RelOptInfo *rel,
+ List *clauses, List *other_clauses);
extern bool relation_has_unique_index_for(PlannerInfo *root, RelOptInfo *rel,
List *restrictlist,
List *exprlist, List *oprlist);
@@ -183,9 +185,11 @@ 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);
extern bool has_useful_pathkeys(PlannerInfo *root, RelOptInfo *rel);
+extern void keybased_rewrite_index_paths(PlannerInfo *root, RelOptInfo *rel);
#endif /* PATHS_H */
diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h
index 8bdb7db..fd0d201 100644
--- a/src/include/optimizer/planmain.h
+++ b/src/include/optimizer/planmain.h
@@ -86,7 +86,7 @@ extern ModifyTable *make_modifytable(PlannerInfo *root,
List *withCheckOptionLists, 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 f6f5516..c5b24b1 100644
--- a/src/test/regress/expected/create_index.out
+++ b/src/test/regress/expected/create_index.out
@@ -2655,18 +2655,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 7fe01e6..c06ea1a 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 = {