Files
bsdports/data/postgresql11/files/patch-s00001-1c_FULL_100_EXT.patch
2019-12-26 07:26:06 +00:00

9930 lines
246 KiB
Diff
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

diff --git a/contrib/fasttrun/Makefile b/contrib/fasttrun/Makefile
new file mode 100644
index 00000000000..78e92b86cbe
--- /dev/null
+++ contrib/fasttrun/Makefile
@@ -0,0 +1,17 @@
+MODULE_big = fasttrun
+OBJS = fasttrun.o
+DATA = fasttrun--2.0.sql fasttrun--unpackaged--2.0.sql
+DOCS = README.fasttrun
+REGRESS = fasttrun
+EXTENSION=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 contrib/fasttrun/README.fasttrun
new file mode 100644
index 00000000000..4b1dfdc1e60
--- /dev/null
+++ 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 contrib/fasttrun/expected/fasttrun.out
new file mode 100644
index 00000000000..ef64fa6400e
--- /dev/null
+++ contrib/fasttrun/expected/fasttrun.out
@@ -0,0 +1,115 @@
+CREATE EXTENSION fasttrun;
+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--2.0.sql contrib/fasttrun/fasttrun--2.0.sql
new file mode 100644
index 00000000000..55484024604
--- /dev/null
+++ contrib/fasttrun/fasttrun--2.0.sql
@@ -0,0 +1,7 @@
+\echo Use "CREATE EXTENSION fasttrun" to load this file. \quit
+
+
+CREATE OR REPLACE FUNCTION fasttruncate(text)
+RETURNS void AS 'MODULE_PATHNAME'
+LANGUAGE C RETURNS NULL ON NULL INPUT VOLATILE;
+
diff --git a/contrib/fasttrun/fasttrun--unpackaged--2.0.sql contrib/fasttrun/fasttrun--unpackaged--2.0.sql
new file mode 100644
index 00000000000..f97896ac5cb
--- /dev/null
+++ contrib/fasttrun/fasttrun--unpackaged--2.0.sql
@@ -0,0 +1,4 @@
+\echo Use "CREATE EXTENSION fasttrun FROM unpackaged" to load this file. \quit
+
+ALTER EXTENSION fasttrun ADD function fasttruncate(text);
+
diff --git a/contrib/fasttrun/fasttrun.c contrib/fasttrun/fasttrun.c
new file mode 100644
index 00000000000..ed59cdc8906
--- /dev/null
+++ contrib/fasttrun/fasttrun.c
@@ -0,0 +1,84 @@
+#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>
+#include <utils/regproc.h>
+#include <utils/varlena.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 ) {
+ VacuumParams params;
+ VacuumRelation *rel;
+
+ params.freeze_min_age = -1;
+ params.freeze_table_age = -1;
+ params.multixact_freeze_min_age = -1;
+ params.multixact_freeze_table_age = -1;
+ params.is_wraparound = false;
+ params.log_min_duration = -1;
+
+ rel = makeNode(VacuumRelation);
+ rel->relation = relvar;
+ rel->oid = relOid;
+ rel->va_cols = NULL;
+ vacuum(VACOPT_ANALYZE, list_make1(rel), &params,
+ GetAccessStrategy(BAS_VACUUM), false);
+ }
+
+ PG_RETURN_VOID();
+}
diff --git a/contrib/fasttrun/fasttrun.control contrib/fasttrun/fasttrun.control
new file mode 100644
index 00000000000..00271c75a52
--- /dev/null
+++ contrib/fasttrun/fasttrun.control
@@ -0,0 +1,5 @@
+comment = 'fast transaction-unsafe truncate'
+default_version = '2.0'
+module_pathname = '$libdir/fasttrun'
+relocatable = true
+
diff --git a/contrib/fasttrun/sql/fasttrun.sql contrib/fasttrun/sql/fasttrun.sql
new file mode 100644
index 00000000000..0e3cb6c9beb
--- /dev/null
+++ contrib/fasttrun/sql/fasttrun.sql
@@ -0,0 +1,48 @@
+CREATE EXTENSION fasttrun;
+
+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 contrib/fulleq/Makefile
new file mode 100644
index 00000000000..77ca7af4dbe
--- /dev/null
+++ contrib/fulleq/Makefile
@@ -0,0 +1,45 @@
+MODULE_big = fulleq
+OBJS = fulleq.o
+DOCS = README.fulleq
+REGRESS = fulleq
+DATA_built = fulleq--2.0.sql fulleq--unpackaged--2.0.sql
+EXTENSION=fulleq
+
+ARGTYPE = bool bytea char name int8 int2 int4 text \
+ oid xid cid oidvector float4 float8 abstime reltime macaddr \
+ inet cidr varchar date time timestamp timestamptz \
+ interval timetz
+
+EXTRA_CLEAN = fulleq--2.0.sql fulleq--unpackaged--2.0.sql
+
+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
+
+all: fulleq--2.0.sql fulleq--unpackaged--2.0.sql
+
+fulleq--2.0.sql: fulleq.sql.in
+ echo '\echo Use "CREATE EXTENSION fulleq" to load this file. \quit' > $@
+ for type in $(ARGTYPE); \
+ do \
+ sed -e "s/ARGTYPE/$$type/g" < $< >> $@; \
+ done
+
+fulleq--unpackaged--2.0.sql: fulleq-unpackaged.sql.in
+ echo '\echo Use "CREATE EXTENSION fulleq FROM unpackaged" to load this file. \quit' > $@
+ echo 'DROP OPERATOR CLASS IF EXISTS int2vector_fill_ops USING hash;' >> $@
+ echo 'DROP OPERATOR FAMILY IF EXISTS int2vector_fill_ops USING hash;' >> $@
+ echo 'DROP FUNCTION IF EXISTS fullhash_int2vector(int2vector);' >> $@
+ echo 'DROP OPERATOR IF EXISTS == (int2vector, int2vector);' >> $@
+ echo 'DROP FUNCTION IF EXISTS isfulleq_int2vector(int2vector, int2vector);' >> $@
+ for type in $(ARGTYPE); \
+ do \
+ sed -e "s/ARGTYPE/$$type/g" < $< >> $@; \
+ done
+
diff --git a/contrib/fulleq/README.fulleq contrib/fulleq/README.fulleq
new file mode 100644
index 00000000000..a677c49c41b
--- /dev/null
+++ 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 contrib/fulleq/expected/fulleq.out
new file mode 100644
index 00000000000..452f8593432
--- /dev/null
+++ contrib/fulleq/expected/fulleq.out
@@ -0,0 +1,61 @@
+CREATE EXTENSION fulleq;
+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-unpackaged.sql.in contrib/fulleq/fulleq-unpackaged.sql.in
new file mode 100644
index 00000000000..8d759d8221f
--- /dev/null
+++ contrib/fulleq/fulleq-unpackaged.sql.in
@@ -0,0 +1,10 @@
+-- For ARGTYPE
+
+ALTER EXTENSION fulleq ADD FUNCTION isfulleq_ARGTYPE(ARGTYPE, ARGTYPE);
+
+ALTER EXTENSION fulleq ADD FUNCTION fullhash_ARGTYPE(ARGTYPE);
+
+ALTER EXTENSION fulleq ADD OPERATOR == (ARGTYPE, ARGTYPE);
+
+ALTER EXTENSION fulleq ADD OPERATOR CLASS ARGTYPE_fill_ops USING hash;
+
diff --git a/contrib/fulleq/fulleq.c contrib/fulleq/fulleq.c
new file mode 100644
index 00000000000..689a1ff33cf
--- /dev/null
+++ contrib/fulleq/fulleq.c
@@ -0,0 +1,102 @@
+#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) \
+ ) ); \
+}
+
+
+static Datum
+hashint2vector(PG_FUNCTION_ARGS)
+{
+ int2vector *key = (int2vector *) PG_GETARG_POINTER(0);
+
+ return hash_any((unsigned char *) key->values, key->dim1 * sizeof(int16));
+}
+
+/*
+ * We don't have a complete set of int2vector support routines,
+ * but we need int2vectoreq for catcache indexing.
+ */
+static Datum
+int2vectoreq(PG_FUNCTION_ARGS)
+{
+ int2vector *a = (int2vector *) PG_GETARG_POINTER(0);
+ int2vector *b = (int2vector *) PG_GETARG_POINTER(1);
+
+ if (a->dim1 != b->dim1)
+ PG_RETURN_BOOL(false);
+ PG_RETURN_BOOL(memcmp(a->values, b->values, a->dim1 * sizeof(int16)) == 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( 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 );
+
+/*
+ * v10 drop * support for int2vector equality and hash operator in commit
+ * 5c80642aa8de8393b08cd3cbf612b325cedd98dc, but for compatibility
+ * we still add this operators
+ */
+FULLEQ_FUNC( int2vector , int2vectoreq , hashint2vector );
diff --git a/contrib/fulleq/fulleq.control contrib/fulleq/fulleq.control
new file mode 100644
index 00000000000..c827b9fb4f7
--- /dev/null
+++ contrib/fulleq/fulleq.control
@@ -0,0 +1,5 @@
+comment = 'exact equal operation'
+default_version = '2.0'
+module_pathname = '$libdir/fulleq'
+relocatable = true
+
diff --git a/contrib/fulleq/fulleq.sql.in contrib/fulleq/fulleq.sql.in
new file mode 100644
index 00000000000..55e980e1235
--- /dev/null
+++ contrib/fulleq/fulleq.sql.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 contrib/fulleq/sql/fulleq.sql
new file mode 100644
index 00000000000..e491e276486
--- /dev/null
+++ contrib/fulleq/sql/fulleq.sql
@@ -0,0 +1,14 @@
+CREATE EXTENSION fulleq;
+
+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 contrib/mchar/Changes
new file mode 100644
index 00000000000..b7f6e0c5718
--- /dev/null
+++ contrib/mchar/Changes
@@ -0,0 +1,20 @@
+2.0 make an extension
+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 contrib/mchar/Makefile
new file mode 100644
index 00000000000..f1b58b64c73
--- /dev/null
+++ contrib/mchar/Makefile
@@ -0,0 +1,28 @@
+MODULE_big = mchar
+OBJS = mchar_io.o mchar_proc.o mchar_op.o mchar_recode.o \
+ mchar_like.o
+EXTENSION=mchar
+DATA = mchar--2.0.sql mchar--unpackaged--2.0.sql
+DOCS = README.mchar
+REGRESS = init mchar mvarchar mm like compat
+ENCODING = UTF8
+
+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 contrib/mchar/README.mchar
new file mode 100644
index 00000000000..479a7d1f40a
--- /dev/null
+++ 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 contrib/mchar/expected/compat.out
new file mode 100644
index 00000000000..480a286e8f6
--- /dev/null
+++ 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 contrib/mchar/expected/init.out
new file mode 100644
index 00000000000..7bae978ec35
--- /dev/null
+++ contrib/mchar/expected/init.out
@@ -0,0 +1,18 @@
+CREATE EXTENSION mchar;
+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 contrib/mchar/expected/like.out
new file mode 100644
index 00000000000..3a57082e45a
--- /dev/null
+++ 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 contrib/mchar/expected/mchar.out
new file mode 100644
index 00000000000..f6c592fd16f
--- /dev/null
+++ contrib/mchar/expected/mchar.out
@@ -0,0 +1,382 @@
+-- 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)
+
+--Note: here we use different space symbols, be carefull to copy it!
+select v, count(*) from
+(values (1, '4 242'::mchar), (2, '4 242'), (3, 'aSDF'), (4, 'asdf')) as t(i,v) group by v;
+ v | count
+-------+-------
+ aSDF | 2
+ 4 242 | 2
+(2 rows)
+
+set enable_hashagg=off;
+select v, count(*) from
+(values (1, '4 242'::mchar), (2, '4 242'), (3, 'aSDF'), (4, 'asdf')) as t(i,v) group by v;
+ v | count
+-------+-------
+ 4 242 | 2
+ aSDF | 2
+(2 rows)
+
+reset enable_hashagg;
diff --git a/contrib/mchar/expected/mm.out contrib/mchar/expected/mm.out
new file mode 100644
index 00000000000..c5b36c21611
--- /dev/null
+++ contrib/mchar/expected/mm.out
@@ -0,0 +1,855 @@
+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;
+select * from (values
+ ('е'::mchar),('ё'),('еа'),('еб'),('ее'),('еж'),('ёа'),('ёб'),('ёё'),('ёж'),('ёе'),('её'))
+ z order by 1;
+ column1
+---------
+ е
+ ё
+ еа
+ ёа
+ еб
+ ёб
+ ее
+ её
+ ёе
+ ёё
+ еж
+ ёж
+(12 rows)
+
+select 'ё'::mchar = 'е';
+ ?column?
+----------
+ f
+(1 row)
+
+select 'Ё'::mchar = 'Е';
+ ?column?
+----------
+ f
+(1 row)
+
+select 'й'::mchar = 'и';
+ ?column?
+----------
+ f
+(1 row)
+
+select 'Й'::mchar = 'И';
+ ?column?
+----------
+ f
+(1 row)
+
+select mvarchar_icase_cmp('ёа','еб'), mvarchar_icase_cmp('еб','ё'),
+ mvarchar_icase_cmp('ё', 'ёа');
+ mvarchar_icase_cmp | mvarchar_icase_cmp | mvarchar_icase_cmp
+--------------------+--------------------+--------------------
+ -1 | 1 | -1
+(1 row)
+
diff --git a/contrib/mchar/expected/mvarchar.out contrib/mchar/expected/mvarchar.out
new file mode 100644
index 00000000000..5c866b43e71
--- /dev/null
+++ 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--2.0.sql contrib/mchar/mchar--2.0.sql
new file mode 100644
index 00000000000..2a49a13abf6
--- /dev/null
+++ contrib/mchar/mchar--2.0.sql
@@ -0,0 +1,1324 @@
+\echo Use "CREATE EXTENSION mchar" to load this file. \quit
+
+-- 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);
+
+
diff --git a/contrib/mchar/mchar--unpackaged--2.0.sql contrib/mchar/mchar--unpackaged--2.0.sql
new file mode 100644
index 00000000000..1acc4ccec1e
--- /dev/null
+++ contrib/mchar/mchar--unpackaged--2.0.sql
@@ -0,0 +1,404 @@
+\echo Use "CREATE EXTENSION mchar FROM unpackaged" to load this file. \quit
+
+-- I/O functions
+
+ALTER EXTENSION mchar ADD FUNCTION mchartypmod_in(cstring[]);
+
+ALTER EXTENSION mchar ADD FUNCTION mchartypmod_out(int4);
+
+ALTER EXTENSION mchar ADD FUNCTION mchar_in(cstring);
+
+ALTER EXTENSION mchar ADD FUNCTION mchar_out(mchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mchar_send(mchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mchar_recv(internal);
+
+ALTER EXTENSION mchar ADD TYPE mchar;
+
+ALTER EXTENSION mchar ADD FUNCTION mchar(mchar, integer, boolean);
+
+ALTER EXTENSION mchar ADD CAST (mchar as mchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mvarchar_in(cstring);
+
+ALTER EXTENSION mchar ADD FUNCTION mvarchar_out(mvarchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mvarchar_send(mvarchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mvarchar_recv(internal);
+
+ALTER EXTENSION mchar ADD TYPE mvarchar;
+
+ALTER EXTENSION mchar ADD FUNCTION mvarchar(mvarchar, integer, boolean);
+
+ALTER EXTENSION mchar ADD CAST (mvarchar as mvarchar);
+
+--Operations and functions
+
+ALTER EXTENSION mchar ADD FUNCTION length(mchar);
+
+ALTER EXTENSION mchar ADD FUNCTION upper(mchar);
+
+ALTER EXTENSION mchar ADD FUNCTION lower(mchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mchar_hash(mchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mchar_concat(mchar, mchar);
+
+ALTER EXTENSION mchar ADD OPERATOR || (mchar, mchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mchar_like(mchar, mvarchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mchar_notlike(mchar, mvarchar);
+
+ALTER EXTENSION mchar ADD OPERATOR ~~ (mchar, mvarchar);
+
+ALTER EXTENSION mchar ADD OPERATOR !~~ (mchar, mvarchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mchar_regexeq(mchar, mchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mchar_regexne(mchar, mchar);
+
+ALTER EXTENSION mchar ADD OPERATOR ~ (mchar, mchar);
+
+ALTER EXTENSION mchar ADD OPERATOR !~ (mchar, mchar);
+
+ALTER EXTENSION mchar ADD FUNCTION similar_escape(mchar, mchar);
+
+ALTER EXTENSION mchar ADD FUNCTION length(mvarchar);
+
+ALTER EXTENSION mchar ADD FUNCTION upper(mvarchar);
+
+ALTER EXTENSION mchar ADD FUNCTION lower(mvarchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mvarchar_hash(mvarchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mvarchar_concat(mvarchar, mvarchar);
+
+ALTER EXTENSION mchar ADD OPERATOR || (mvarchar, mvarchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mvarchar_like(mvarchar, mvarchar);
+
+ALTER EXTENSION mchar ADD FUNCTION like_escape(mvarchar, mvarchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mvarchar_notlike(mvarchar, mvarchar);
+
+ALTER EXTENSION mchar ADD OPERATOR ~~ (mvarchar, mvarchar);
+
+ALTER EXTENSION mchar ADD OPERATOR !~~ (mvarchar, mvarchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mvarchar_regexeq(mvarchar, mvarchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mvarchar_regexne(mvarchar, mvarchar);
+
+ALTER EXTENSION mchar ADD OPERATOR ~ (mvarchar, mvarchar);
+
+ALTER EXTENSION mchar ADD OPERATOR !~ (mvarchar, mvarchar);
+
+ALTER EXTENSION mchar ADD FUNCTION similar_escape(mvarchar, mvarchar);
+
+ALTER EXTENSION mchar ADD FUNCTION substr (mchar, int4);
+
+ALTER EXTENSION mchar ADD FUNCTION substr (mchar, int4, int4);
+
+ALTER EXTENSION mchar ADD FUNCTION substr (mvarchar, int4);
+
+ALTER EXTENSION mchar ADD FUNCTION substr (mvarchar, int4, int4);
+
+-- Comparing
+-- MCHAR
+
+ALTER EXTENSION mchar ADD FUNCTION mchar_icase_cmp(mchar, mchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mchar_icase_eq(mchar, mchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mchar_icase_ne(mchar, mchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mchar_icase_lt(mchar, mchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mchar_icase_le(mchar, mchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mchar_icase_gt(mchar, mchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mchar_icase_ge(mchar, mchar);
+
+ALTER EXTENSION mchar ADD OPERATOR < (mchar, mchar);
+
+ALTER EXTENSION mchar ADD OPERATOR > (mchar, mchar);
+
+ALTER EXTENSION mchar ADD OPERATOR <= (mchar, mchar);
+
+ALTER EXTENSION mchar ADD OPERATOR >= (mchar, mchar);
+
+ALTER EXTENSION mchar ADD OPERATOR = (mchar, mchar);
+
+ALTER EXTENSION mchar ADD OPERATOR <> (mchar, mchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mchar_case_cmp(mchar, mchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mchar_case_eq(mchar, mchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mchar_case_ne(mchar, mchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mchar_case_lt(mchar, mchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mchar_case_le(mchar, mchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mchar_case_gt(mchar, mchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mchar_case_ge(mchar, mchar);
+
+
+ALTER EXTENSION mchar ADD OPERATOR &< (mchar, mchar);
+
+ALTER EXTENSION mchar ADD OPERATOR &> (mchar, mchar);
+
+ALTER EXTENSION mchar ADD OPERATOR &<= (mchar, mchar);
+
+ALTER EXTENSION mchar ADD OPERATOR &>= (mchar, mchar);
+
+ALTER EXTENSION mchar ADD OPERATOR &= (mchar, mchar);
+
+ALTER EXTENSION mchar ADD OPERATOR &<> (mchar, mchar);
+
+--MVARCHAR
+
+ALTER EXTENSION mchar ADD FUNCTION mvarchar_icase_cmp(mvarchar, mvarchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mvarchar_icase_eq(mvarchar, mvarchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mvarchar_icase_ne(mvarchar, mvarchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mvarchar_icase_lt(mvarchar, mvarchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mvarchar_icase_le(mvarchar, mvarchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mvarchar_icase_gt(mvarchar, mvarchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mvarchar_icase_ge(mvarchar, mvarchar);
+
+ALTER EXTENSION mchar ADD OPERATOR < (mvarchar, mvarchar);
+
+ALTER EXTENSION mchar ADD OPERATOR > (mvarchar, mvarchar);
+
+ALTER EXTENSION mchar ADD OPERATOR <= (mvarchar, mvarchar);
+
+ALTER EXTENSION mchar ADD OPERATOR >= (mvarchar, mvarchar);
+
+ALTER EXTENSION mchar ADD OPERATOR = (mvarchar, mvarchar);
+
+ALTER EXTENSION mchar ADD OPERATOR <> (mvarchar, mvarchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mvarchar_case_cmp(mvarchar, mvarchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mvarchar_case_eq(mvarchar, mvarchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mvarchar_case_ne(mvarchar, mvarchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mvarchar_case_lt(mvarchar, mvarchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mvarchar_case_le(mvarchar, mvarchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mvarchar_case_gt(mvarchar, mvarchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mvarchar_case_ge(mvarchar, mvarchar);
+
+ALTER EXTENSION mchar ADD OPERATOR &< (mvarchar, mvarchar);
+
+ALTER EXTENSION mchar ADD OPERATOR &> (mvarchar, mvarchar);
+
+ALTER EXTENSION mchar ADD OPERATOR &<= (mvarchar, mvarchar);
+
+ALTER EXTENSION mchar ADD OPERATOR &>= (mvarchar, mvarchar);
+
+ALTER EXTENSION mchar ADD OPERATOR &= (mvarchar, mvarchar);
+
+ALTER EXTENSION mchar ADD OPERATOR &<> (mvarchar, mvarchar);
+
+-- MCHAR <> MVARCHAR
+
+ALTER EXTENSION mchar ADD FUNCTION mc_mv_icase_cmp(mchar, mvarchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mc_mv_icase_eq(mchar, mvarchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mc_mv_icase_ne(mchar, mvarchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mc_mv_icase_lt(mchar, mvarchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mc_mv_icase_le(mchar, mvarchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mc_mv_icase_gt(mchar, mvarchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mc_mv_icase_ge(mchar, mvarchar);
+
+ALTER EXTENSION mchar ADD OPERATOR < (mchar, mvarchar);
+
+ALTER EXTENSION mchar ADD OPERATOR > (mchar, mvarchar);
+
+ALTER EXTENSION mchar ADD OPERATOR <= (mchar, mvarchar);
+
+ALTER EXTENSION mchar ADD OPERATOR >= (mchar, mvarchar);
+
+ALTER EXTENSION mchar ADD OPERATOR = (mchar, mvarchar);
+
+ALTER EXTENSION mchar ADD OPERATOR <> (mchar, mvarchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mc_mv_case_cmp(mchar, mvarchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mc_mv_case_eq(mchar, mvarchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mc_mv_case_ne(mchar, mvarchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mc_mv_case_lt(mchar, mvarchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mc_mv_case_le(mchar, mvarchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mc_mv_case_gt(mchar, mvarchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mc_mv_case_ge(mchar, mvarchar);
+
+ALTER EXTENSION mchar ADD OPERATOR &< (mchar, mvarchar);
+
+ALTER EXTENSION mchar ADD OPERATOR &> (mchar, mvarchar);
+
+ALTER EXTENSION mchar ADD OPERATOR &<= (mchar, mvarchar);
+
+ALTER EXTENSION mchar ADD OPERATOR &>= (mchar, mvarchar);
+
+ALTER EXTENSION mchar ADD OPERATOR &= (mchar, mvarchar);
+
+ALTER EXTENSION mchar ADD OPERATOR &<> (mchar, mvarchar);
+
+-- MVARCHAR <> MCHAR
+
+ALTER EXTENSION mchar ADD FUNCTION mv_mc_icase_cmp(mvarchar, mchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mv_mc_icase_eq(mvarchar, mchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mv_mc_icase_ne(mvarchar, mchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mv_mc_icase_lt(mvarchar, mchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mv_mc_icase_le(mvarchar, mchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mv_mc_icase_gt(mvarchar, mchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mv_mc_icase_ge(mvarchar, mchar);
+
+ALTER EXTENSION mchar ADD OPERATOR < (mvarchar, mchar);
+
+ALTER EXTENSION mchar ADD OPERATOR > (mvarchar, mchar);
+
+ALTER EXTENSION mchar ADD OPERATOR <= (mvarchar, mchar);
+
+ALTER EXTENSION mchar ADD OPERATOR >= (mvarchar, mchar);
+
+ALTER EXTENSION mchar ADD OPERATOR = (mvarchar, mchar);
+
+ALTER EXTENSION mchar ADD OPERATOR <> (mvarchar, mchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mv_mc_case_cmp(mvarchar, mchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mv_mc_case_eq(mvarchar, mchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mv_mc_case_ne(mvarchar, mchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mv_mc_case_lt(mvarchar, mchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mv_mc_case_le(mvarchar, mchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mv_mc_case_gt(mvarchar, mchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mv_mc_case_ge(mvarchar, mchar);
+
+ALTER EXTENSION mchar ADD OPERATOR &< (mvarchar, mchar);
+
+ALTER EXTENSION mchar ADD OPERATOR &> (mvarchar, mchar);
+
+ALTER EXTENSION mchar ADD OPERATOR &<= (mvarchar, mchar);
+
+ALTER EXTENSION mchar ADD OPERATOR &>= (mvarchar, mchar);
+
+ALTER EXTENSION mchar ADD OPERATOR &= (mvarchar, mchar);
+
+ALTER EXTENSION mchar ADD OPERATOR &<> (mvarchar, mchar);
+
+-- MCHAR - VARCHAR operations
+
+ALTER EXTENSION mchar ADD FUNCTION mchar_mvarchar_concat(mchar, mvarchar);
+
+ALTER EXTENSION mchar ADD OPERATOR || (mchar, mvarchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mvarchar_mchar_concat(mvarchar, mchar);
+
+ALTER EXTENSION mchar ADD OPERATOR || (mvarchar, mchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mvarchar_mchar(mvarchar, integer, boolean);
+
+ALTER EXTENSION mchar ADD CAST (mvarchar as mchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mchar_mvarchar(mchar, integer, boolean);
+
+ALTER EXTENSION mchar ADD CAST (mchar as mvarchar);
+
+-- Aggregates
+
+ALTER EXTENSION mchar ADD FUNCTION mchar_larger(mchar, mchar);
+
+ALTER EXTENSION mchar ADD AGGREGATE max (mchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mchar_smaller(mchar, mchar);
+
+ALTER EXTENSION mchar ADD AGGREGATE min (mchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mvarchar_larger(mvarchar, mvarchar);
+
+ALTER EXTENSION mchar ADD AGGREGATE max (mvarchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mvarchar_smaller(mvarchar, mvarchar);
+
+ALTER EXTENSION mchar ADD AGGREGATE min (mvarchar);
+
+-- B-tree support
+ALTER EXTENSION mchar ADD OPERATOR FAMILY icase_ops USING btree;
+
+ALTER EXTENSION mchar ADD OPERATOR FAMILY case_ops USING btree;
+
+ALTER EXTENSION mchar ADD OPERATOR CLASS mchar_icase_ops USING btree;
+
+ALTER EXTENSION mchar ADD OPERATOR CLASS mchar_case_ops USING btree;
+
+ALTER EXTENSION mchar ADD OPERATOR CLASS mchar_icase_ops USING hash;
+
+ALTER EXTENSION mchar ADD OPERATOR CLASS mvarchar_icase_ops USING btree;
+
+ALTER EXTENSION mchar ADD OPERATOR CLASS mvarchar_case_ops USING btree;
+
+ALTER EXTENSION mchar ADD OPERATOR CLASS mvarchar_icase_ops USING hash;
+
+
+-- Index support for LIKE
+
+--mchar_pattern_fixed_prefix could be with wrong number of arguments
+ALTER EXTENSION mchar ADD FUNCTION mchar_pattern_fixed_prefix;
+
+ALTER EXTENSION mchar ADD FUNCTION mchar_greaterstring(internal);
+
+ALTER EXTENSION mchar ADD FUNCTION isfulleq_mchar(mchar, mchar);
+
+ALTER EXTENSION mchar ADD FUNCTION fullhash_mchar(mchar);
+
+ALTER EXTENSION mchar ADD OPERATOR == (mchar, mchar);
+
+ALTER EXTENSION mchar ADD OPERATOR CLASS mchar_fill_ops USING hash;
+
+ALTER EXTENSION mchar ADD FUNCTION isfulleq_mvarchar(mvarchar, mvarchar);
+
+ALTER EXTENSION mchar ADD FUNCTION fullhash_mvarchar(mvarchar);
+
+ALTER EXTENSION mchar ADD OPERATOR == (mvarchar, mvarchar);
+
+ALTER EXTENSION mchar ADD OPERATOR CLASS mvarchar_fill_ops USING hash;
+
+
diff --git a/contrib/mchar/mchar.control contrib/mchar/mchar.control
new file mode 100644
index 00000000000..1ddda5a5206
--- /dev/null
+++ contrib/mchar/mchar.control
@@ -0,0 +1,6 @@
+# mchar extension
+comment = 'SQL Server text type'
+default_version = '2.0'
+module_pathname = '$libdir/mchar'
+relocatable = true
+
diff --git a/contrib/mchar/mchar.h contrib/mchar/mchar.h
new file mode 100644
index 00000000000..a88a0e1eb76
--- /dev/null
+++ contrib/mchar/mchar.h
@@ -0,0 +1,63 @@
+#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 == ' ' */
+
+Datum hash_uchar( UChar *s, int len );
+#endif
diff --git a/contrib/mchar/mchar_io.c contrib/mchar/mchar_io.c
new file mode 100644
index 00000000000..9179412ae5d
--- /dev/null
+++ 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 contrib/mchar/mchar_like.c
new file mode 100644
index 00000000000..c1a09358f4b
--- /dev/null
+++ 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);
+ int 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);
+ int result;
+ bool 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 = DatumGetInt32( DirectFunctionCall2(
+ mchar_like,
+ PG_GETARG_DATUM(0),
+ PG_GETARG_DATUM(1)
+ )) == LIKE_TRUE;
+
+ 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 contrib/mchar/mchar_op.c
new file mode 100644
index 00000000000..4694d9cf3c3
--- /dev/null
+++ 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 contrib/mchar/mchar_proc.c
new file mode 100644
index 00000000000..edabfb5eb66
--- /dev/null
+++ contrib/mchar/mchar_proc.c
@@ -0,0 +1,315 @@
+#include "mchar.h"
+#include "mb/pg_wchar.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);
+}
+
+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 contrib/mchar/mchar_recode.c
new file mode 100644
index 00000000000..12bc6d4f3aa
--- /dev/null
+++ contrib/mchar/mchar_recode.c
@@ -0,0 +1,166 @@
+#include "mchar.h"
+#include "access/hash.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) {
+
+ createUObjs();
+
+ return (int)ucol_strcoll( colCaseInsensitive,
+ a, alen,
+ b, blen);
+}
+
+int
+UCharCompare(UChar * a, int alen, UChar *b, int blen) {
+
+ createUObjs();
+
+ return (int)ucol_strcoll( colCaseSensitive,
+ a, alen,
+ b, blen);
+}
+
+Datum
+hash_uchar( UChar *s, int len ) {
+ int32 length = INT_MAX, i;
+ Datum res;
+ uint8 *d;
+
+ if ( len == 0 )
+ return hash_any( NULL, 0 );
+
+ createUObjs();
+
+ for(i=2;; i*=2)
+ {
+ d = palloc(len * i);
+ length = ucol_getSortKey(colCaseInsensitive, s, len, d, len*i);
+
+ if (length == 0)
+ elog(ERROR,"ICU ucol_getSortKey fails");
+
+ if (length < len*i)
+ break;
+
+ pfree(d);
+ }
+
+ res = hash_any( (unsigned char*) d, length);
+
+ pfree(d);
+
+ return res;
+}
+
diff --git a/contrib/mchar/sql/compat.sql contrib/mchar/sql/compat.sql
new file mode 100644
index 00000000000..d5b6a986960
--- /dev/null
+++ 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 contrib/mchar/sql/init.sql
new file mode 100644
index 00000000000..04310044458
--- /dev/null
+++ contrib/mchar/sql/init.sql
@@ -0,0 +1,23 @@
+CREATE EXTENSION mchar;
+
+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 contrib/mchar/sql/like.sql
new file mode 100644
index 00000000000..aebf92446fa
--- /dev/null
+++ 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 contrib/mchar/sql/mchar.sql
new file mode 100644
index 00000000000..2ec0e659f4f
--- /dev/null
+++ contrib/mchar/sql/mchar.sql
@@ -0,0 +1,90 @@
+-- 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;
+
+
+--Note: here we use different space symbols, be carefull to copy it!
+select v, count(*) from
+(values (1, '4 242'::mchar), (2, '4 242'), (3, 'aSDF'), (4, 'asdf')) as t(i,v) group by v;
+set enable_hashagg=off;
+select v, count(*) from
+(values (1, '4 242'::mchar), (2, '4 242'), (3, 'aSDF'), (4, 'asdf')) as t(i,v) group by v;
+reset enable_hashagg;
diff --git a/contrib/mchar/sql/mm.sql contrib/mchar/sql/mm.sql
new file mode 100644
index 00000000000..2e11b937040
--- /dev/null
+++ contrib/mchar/sql/mm.sql
@@ -0,0 +1,196 @@
+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;
+
+select * from (values
+ ('е'::mchar),('ё'),('еа'),('еб'),('ее'),('еж'),('ёа'),('ёб'),('ёё'),('ёж'),('ёе'),('её'))
+ z order by 1;
+
+select 'ё'::mchar = 'е';
+select 'Ё'::mchar = 'Е';
+select 'й'::mchar = 'и';
+select 'Й'::mchar = 'И';
+
+select mvarchar_icase_cmp('ёа','еб'), mvarchar_icase_cmp('еб','ё'),
+ mvarchar_icase_cmp('ё', 'ёа');
diff --git a/contrib/mchar/sql/mvarchar.sql contrib/mchar/sql/mvarchar.sql
new file mode 100644
index 00000000000..91b0981075d
--- /dev/null
+++ 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/src/backend/nodes/outfuncs.c src/backend/nodes/outfuncs.c
index f5d786d79ad..9efe66f7519 100644
--- a/src/backend/nodes/outfuncs.c
+++ src/backend/nodes/outfuncs.c
@@ -1943,6 +1943,7 @@ _outAppendPath(StringInfo str, const AppendPath *node)
WRITE_NODE_FIELD(partitioned_rels);
WRITE_NODE_FIELD(subpaths);
WRITE_INT_FIELD(first_partial_path);
+ WRITE_BOOL_FIELD(pull_tlist);
}
static void
diff --git a/src/backend/optimizer/path/Makefile src/backend/optimizer/path/Makefile
index 6864a621327..ef250e0f248 100644
--- a/src/backend/optimizer/path/Makefile
+++ 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 src/backend/optimizer/path/allpaths.c
index 56ccde977cc..1b8bdabd4ac 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ src/backend/optimizer/path/allpaths.c
@@ -729,6 +729,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);
}
@@ -1601,7 +1604,8 @@ add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
if (subpaths_valid)
add_path(rel, (Path *) create_append_path(root, rel, subpaths, NIL,
NULL, 0, false,
- partitioned_rels, -1));
+ partitioned_rels, -1,
+ false, NIL));
/*
* Consider an append of unordered, unparameterized partial paths. Make
@@ -1644,7 +1648,8 @@ add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
appendpath = create_append_path(root, rel, NIL, partial_subpaths,
NULL, parallel_workers,
enable_parallel_append,
- partitioned_rels, -1);
+ partitioned_rels, -1,
+ false, NIL);
/*
* Make sure any subsequent partial paths use the same row count
@@ -1693,7 +1698,8 @@ add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
appendpath = create_append_path(root, rel, pa_nonpartial_subpaths,
pa_partial_subpaths,
NULL, parallel_workers, true,
- partitioned_rels, partial_rows);
+ partitioned_rels, partial_rows,
+ false, NIL);
add_partial_path(rel, (Path *) appendpath);
}
@@ -1755,7 +1761,8 @@ add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
add_path(rel, (Path *)
create_append_path(root, rel, subpaths, NIL,
required_outer, 0, false,
- partitioned_rels, -1));
+ partitioned_rels, -1,
+ false, NIL));
}
}
@@ -2024,7 +2031,8 @@ set_dummy_rel_pathlist(RelOptInfo *rel)
/* Set up the dummy path */
add_path(rel, (Path *) create_append_path(NULL, rel, NIL, NIL,
rel->lateral_relids,
- 0, false, NIL, -1));
+ 0, false, NIL, -1,
+ false, NIL));
/*
* We set the cheapest-path fields immediately, just in case they were
diff --git a/src/backend/optimizer/path/appendorpath.c src/backend/optimizer/path/appendorpath.c
new file mode 100644
index 00000000000..3127d7f59bf
--- /dev/null
+++ src/backend/optimizer/path/appendorpath.c
@@ -0,0 +1,983 @@
+/*
+ * 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/fmgroids.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, false);
+ }
+
+ /*
+ * 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(root, rel, indexPaths, NIL, NULL, 0,
+ false, NIL, -1.0, 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(root, rel, indexPaths, NIL, NULL,
+ 0, false, NIL, -1.0,
+ true, pathkeys);
+ add_path(rel, (Path *) appendidxpath);
+ }
+ } else {
+ appendidxpath = create_append_path(root, rel, bestpath->bitmapquals,
+ NIL, NULL,
+ 0, false, NIL, -1.0, 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), false ) )
+ 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 ) == 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, false) )
+ /*
+ * a:( Var < 15 ) > b:( Var <= 10 )
+ */
+ return 1;
+ else if ( predicate_implied_by((List*)b->expr, (List*)a->expr, false) )
+ /*
+ * 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) == 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;
+}
+
+static IndexOptInfo *sortingIndex = NULL;
+static bool volatile unableToDefine = false;
+
+/*
+ * 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;
+
+ if (res == 0 && ae->strategy == be->strategy &&
+ be->strategy != BTEqualStrategyNumber &&
+ equal(ae->expr, be->expr))
+ {
+ /*
+ * It's impossible to get defined order for non-eq the same clauses
+ */
+ unableToDefine = true;
+ PG_RE_THROW(); /* it should be PG_THROW(), but it's the same */
+ }
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * 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 src/backend/optimizer/path/indxpath.c
index 658700c2a43..d9b9b6ce873 100644
--- a/src/backend/optimizer/path/indxpath.c
+++ src/backend/optimizer/path/indxpath.c
@@ -39,6 +39,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"
/* XXX see PartCollMatchesExprColl */
#define IndexCollMatchesExprColl(idxcollation, exprcollation) \
@@ -115,8 +123,6 @@ static List *build_index_paths(PlannerInfo *root, RelOptInfo *rel,
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);
@@ -1260,7 +1266,7 @@ build_paths_for_OR(PlannerInfo *root, RelOptInfo *rel,
* 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)
{
@@ -3298,6 +3304,224 @@ match_index_to_operand(Node *operand,
return false;
}
+/****************************************************************************
+ * ---- 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 Oid
+findTypeOid(char *typname)
+{
+ CatCList *catlist;
+ HeapTuple tup;
+ int n_members;
+ Oid typoid;
+
+ catlist = SearchSysCacheList(TYPENAMENSP, 1,
+ CStringGetDatum(typname), 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' type",
+ n_members, typname);
+ return InvalidOid;
+ }
+
+ tup = &catlist->members[0]->tuple;
+
+ typoid = HeapTupleGetOid(tup);
+
+ ReleaseSysCacheList(catlist);
+
+ return typoid;
+}
+
+static bool
+fillMCharOIDS() {
+ CatCList *catlist;
+ HeapTuple tup;
+ char *funcname = "mchar_pattern_fixed_prefix";
+ int n_members;
+
+ catlist = SearchSysCacheList(PROCNAMEARGSNSP, 1,
+ CStringGetDatum(funcname), 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 ) {
+ char *quals_funcname = "mchar_greaterstring";
+ Oid tmp_mmPFPOid = HeapTupleGetOid(tup);
+
+ ReleaseSysCacheList(catlist);
+
+ mcharOid = findTypeOid("mchar");
+ mvarcharOid = findTypeOid("mvarchar");
+
+ 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);
+ 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 ----
****************************************************************************/
@@ -3490,9 +3714,16 @@ match_special_index_operator(Expr *clause, Oid opfamily, Oid idxcollation,
pfree(prefix);
}
- /* done if the expression doesn't look indexable */
- if (!isIndexable)
+ if ( !isIndexable ) {
+ /* done if the expression doesn't look indexable,
+ but we should previously check it for mchar/mvarchar types */
+ if ( mchar_pattern_fixed_prefix(expr_op, InvalidOid,
+ patt, Pattern_Type_Like,
+ &prefix, NULL) != Pattern_Prefix_None ) {
+ return true;
+ }
return false;
+ }
/*
* Must also check that index's opfamily supports the operators we will
@@ -3748,6 +3979,14 @@ expand_indexqual_opclause(RestrictInfo *rinfo, Oid opfamily, Oid idxcollation)
Const *patt = (Const *) rightop;
Const *prefix = NULL;
Pattern_Prefix_Status pstatus;
+ Oid leftTypeOid;
+
+ pstatus = mchar_pattern_fixed_prefix(expr_op, opfamily,
+ patt, Pattern_Type_Like,
+ &prefix, &leftTypeOid);
+
+ if ( pstatus != Pattern_Prefix_None )
+ return mchar_prefix_quals(leftop, leftTypeOid, opfamily, prefix, pstatus);
/*
* LIKE and regex operators are not members of any btree index opfamily,
diff --git a/src/backend/optimizer/path/joinrels.c src/backend/optimizer/path/joinrels.c
index 7079b6ac3fe..b76e0d8edb0 100644
--- a/src/backend/optimizer/path/joinrels.c
+++ src/backend/optimizer/path/joinrels.c
@@ -1255,7 +1255,8 @@ mark_dummy_rel(RelOptInfo *rel)
/* Set up the dummy path */
add_path(rel, (Path *) create_append_path(NULL, rel, NIL, NIL,
rel->lateral_relids,
- 0, false, NIL, -1));
+ 0, false, NIL, -1,
+ false, NIL));
/* Set or update cheapest_total_path and related fields */
set_cheapest(rel);
diff --git a/src/backend/optimizer/path/pathkeys.c src/backend/optimizer/path/pathkeys.c
index 5579c6b0679..4fa9fd2eeab 100644
--- a/src/backend/optimizer/path/pathkeys.c
+++ src/backend/optimizer/path/pathkeys.c
@@ -1625,7 +1625,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 src/backend/optimizer/plan/createplan.c
index 5f6d2bad7be..b6dd080565c 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ src/backend/optimizer/plan/createplan.c
@@ -154,7 +154,6 @@ static Node *replace_nestloop_params(PlannerInfo *root, Node *expr);
static Node *replace_nestloop_params_mutator(Node *node, PlannerInfo *root);
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_generic_path_info(Plan *dest, Path *src);
@@ -1027,7 +1026,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;
RelOptInfo *rel = best_path->path.parent;
@@ -1047,6 +1046,7 @@ create_append_plan(PlannerInfo *root, AppendPath *best_path)
/* Generate a Result plan with constant-FALSE gating qual */
Plan *plan;
+ tlist = build_path_tlist(root, &best_path->path);
plan = (Plan *) make_result(tlist,
(Node *) list_make1(makeBoolConst(false,
false)),
@@ -1115,6 +1115,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, best_path->first_partial_path,
tlist, best_path->partitioned_rels,
partpruneinfo);
@@ -4534,7 +4539,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/planner.c src/backend/optimizer/plan/planner.c
index 8e152078c59..6a3f4667e89 100644
--- a/src/backend/optimizer/plan/planner.c
+++ src/backend/optimizer/plan/planner.c
@@ -1590,7 +1590,8 @@ inheritance_planner(PlannerInfo *root)
/* Make a dummy path, cf set_dummy_rel_pathlist() */
dummy_path = (Path *) create_append_path(NULL, final_rel, NIL, NIL,
- NULL, 0, false, NIL, -1);
+ NULL, 0, false, NIL, -1,
+ false, NIL);
/* These lists must be nonempty to make a valid ModifyTable node */
subpaths = list_make1(dummy_path);
@@ -3958,7 +3959,8 @@ create_degenerate_grouping_paths(PlannerInfo *root, RelOptInfo *input_rel,
0,
false,
NIL,
- -1);
+ -1,
+ false, NIL);
}
else
{
diff --git a/src/backend/optimizer/plan/setrefs.c src/backend/optimizer/plan/setrefs.c
index 80e6e0da0d6..ea7b1ca1d7c 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ src/backend/optimizer/plan/setrefs.c
@@ -2252,6 +2252,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);
+
/* Look for the var in the input tlists, first in the outer */
if (context->outer_itlist)
{
@@ -2266,6 +2270,9 @@ fix_join_expr_mutator(Node *node, fix_join_expr_context *context)
/* then in the inner. */
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/prep/prepunion.c src/backend/optimizer/prep/prepunion.c
index c8465a14af5..59cce11b7f0 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ src/backend/optimizer/prep/prepunion.c
@@ -656,7 +656,7 @@ generate_union_paths(SetOperationStmt *op, PlannerInfo *root,
* Append the child results together.
*/
path = (Path *) create_append_path(root, result_rel, pathlist, NIL,
- NULL, 0, false, NIL, -1);
+ NULL, 0, false, NIL, -1, false, NIL);
/*
* For UNION ALL, we just need the Append path. For UNION, need to add
@@ -712,7 +712,7 @@ generate_union_paths(SetOperationStmt *op, PlannerInfo *root,
ppath = (Path *)
create_append_path(root, result_rel, NIL, partial_pathlist,
NULL, parallel_workers, enable_parallel_append,
- NIL, -1);
+ NIL, -1, false, NIL);
ppath = (Path *)
create_gather_path(root, result_rel, ppath,
result_rel->reltarget, NULL, NULL);
@@ -822,7 +822,7 @@ generate_nonunion_paths(SetOperationStmt *op, PlannerInfo *root,
* Append the child results together.
*/
path = (Path *) create_append_path(root, result_rel, pathlist, NIL,
- NULL, 0, false, NIL, -1);
+ NULL, 0, false, NIL, -1, false, NIL);
/* Identify the grouping semantics */
groupList = generate_setop_grouplist(op, tlist);
diff --git a/src/backend/optimizer/util/pathnode.c src/backend/optimizer/util/pathnode.c
index 4736d84a837..93e844bf7f7 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ src/backend/optimizer/util/pathnode.c
@@ -1221,7 +1221,8 @@ create_append_path(PlannerInfo *root,
List *subpaths, List *partial_subpaths,
Relids required_outer,
int parallel_workers, bool parallel_aware,
- List *partitioned_rels, double rows)
+ List *partitioned_rels, double rows,
+ bool pull_tlist, List *pathkeys)
{
AppendPath *pathnode = makeNode(AppendPath);
ListCell *l;
@@ -1253,8 +1254,10 @@ create_append_path(PlannerInfo *root,
pathnode->path.parallel_aware = parallel_aware;
pathnode->path.parallel_safe = rel->consider_parallel;
pathnode->path.parallel_workers = parallel_workers;
- pathnode->path.pathkeys = NIL; /* result is always considered unsorted */
+ pathnode->path.pathkeys = pathkeys; /* !=NIL in case of append OR index
+ scans */
pathnode->partitioned_rels = list_copy(partitioned_rels);
+ pathnode->pull_tlist = pull_tlist;
/*
* For parallel append, non-partial paths are sorted by descending total
@@ -3614,7 +3617,8 @@ reparameterize_path(PlannerInfo *root, Path *path,
apath->path.parallel_workers,
apath->path.parallel_aware,
apath->partitioned_rels,
- -1);
+ -1,
+ apath->pull_tlist, apath->path.pathkeys);
}
default:
break;
diff --git a/src/backend/parser/gram.y src/backend/parser/gram.y
index bc65319c2c4..77347e8cb76 100644
--- a/src/backend/parser/gram.y
+++ src/backend/parser/gram.y
@@ -13027,7 +13027,7 @@ a_expr: c_expr { $$ = $1; }
}
| a_expr LIKE a_expr ESCAPE a_expr %prec LIKE
{
- FuncCall *n = makeFuncCall(SystemFuncName("like_escape"),
+ FuncCall *n = makeFuncCall(list_make1(makeString("like_escape")),
list_make2($3, $5),
@2);
$$ = (Node *) makeSimpleA_Expr(AEXPR_LIKE, "~~",
@@ -13040,7 +13040,7 @@ a_expr: c_expr { $$ = $1; }
}
| a_expr NOT_LA LIKE a_expr ESCAPE a_expr %prec NOT_LA
{
- FuncCall *n = makeFuncCall(SystemFuncName("like_escape"),
+ FuncCall *n = makeFuncCall(list_make1(makeString("like_escape")),
list_make2($4, $6),
@2);
$$ = (Node *) makeSimpleA_Expr(AEXPR_LIKE, "!~~",
@@ -13053,7 +13053,7 @@ a_expr: c_expr { $$ = $1; }
}
| a_expr ILIKE a_expr ESCAPE a_expr %prec ILIKE
{
- FuncCall *n = makeFuncCall(SystemFuncName("like_escape"),
+ FuncCall *n = makeFuncCall(list_make1(makeString("like_escape")),
list_make2($3, $5),
@2);
$$ = (Node *) makeSimpleA_Expr(AEXPR_ILIKE, "~~*",
@@ -13066,7 +13066,7 @@ a_expr: c_expr { $$ = $1; }
}
| a_expr NOT_LA ILIKE a_expr ESCAPE a_expr %prec NOT_LA
{
- FuncCall *n = makeFuncCall(SystemFuncName("like_escape"),
+ FuncCall *n = makeFuncCall(list_make1(makeString("like_escape")),
list_make2($4, $6),
@2);
$$ = (Node *) makeSimpleA_Expr(AEXPR_ILIKE, "!~~*",
@@ -13075,7 +13075,7 @@ 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_SIMILAR, "~",
@@ -13083,7 +13083,7 @@ a_expr: c_expr { $$ = $1; }
}
| a_expr SIMILAR TO a_expr ESCAPE a_expr %prec SIMILAR
{
- FuncCall *n = makeFuncCall(SystemFuncName("similar_escape"),
+ FuncCall *n = makeFuncCall(list_make1(makeString("similar_escape")),
list_make2($4, $6),
@2);
$$ = (Node *) makeSimpleA_Expr(AEXPR_SIMILAR, "~",
@@ -13091,7 +13091,7 @@ a_expr: c_expr { $$ = $1; }
}
| a_expr NOT_LA SIMILAR TO a_expr %prec NOT_LA
{
- FuncCall *n = makeFuncCall(SystemFuncName("similar_escape"),
+ FuncCall *n = makeFuncCall(list_make1(makeString("similar_escape")),
list_make2($5, makeNullAConst(-1)),
@2);
$$ = (Node *) makeSimpleA_Expr(AEXPR_SIMILAR, "!~",
@@ -13099,7 +13099,7 @@ a_expr: c_expr { $$ = $1; }
}
| a_expr NOT_LA SIMILAR TO a_expr ESCAPE a_expr %prec NOT_LA
{
- FuncCall *n = makeFuncCall(SystemFuncName("similar_escape"),
+ FuncCall *n = makeFuncCall(list_make1(makeString("similar_escape")),
list_make2($5, $7),
@2);
$$ = (Node *) makeSimpleA_Expr(AEXPR_SIMILAR, "!~",
diff --git a/src/include/nodes/relation.h src/include/nodes/relation.h
index ffeb3eb1fee..20cbb78abde 100644
--- a/src/include/nodes/relation.h
+++ src/include/nodes/relation.h
@@ -1319,6 +1319,11 @@ typedef struct AppendPath
/* RT indexes of non-leaf tables in a partition tree */
List *partitioned_rels;
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 */
/* Index of first partial path in subpaths */
int first_partial_path;
diff --git a/src/include/optimizer/pathnode.h src/include/optimizer/pathnode.h
index 7c5ff226501..e1fa74f8b8c 100644
--- a/src/include/optimizer/pathnode.h
+++ src/include/optimizer/pathnode.h
@@ -68,7 +68,8 @@ extern AppendPath *create_append_path(PlannerInfo *root, RelOptInfo *rel,
List *subpaths, List *partial_subpaths,
Relids required_outer,
int parallel_workers, bool parallel_aware,
- List *partitioned_rels, double rows);
+ List *partitioned_rels, double rows,
+ 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 src/include/optimizer/paths.h
index cafde307adb..4facfc14266 100644
--- a/src/include/optimizer/paths.h
+++ src/include/optimizer/paths.h
@@ -71,6 +71,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);
@@ -227,6 +229,7 @@ extern List *select_outer_pathkeys_for_merge(PlannerInfo *root,
extern List *make_inner_pathkeys_for_merge(PlannerInfo *root,
List *mergeclauses,
List *outer_pathkeys);
+extern int pathkeys_useful_for_ordering(PlannerInfo *root, List *pathkeys);
extern List *trim_mergeclauses_for_inner_pathkeys(PlannerInfo *root,
List *mergeclauses,
List *pathkeys);
@@ -234,6 +237,7 @@ 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);
extern PathKey *make_canonical_pathkey(PlannerInfo *root,
EquivalenceClass *eclass, Oid opfamily,
int strategy, bool nulls_first);
diff --git a/src/include/optimizer/planmain.h src/include/optimizer/planmain.h
index a081ca689a3..d30ea7dc490 100644
--- a/src/include/optimizer/planmain.h
+++ src/include/optimizer/planmain.h
@@ -59,6 +59,9 @@ extern Plan *materialize_finished_plan(Plan *subplan);
extern bool is_projection_capable_path(Path *path);
extern bool is_projection_capable_plan(Plan *plan);
+extern Node * fix_indexqual_operand(Node *node, IndexOptInfo *index, int
+ indexcol);
+
/* External use of these functions is deprecated: */
extern Sort *make_sort_from_sortclauses(List *sortcls, Plan *lefttree);
extern Agg *make_agg(List *tlist, List *qual,
diff --git a/src/test/regress/expected/create_index.out src/test/regress/expected/create_index.out
index be25101db24..6f64f96c167 100644
--- a/src/test/regress/expected/create_index.out
+++ src/test/regress/expected/create_index.out
@@ -2861,18 +2861,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 src/test/regress/expected/select.out
index 7ad1f0bc773..69f238906ba 100644
--- a/src/test/regress/expected/select.out
+++ src/test/regress/expected/select.out
@@ -517,6 +517,124 @@ TABLE int8_tbl;
4567890123456789 | -4567890123456789
(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
--
diff --git a/src/test/regress/sql/select.sql src/test/regress/sql/select.sql
index fdab06d5bc1..787a88cfe4c 100644
--- a/src/test/regress/sql/select.sql
+++ src/test/regress/sql/select.sql
@@ -148,6 +148,33 @@ SELECT 2+2, 57
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 src/tools/msvc/Mkvcbuild.pm
index a083ac77f6e..56ad6c9577a 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ src/tools/msvc/Mkvcbuild.pm
@@ -35,8 +35,8 @@ my $contrib_defines = { 'refint' => 'REFINT_VERBOSE' };
my @contrib_uselibpq = ('dblink', 'oid2name', 'postgres_fdw', 'vacuumlo');
my @contrib_uselibpgport = ('oid2name', 'pg_standby', 'vacuumlo');
my @contrib_uselibpgcommon = ('oid2name', 'pg_standby', 'vacuumlo');
-my $contrib_extralibs = undef;
-my $contrib_extraincludes = { 'dblink' => ['src/backend'] };
+my $contrib_extralibs = {'mchar' => ['$(ICU46_LIB)\icuin.lib', '$(ICU46_LIB)\icuuc.lib']};
+my $contrib_extraincludes = { 'dblink' => ['src/backend'], 'mchar' => ['$(ICU46_INCLUDE)'] };
my $contrib_extrasource = {
'cube' => [ 'contrib/cube/cubescan.l', 'contrib/cube/cubeparse.y' ],
'seg' => [ 'contrib/seg/segscan.l', 'contrib/seg/segparse.y' ],
@@ -65,6 +65,7 @@ my $frontend_extralibs = {
'initdb' => ['ws2_32.lib'],
'pg_restore' => ['ws2_32.lib'],
'pgbench' => ['ws2_32.lib'],
+ 'mchar' => ['$(ICU46_LIB)\icuin.lib', '$(ICU46_LIB)\icuuc.lib'],
'psql' => ['ws2_32.lib']
};
my $frontend_extraincludes = {
@@ -462,6 +463,7 @@ sub mkvcbuild
$pgcrypto->AddLibrary('ws2_32.lib');
my $mf = Project::read_file('contrib/pgcrypto/Makefile');
GenerateContribSqlFiles('pgcrypto', $mf);
+ GenerateFulleqSql();
foreach my $subdir ('contrib', 'src/test/modules')
{
@@ -1014,6 +1016,59 @@ sub GenerateContribSqlFiles
return;
}
+sub GenerateFulleqSql
+{
+ my @argtypes = ('bool', 'bytea', 'char', 'name', 'int8', 'int2', 'int4',
+ 'text', 'oid', 'xid', 'cid', 'oidvector', 'float4',
+ 'float8', 'abstime', 'reltime', 'macaddr', 'inet', 'cidr',
+ 'varchar', 'date', 'time', 'timestamp', 'timestamptz',
+ 'interval', 'timetz');
+
+ #form extension script
+ my $i;
+ open($i, '<', "contrib/fulleq/fulleq.sql.in") ||
+ croak "Could not read contrib/fulleq/fulleq.sql.in";
+ my $template = do { local $/; <$i> };
+ close($i);
+
+ my $o;
+ open($o, '>', "contrib/fulleq/fulleq--2.0.sql") ||
+ croak "Could not write to contrib/fulleq/fulleq--2.0.sql";
+ print $o "\\echo Use \"CREATE EXTENSION fulleq\" to load this file. \\quit\n";
+ foreach my $argtype (@argtypes)
+ {
+ my $newtype = $template;
+ $newtype =~ s/ARGTYPE/$argtype/g;
+ print $o $newtype;
+ }
+ close($o);
+
+ #form migration script
+ $template = undef;
+ open($i, '<', "contrib/fulleq/fulleq-unpackaged.sql.in") ||
+ croak "Could not read contrib/fulleq/fulleq-unpackaged.sql.in";
+ $template = do { local $/; <$i> };
+ close($i);
+
+ open($o, '>', "contrib/fulleq/fulleq--unpackaged--2.0.sql") ||
+ croak "Could not write to contrib/fulleq/fulleq--2.0.sql";
+
+ print $o "\\echo Use \"CREATE EXTENSION fulleq FROM unpackaged\" to load this file. \\quit\n";
+ print $o "DROP OPERATOR CLASS IF EXISTS int2vector_fill_ops USING hash;\n";
+ print $o "DROP OPERATOR FAMILY IF EXISTS int2vector_fill_ops USING hash;\n";
+ print $o "DROP FUNCTION IF EXISTS fullhash_int2vector(int2vector);\n";
+ print $o "DROP OPERATOR IF EXISTS == (int2vector, int2vector);\n";
+ print $o "DROP FUNCTION IF EXISTS isfulleq_int2vector(int2vector, int2vector);\n";
+
+ foreach my $argtype (@argtypes)
+ {
+ my $newtype = $template;
+ $newtype =~ s/ARGTYPE/$argtype/g;
+ print $o $newtype;
+ }
+ close($o);
+}
+
sub AdjustContribProj
{
my $proj = shift;