diff -c -r -N src.orig/backend/access/common/scankey.c src/backend/access/common/scankey.c *** src.orig/backend/access/common/scankey.c Fri Jun 22 13:10:37 2007 --- src/backend/access/common/scankey.c Fri Jun 22 13:21:50 2007 *************** *** 94,98 **** entry->sk_strategy = strategy; entry->sk_subtype = subtype; entry->sk_argument = argument; ! fmgr_info_copy(&entry->sk_func, finfo, CurrentMemoryContext); } --- 94,101 ---- entry->sk_strategy = strategy; entry->sk_subtype = subtype; entry->sk_argument = argument; ! if ( finfo ) ! fmgr_info_copy(&entry->sk_func, finfo, CurrentMemoryContext); ! else ! memset(&entry->sk_func, 0, sizeof(entry->sk_func)); } diff -c -r -N src.orig/backend/access/nbtree/nbtutils.c src/backend/access/nbtree/nbtutils.c *** src.orig/backend/access/nbtree/nbtutils.c Fri Jun 22 13:10:38 2007 --- src/backend/access/nbtree/nbtutils.c Fri Jun 22 13:21:50 2007 *************** *** 248,259 **** if (numberOfKeys == 1) { /* ! * We don't use indices for 'A is null' and 'A is not null' currently ! * and 'A < = > <> NULL' will always fail - so qual is not OK if ! * comparison value is NULL. - vadim 03/21/97 */ if (cur->sk_flags & SK_ISNULL) ! so->qual_ok = false; memcpy(outkeys, inkeys, sizeof(ScanKeyData)); so->numberOfKeys = 1; /* We can mark the qual as required if it's for first index col */ --- 248,270 ---- if (numberOfKeys == 1) { /* ! * A IS (NOT) NULL is transformed to scankey with the ! * SK_INDEXFINDNULL flag set. In this case the strategy ! * determines if it IS NULL or IS NOT NULL. ! * ! * Note the use of SK_INDEXFINDNULL. This flag needs to be ! * set for the index to find NULLs. This is to distinguish ! * the static predicates (a = NULL) and (a IS NULL) and the ! * runtime predicate (a = col) where col happend to be NULL ! * this iteration. */ if (cur->sk_flags & SK_ISNULL) ! { ! if( cur->sk_strategy != BTEqualStrategyNumber && cur->sk_strategy != BTLessStrategyNumber ) ! so->qual_ok = false; ! else if( !(cur->sk_flags & SK_INDEXFINDNULL) ) ! so->qual_ok = false; ! } memcpy(outkeys, inkeys, sizeof(ScanKeyData)); so->numberOfKeys = 1; /* We can mark the qual as required if it's for first index col */ *************** *** 294,306 **** /* Note: we assume SK_ISNULL is never set in a row header key */ if (cur->sk_flags & SK_ISNULL) { ! so->qual_ok = false; ! ! /* ! * Quit processing so we don't try to invoke comparison ! * routines on NULLs. ! */ ! return; } } --- 305,322 ---- /* Note: we assume SK_ISNULL is never set in a row header key */ if (cur->sk_flags & SK_ISNULL) { ! /* If it doesn't represent IS (NOT) NULL, it's always false */ ! if( !(cur->sk_flags & SK_INDEXFINDNULL) ) ! { ! so->qual_ok = false; ! return; ! } ! /* Check for expected strategies */ ! if( cur->sk_strategy != BTEqualStrategyNumber && cur->sk_strategy != BTLessStrategyNumber ) ! { ! elog( ERROR, "Unrecognised NullTest in _bt_preprocesskeys" ); ! return; ! } } } *************** *** 331,336 **** --- 347,363 ---- if (!chk || j == (BTEqualStrategyNumber - 1)) continue; + + /* If we have a (col IS NULL), cannot accept any other predicates */ + if ( eq->sk_flags & SK_ISNULL ) + { + so->qual_ok = false; + break; + } + /* And a (col IS NOT NULL) is redundant with an equals */ + if( chk->sk_flags & SK_ISNULL ) + continue; + test = FunctionCall2(&chk->sk_func, eq->sk_argument, chk->sk_argument); *************** *** 361,373 **** ScanKey lt = xform[BTLessStrategyNumber - 1]; ScanKey le = xform[BTLessEqualStrategyNumber - 1]; ! test = FunctionCall2(&le->sk_func, ! lt->sk_argument, ! le->sk_argument); ! if (DatumGetBool(test)) ! xform[BTLessEqualStrategyNumber - 1] = NULL; ! else xform[BTLessStrategyNumber - 1] = NULL; } /* keep only one of >, >= */ --- 388,406 ---- ScanKey lt = xform[BTLessStrategyNumber - 1]; ScanKey le = xform[BTLessEqualStrategyNumber - 1]; ! /* An IS NOT NULL is redundant with le clause */ ! if( lt->sk_flags & SK_ISNULL ) xform[BTLessStrategyNumber - 1] = NULL; + else + { + test = FunctionCall2(&le->sk_func, + lt->sk_argument, + le->sk_argument); + if (DatumGetBool(test)) + xform[BTLessEqualStrategyNumber - 1] = NULL; + else + xform[BTLessStrategyNumber - 1] = NULL; + } } /* keep only one of >, >= */ *************** *** 434,439 **** --- 467,512 ---- /* have we seen one of these before? */ if (xform[j]) { + /* If either arg is NULL, need special handling */ + if ( (cur->sk_flags | xform[j]->sk_flags) & SK_ISNULL) + { + if ( j == (BTEqualStrategyNumber - 1) ) /* IS NULL clause */ + { + /* If matching NULL, we can *only* match null */ + if ( (cur->sk_flags ^ xform[j]->sk_flags) & SK_ISNULL) + { + so->qual_ok = false; + continue; + } + /* If both = NULL, goto next attr */ + if( cur->sk_flags & SK_ISNULL ) + { + continue; + } + /* Should never reach here */ + } + /* IS NOT NULL is redundant with any other LT clause */ + if ( j == (BTLessStrategyNumber - 1) ) /* IS NOT NULL clause */ + { + /* Both IS NOT NULL, goto next attr */ + if ( (cur->sk_flags & xform[j]->sk_flags) & SK_ISNULL) + { + continue; + } + /* Keep the non-redundant one */ + if( xform[j]->sk_flags & SK_ISNULL ) + { + xform[j] = cur; + continue; + } + if( cur->sk_flags & SK_ISNULL ) + { + continue; + } + } + /* Should never get here */ + elog( ERROR, "Impossible case in _bt_preprocess_keys" ); + } /* yup, keep the more restrictive key */ test = FunctionCall2(&cur->sk_func, cur->sk_argument, *************** *** 617,628 **** tupdesc, &isNull); ! /* btree doesn't support 'A is null' clauses, yet */ ! if (key->sk_flags & SK_ISNULL) ! { ! /* we shouldn't get here, really; see _bt_preprocess_keys() */ ! *continuescan = false; ! return false; } if (isNull) --- 690,740 ---- tupdesc, &isNull); ! if (key->sk_flags & SK_ISNULL) { ! /* ! * Null test case ! */ ! ! if ( key->sk_strategy == BTEqualStrategyNumber ) ! { ! /* This scankey is (col IS NULL). The comment below ! * applies here too: if we're at a not null value on ! * a required key, we must be at a point where it is ! * no longer useful to proceed. */ ! ! if( !isNull ) ! { ! /* ! * Since NULLs are sorted after non-NULLs, we ! * reached first not-null value ! */ ! if ((key->sk_flags & SK_BT_REQBKWD) && ScanDirectionIsBackward(dir)) ! *continuescan = false; ! ! return false; ! } ! ! /* ! * Satisfy NULL IS NULL ! */ ! continue; ! } ! ! if ( key->sk_strategy == BTLessStrategyNumber ) ! { ! /* This scankey is (col IS NOT NULL). As per comment ! below, if we have a NULL value on a ForwardScan ! we've reached the end of useful values.*/ ! if (isNull) ! { ! if ( (key->sk_flags & SK_BT_REQFWD) && ScanDirectionIsForward(dir)) ! *continuescan = false; ! ! return false; ! } ! ! continue; ! } } if (isNull) diff -c -r -N src.orig/backend/executor/nodeIndexscan.c src/backend/executor/nodeIndexscan.c *** src.orig/backend/executor/nodeIndexscan.c Fri Jun 22 13:10:38 2007 --- src/backend/executor/nodeIndexscan.c Fri Jun 22 13:21:50 2007 *************** *** 722,729 **** strategy_cell = lnext(strategy_cell); subtype = lfirst_oid(subtype_cell); subtype_cell = lnext(subtype_cell); ! if (IsA(clause, OpExpr)) { /* indexkey op const or indexkey op expression */ int flags = 0; --- 722,759 ---- strategy_cell = lnext(strategy_cell); subtype = lfirst_oid(subtype_cell); subtype_cell = lnext(subtype_cell); + + if (IsA(clause, NullTest)) + { + leftop = (Expr *) ((NullTest *) clause)->arg; ! if (leftop && IsA(leftop, RelabelType)) ! leftop = ((RelabelType *) leftop)->arg; ! ! Assert(leftop != NULL); ! ! if (!(IsA(leftop, Var) && ! var_is_rel((Var *) leftop))) ! elog(ERROR, "NullTest doesn't have key"); ! ! varattno = ((Var *) leftop)->varattno; ! ! /* ! * We need SK_INDEXFINDNULL for IS NULL to work, for ! * runtime keys a NULL is special and shouldn't ! * match anything. ! * ! * Note: for IS (NOT) NULL no procedure is needed. ! */ ! ScanKeyEntryInitializeWithInfo(&scan_keys[j], ! SK_ISNULL | SK_INDEXFINDNULL, ! varattno, /* attribute number to scan */ ! strategy, /* op's strategy */ ! subtype, /* strategy subtype */ ! NULL, /* reg proc to use */ ! (Datum)0 ); /* constant */ ! } ! else if (IsA(clause, OpExpr)) { /* indexkey op const or indexkey op expression */ int flags = 0; diff -c -r -N src.orig/backend/nodes/outfuncs.c src/backend/nodes/outfuncs.c *** src.orig/backend/nodes/outfuncs.c Fri Jun 22 13:10:38 2007 --- src/backend/nodes/outfuncs.c Fri Jun 22 13:21:50 2007 *************** *** 1105,1110 **** --- 1105,1111 ---- _outPathInfo(str, (Path *) node); WRITE_NODE_FIELD(subpaths); + WRITE_BOOL_FIELD(pull_tlist); } static void diff -c -r -N src.orig/backend/optimizer/path/allpaths.c src/backend/optimizer/path/allpaths.c *** src.orig/backend/optimizer/path/allpaths.c Fri Jun 22 13:10:38 2007 --- src/backend/optimizer/path/allpaths.c Fri Jun 22 13:21:50 2007 *************** *** 220,226 **** /* Reset output-rows estimate to 0 */ rel->rows = 0; ! add_path(rel, (Path *) create_append_path(rel, NIL)); /* Select cheapest path (pretty easy in this case...) */ set_cheapest(rel); --- 220,226 ---- /* Reset output-rows estimate to 0 */ rel->rows = 0; ! add_path(rel, (Path *) create_append_path(rel, NIL, false, NIL)); /* Select cheapest path (pretty easy in this case...) */ set_cheapest(rel); *************** *** 242,247 **** --- 242,250 ---- /* 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); *************** *** 394,400 **** * the parent rel. (Note: this is correct even if we have zero or one * live subpath due to constraint exclusion.) */ ! add_path(rel, (Path *) create_append_path(rel, subpaths)); /* Select cheapest path (pretty easy in this case...) */ set_cheapest(rel); --- 397,403 ---- * the parent rel. (Note: this is correct even if we have zero or one * live subpath due to constraint exclusion.) */ ! add_path(rel, (Path *) create_append_path(rel, subpaths, false, NIL)); /* Select cheapest path (pretty easy in this case...) */ set_cheapest(rel); diff -c -r -N src.orig/backend/optimizer/path/indxpath.c src/backend/optimizer/path/indxpath.c *** src.orig/backend/optimizer/path/indxpath.c Fri Jun 22 13:10:38 2007 --- src/backend/optimizer/path/indxpath.c Fri Jun 22 13:21:50 2007 *************** *** 36,41 **** --- 36,48 ---- #include "utils/pg_locale.h" #include "utils/selfuncs.h" + /* + * index support for LIKE mchar + */ + #include "fmgr.h" + #include "utils/lsyscache.h" + #include "utils/syscache.h" + #include "parser/parse_type.h" /* * DoneMatchingIndexKeys() - MACRO *************** *** 1189,1194 **** --- 1196,1209 ---- return true; } + /* We can index NULL tests also, for btree only */ + if( index->relam == BTREE_AM_OID && IsA( clause, NullTest ) ) + { + leftop = (Node*) ((NullTest*)clause)->arg; + if (match_index_to_operand(leftop, indexcol, index)) + return true; + } + /* * Clause must be a binary opclause, or possibly a ScalarArrayOpExpr * (which is always binary, by definition). Or it could be a *************** *** 1891,1897 **** break; } } ! /* Otherwise, ignore if not a binary opclause */ if (!is_opclause(clause) || list_length(clause->args) != 2) continue; --- 1906,1912 ---- break; } } ! /* Otherwise, ignore if not a binary opclause */ if (!is_opclause(clause) || list_length(clause->args) != 2) continue; *************** *** 2110,2115 **** --- 2125,2322 ---- } /**************************************************************************** + * ---- ROUTINES FOR "SPECIAL" INDEXABLE OPERATORS FOR + * SPECIAL USER_DEFINED TYPES ---- + * -- teodor + ****************************************************************************/ + + static Oid mmPFPOid = InvalidOid; + static Oid mmGTOid = InvalidOid; + static Oid mcharOid = InvalidOid; + static Oid mvarcharOid = InvalidOid; + + static bool + fillMCharOIDS() { + CatCList *catlist; + HeapTuple tup; + char *funcname = "mchar_pattern_fixed_prefix"; + int n_members; + + catlist = SearchSysCacheList(PROCNAMEARGSNSP, 1, + CStringGetDatum(funcname), + 0, 0, 0); + n_members = catlist->n_members; + + if ( n_members != 1 ) { + ReleaseSysCacheList(catlist); + if ( n_members > 1 ) + elog(ERROR,"There are %d candidates for '%s' function'", n_members, funcname); + return false; + } + + tup = &catlist->members[0]->tuple; + + if ( HeapTupleGetOid(tup) != mmPFPOid ) { + TypeName *typename; + char *quals_funcname = "mchar_greaterstring"; + Oid tmp_mmPFPOid = HeapTupleGetOid(tup); + + ReleaseSysCacheList(catlist); + + typename = makeTypeName("mchar"); + mcharOid = LookupTypeName(NULL, typename); + + typename = makeTypeName("mvarchar"); + mvarcharOid = LookupTypeName(NULL, typename); + + + if ( mcharOid == InvalidOid || mvarcharOid == InvalidOid ) { + elog(LOG,"Can't find mchar/mvarvarchar types: mchar=%d mvarchar=%d", + mcharOid, mvarcharOid); + return false; + } + + catlist = SearchSysCacheList(PROCNAMEARGSNSP, 1, + CStringGetDatum(quals_funcname), + 0, 0, 0); + n_members = catlist->n_members; + + if ( n_members != 1 ) { + ReleaseSysCacheList(catlist); + if ( n_members > 1 ) + elog(ERROR,"There are %d candidates for '%s' function'", n_members, quals_funcname); + return false; + } + + tup = &catlist->members[0]->tuple; + mmGTOid = HeapTupleGetOid(tup); + mmPFPOid = tmp_mmPFPOid; + } + + ReleaseSysCacheList(catlist); + + return true; + } + + static Pattern_Prefix_Status + mchar_pattern_fixed_prefix(Oid opOid, Oid opclassOid, Const *patt, Pattern_Type ptype, + Const **prefix, Const **rest) { + HeapTuple tup; + Form_pg_operator oprForm; + bool isMCharLike = true; + Oid typeOid; + + 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->oprleft != oprForm->oprright ) + isMCharLike = false; + + if ( !( oprForm->oprleft == mcharOid || oprForm->oprleft == mvarcharOid ) ) + isMCharLike = false; + + if ( oprForm->oprleft != patt->consttype ) + isMCharLike = false; + + typeOid = oprForm->oprleft; + + ReleaseSysCache(tup); + + if ( !isMCharLike ) + return Pattern_Prefix_None; + + if ( opclassOid != InvalidOid ) { + Form_pg_opclass claForm; + + tup = SearchSysCache(CLAOID, opclassOid, 0, 0, 0); + claForm = (Form_pg_opclass) GETSTRUCT(tup); + + if ( claForm->opcamid != BTREE_AM_OID ) + isMCharLike = false; + + if ( typeOid == mcharOid && strncmp(claForm->opcname.data, "mchar_icase_ops", 15 /* strlen(mchar_icase_ops) */ ) != 0 ) + isMCharLike = false; + + if ( typeOid == mvarcharOid && strncmp(claForm->opcname.data, "mvarchar_icase_ops", 18 /* strlen(mvarchar_icase_ops) */ ) != 0 ) + isMCharLike = false; + + ReleaseSysCache(tup); + } + + if ( !isMCharLike ) + return Pattern_Prefix_None; + + return (Pattern_Prefix_Status)DatumGetInt32( OidFunctionCall5( + mmPFPOid, + BoolGetDatum( (typeOid == mcharOid) ? true : false ), + PointerGetDatum( patt ), + Int32GetDatum( ptype ), + PointerGetDatum( prefix ), + PointerGetDatum( rest ) + ) ); + } + + static List * + mchar_prefix_quals(Node *leftop, 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(opclass, InvalidOid, BTEqualStrategyNumber); + + if (oproid == InvalidOid) + elog(ERROR, "no = operator for opclass %u", opclass); + + expr = make_opclause(oproid, BOOLOID, false, + (Expr *) leftop, (Expr *) prefix_const); + result = list_make1(make_restrictinfo(expr, true, false, false, NULL)); + return result; + } + + /* We can always say "x >= prefix". */ + oproid = get_opclass_member(opclass, InvalidOid, BTGreaterEqualStrategyNumber); + if (oproid == InvalidOid) + elog(ERROR, "no >= operator for opclass %u", opclass); + expr = make_opclause(oproid, BOOLOID, false, + (Expr *) leftop, (Expr *) prefix_const); + result = list_make1(make_restrictinfo(expr, true, false, false, NULL)); + + /* If we can create a string larger than the prefix, we can say + * "x < greaterstr". */ + + greaterstr = (Const*)DatumGetPointer( OidFunctionCall2( + mmGTOid, + BoolGetDatum( (prefix_const->consttype == mcharOid) ? true : false ), + PointerGetDatum( prefix_const ) + ) ); + + if (greaterstr) { + oproid = get_opclass_member(opclass, InvalidOid, BTLessStrategyNumber); + + if (oproid == InvalidOid) + elog(ERROR, "no < operator for opclass %u", opclass); + + expr = make_opclause(oproid, BOOLOID, false, + (Expr *) leftop, (Expr *) greaterstr); + result = lappend(result, make_restrictinfo(expr, true, false, false, NULL)); + } + + return result; + } + + + /**************************************************************************** * ---- ROUTINES FOR "SPECIAL" INDEXABLE OPERATORS ---- ****************************************************************************/ *************** *** 2296,2304 **** pfree(prefix); } ! /* done if the expression doesn't look indexable */ ! if (!isIndexable) return false; /* * Must also check that index's opclass supports the operators we will --- 2503,2518 ---- pfree(prefix); } ! 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, &rest) != Pattern_Prefix_None ) { ! return true; ! } return false; + } /* * Must also check that index's opclass supports the operators we will *************** *** 2433,2438 **** --- 2647,2658 ---- index, indexcol)); } + else if( index->relam == BTREE_AM_OID && IsA( rinfo->clause, NullTest ) ) + { + resultquals = lappend(resultquals, + make_restrictinfo( rinfo->clause, true, false, false, NULL ) + ); + } else elog(ERROR, "unsupported indexqual type: %d", (int) nodeTag(clause)); *************** *** 2529,2534 **** --- 2749,2761 ---- Const *rest = NULL; Pattern_Prefix_Status pstatus; List *result; + + pstatus = mchar_pattern_fixed_prefix(expr_op, opclass, + patt, Pattern_Type_Like, + &prefix, &rest); + + if ( pstatus != Pattern_Prefix_None ) + return mchar_prefix_quals(leftop, opclass, prefix, pstatus); switch (expr_op) { diff -c -r -N src.orig/backend/optimizer/path/joinpath.c src/backend/optimizer/path/joinpath.c *** src.orig/backend/optimizer/path/joinpath.c Fri Jun 22 13:10:38 2007 --- src/backend/optimizer/path/joinpath.c Fri Jun 22 13:21:50 2007 *************** *** 871,877 **** return NULL; /* Form and return the completed Append path. */ ! return (Path *) create_append_path(rel, append_paths); } /* --- 871,877 ---- return NULL; /* Form and return the completed Append path. */ ! return (Path *) create_append_path(rel, append_paths, false, NIL); } /* diff -c -r -N src.orig/backend/optimizer/path/orindxpath.c src/backend/optimizer/path/orindxpath.c *** src.orig/backend/optimizer/path/orindxpath.c Fri Jun 22 13:10:38 2007 --- src/backend/optimizer/path/orindxpath.c Fri Jun 22 13:21:50 2007 *************** *** 15,24 **** --- 15,71 ---- #include "postgres.h" + #include "access/skey.h" + #include "catalog/pg_am.h" #include "optimizer/cost.h" + #include "optimizer/clauses.h" #include "optimizer/paths.h" + #include "optimizer/pathnode.h" + #include "optimizer/planmain.h" + #include "optimizer/predtest.h" #include "optimizer/restrictinfo.h" + #include "utils/lsyscache.h" + typedef struct CKey { + RestrictInfo *rinfo; /* original rinfo */ + int n; /* IndexPath's number in bitmapquals */ + OpExpr *normalizedexpr; /* expression with Var on left */ + Var *var; + Node *value; + Oid opclass; + 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 opclass; + Oid subtype; + int strategy; + int attno; + } ExExpr; + + + typedef struct IndexPathEx { + IndexPath *path; + List *preparedquals; /* list of ExExpr */ + } IndexPathEx; /*---------- * create_or_index_quals *************** *** 180,183 **** --- 227,1107 ---- /* Tell caller to recompute rel's rows estimate */ return true; + } + + + /*---------- + * keybased_rewrite_or_index_quals + * Examine join OR-of-AND quals to see if any useful common restriction + * clauses can be extracted. If so, try to use for creating new index paths. + * + * For example consider + * WHERE ( a.x=5 and a.y>10 ) OR a.x>5 + * and there is an index on a.x or (a.x, a.y). So, plan + * will be seqscan or BitmapOr(IndexPath,IndexPath) + * So, we can add some restriction: + * WHERE (( a.x=5 and a.y>10 ) OR a.x>5) AND a.x>=5 + * and plan may be so + * Index Scan (a.x>=5) + * Filter( (( a.x=5 and a.y>10 ) OR a.x>5) ) + * + * We don't want to add new clauses to baserestrictinfo, just + * use it as index quals. + * + * Next thing which it possible to test is use append of + * searches instead of OR. + * For example consider + * WHERE ( a.x=5 and a.y>10 ) OR a.x>6 + * and there is an index on (a.x) (a.x, a.y) + * So, we can suggest follow plan: + * Append + * Filter ( a.x=5 and a.y>10 ) OR (a.x>6) + * Index Scan (a.x=5) --in case of index on (a.x) + * Index Scan (a.x>6) + * For that we should proof that index quals isn't overlapped, + * also, some index quals may be containedi in other, so it can be eliminated + */ + + void + keybased_rewrite_index_paths(PlannerInfo *root, RelOptInfo *rel) + { + BitmapOrPath *bestpath = NULL; + ListCell *i; + List *commonquals; + AppendPath *appendidxpath; + List *indexPaths; + IndexOptInfo *index; + + foreach(i, rel->baserestrictinfo) + { + RestrictInfo *rinfo = (RestrictInfo *) lfirst(i); + + if (restriction_is_or_clause(rinfo) && + !rinfo->outerjoin_delayed) + { + /* + * Use the generate_bitmap_or_paths() machinery to estimate the + * value of each OR clause. We can use regular restriction + * clauses along with the OR clause contents to generate + * indexquals. We pass outer_rel = NULL so that sub-clauses + * that are actually joins will be ignored. + */ + List *orpaths; + ListCell *k; + + orpaths = generate_bitmap_or_paths(root, rel, + list_make1(rinfo), + rel->baserestrictinfo, + NULL); + + /* 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; + + /* + * It's possible to do only one index scan :) + */ + index = ipath->indexinfo; + + if ( root->query_pathkeys != NIL && OidIsValid(index->ordering[0]) ) + { + List *pathkeys; + + pathkeys = build_index_pathkeys(root, index, + ForwardScanDirection, true); + 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, true); + 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) ); + ipath->rows = rel->tuples * clauselist_selectivity(root, + ipath->indexquals, + rel->relid, + JOIN_INNER); + ipath->rows = clamp_row_est(ipath->rows); + cost_index(ipath, root, ipath->indexinfo, ipath->indexquals, NULL); + } + + /* + * 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 && OidIsValid(index->ordering[0]) && + (indexPaths = sortIndexScans( bestpath->bitmapquals )) !=NULL ) { + List *pathkeys; + + pathkeys = build_index_pathkeys(root, index, + ForwardScanDirection, true); + pathkeys = truncate_useless_pathkeys(root, rel, + pathkeys); + + appendidxpath = create_append_path(rel, indexPaths, 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, true); + pathkeys = truncate_useless_pathkeys(root, rel, + pathkeys); + + indexPaths = reverseScanDirIdxPaths(indexPaths); + appendidxpath = create_append_path(rel, indexPaths, true, pathkeys); + add_path(rel, (Path *) appendidxpath); + } + } else { + appendidxpath = create_append_path(rel, bestpath->bitmapquals, 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 ) { + 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, &key->opclass); + Assert( IsA(key->var, Var) ); + + /* 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_opclass_strategy( key->normalizedexpr->opno, key->opclass); + 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; + List *quals = NIL; + + foreach(i, path->indexquals) { + CKey *k = transformToCkey( path->indexinfo, (RestrictInfo*)lfirst(i) ); + 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->opclass != b->opclass ) + 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; + int strategy; + Oid subtype; + bool recheck; + + 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_opclass_properties(key->normalizedexpr->opno, key->opclass, + &strategy, &subtype, &recheck); + + if ( strategy != key->strategy ) { + /* + * We should check because it's possible to have "strange" + * opclasses - without some strategies... + */ + key->normalizedexpr->opno = get_opclass_member(key->opclass, subtype, key->strategy); + + if ( key->normalizedexpr->opno == InvalidOid ) + return NULL; + + key->normalizedexpr->opfuncid = get_opcode( key->normalizedexpr->opno ); + Assert ( key->normalizedexpr->opfuncid != InvalidOid ); + } + + rinfo = make_restrictinfo( + (Expr*)key->normalizedexpr, + true, /*is_pushed_down*/ + false, /* outerjoin_delayed */ + false, /* pseudoconstant */ + NULL /* required_relids, will be filled later */ + ); + + return rinfo; + } + + /* + * Remove unneeded RestrioctionInfo nodes as it + * needed by predicate_*_by() + */ + static List* + make_predicate(List *indexquals) { + ListCell *i; + List *checkpred=NULL; + + foreach(i, indexquals) + { + 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; + + checkpred = lappend(checkpred, expr); + } + + return checkpred; + } + + #define CELL_GET_QUALS(x) ( ((IndexPath*)lfirst(x))->indexquals ) + + /* + * returns list of all nested quals + */ + static List* + contained_quals(List *nested, List* quals, ListCell *check) { + ListCell *i; + List *checkpred=NULL; + + if ( list_member_ptr( nested, lfirst(check) ) ) + return nested; + + if ( (checkpred=make_predicate(CELL_GET_QUALS(check)))==NULL ) + return nested; + + if ( contain_mutable_functions((Node*)checkpred) ) + return nested; + + foreach(i, quals ) + { + if ( check == i ) + continue; + + if ( list_member_ptr( nested, lfirst(i) ) ) + continue; + + if ( predicate_implied_by( checkpred, CELL_GET_QUALS(i) ) ) + nested = lappend( nested, lfirst(i) ); + } + return nested; + } + + /* + * Checks that one row can be in several quals. + * It's guaranteed by predicate_refuted_by() + */ + static bool + is_intersect(ListCell *check) { + ListCell *i; + List *checkpred=NULL; + + checkpred=make_predicate(CELL_GET_QUALS(check)); + Assert( checkpred != NULL ); + + + for_each_cell(i, check) { + if ( i==check ) + continue; + + if ( predicate_refuted_by( checkpred, CELL_GET_QUALS(i) ) == false ) + return true; + } + + return false; + } + + /* + * Removes nested quals and gurantees that quals are not intersected, + * ie one row can't satisfy to several quals. It's open a possibility of + * Append node using instead of BitmapOr + */ + static BitmapOrPath* + cleanup_nested_quals( PlannerInfo *root, RelOptInfo *rel, BitmapOrPath *path ) { + ListCell *i; + IndexOptInfo *index=NULL; + List *nested = NULL; + + /* + * check all path to use only one index + */ + foreach(i, path->bitmapquals ) + { + + if ( IsA(lfirst(i), IndexPath) ) { + 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; + } else + return NULL; + } + + /* + * eliminate nested quals + */ + foreach(i, path->bitmapquals ) { + nested = contained_quals(nested, path->bitmapquals, i); + } + + if ( nested != NIL ) { + path->bitmapquals = list_difference_ptr( path->bitmapquals, nested ); + + Assert( list_length( path->bitmapquals )>0 ); + + /* + * All quals becomes only one after eliminating nested quals + */ + if (list_length( path->bitmapquals ) == 1) + return (BitmapOrPath*)linitial(path->bitmapquals); + } + + /* + * Checks for intersection + */ + foreach(i, path->bitmapquals ) { + if ( is_intersect( i ) ) + return NULL; + } + + return path; + } + + /* + * Checks if whole result of one simple operation is contained + * in another + */ + static int + simpleCmpExpr( ExExpr *a, ExExpr *b ) { + if ( predicate_implied_by((List*)a->expr, (List*)b->expr) ) + /* + * a:( Var < 15 ) > b:( Var <= 10 ) + */ + return 1; + else if ( predicate_implied_by((List*)b->expr, (List*)a->expr) ) + /* + * a:( Var <= 10 ) < b:( Var < 15 ) + */ + return -1; + else + return 0; + } + + /* + * Trys to define where is equation - on left or right side + * a(< 10) b(=11) - on right + * a(> 10) b(=9) - on left + * a(= 10) b(=11) - on right + * a(= 10) b(=9) - on left + * Any other - result is 0; + */ + static int + cmpEqExpr( ExExpr *a, ExExpr *b ) { + Oid oldop = b->expr->opno; + int res=0; + + b->expr->opno = get_opclass_member(b->opclass, b->subtype, 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_opclass_member(b->opclass, b->subtype, BTGreaterStrategyNumber); + if ( b->expr->opno != InvalidOid ) { + b->expr->opfuncid = get_opcode( b->expr->opno ); + res = -simpleCmpExpr(a,b); + } + } + + b->expr->opno = oldop; + b->expr->opfuncid = get_opcode( b->expr->opno ); + + return res; + } + + /* + * Is result of a contained in result of b or on the contrary? + */ + static int + cmpNegCmp( ExExpr *a, ExExpr *b ) { + Oid oldop = b->expr->opno; + int res = 0; + + b->expr->opno = get_negator( b->expr->opno ); + if ( b->expr->opno != InvalidOid ) { + b->expr->opfuncid = get_opcode( b->expr->opno ); + res = simpleCmpExpr(a,b); + } + + b->expr->opno = oldop; + b->expr->opfuncid = get_opcode( b->expr->opno ); + + return ( IS_LESS(a->strategy) ) ? res : -res; + } + + /* + * Returns 1 if whole result of a is on left comparing with result of b + * Returns -1 if whole result of a is on right comparing with result of b + * Return 0 if it's impossible to define or results is overlapped + * Expressions should use the same attribute of index and should be + * a simple: just one operation with index. + */ + static int + cmpExpr( ExExpr *a, ExExpr *b ) { + int res; + + /* + * If a and b are overlapped, we can't decide which one is + * lefter or righter + */ + if ( IS_ONE_DIRECTION(a->strategy, b->strategy) || predicate_refuted_by((List*)a->expr, (List*)b->expr) == false ) + return 0; + + /* + * In this place it's impossible to have a row which satisfies + * a and b expressions, so we will try to find relatiove position of that results + */ + if ( b->strategy == BTEqualStrategyNumber ) { + return -cmpEqExpr(a, b); /* Covers cases with any operations in a */ + } else if ( a->strategy == BTEqualStrategyNumber ) { + return cmpEqExpr(b, a); + } else if ( (res = cmpNegCmp(a, b)) == 0 ) { /* so, a(<10) b(>20) */ + res = -cmpNegCmp(b, a); + } + + return res; + } + + /* + * Try to define positions of result which satisfy indexquals a and b per + * one index's attribute. + */ + static int + cmpColumnQuals( List *a, List *b, int attno ) { + int res = 0; + ListCell *ai, *bi; + + foreach(ai, a) { + ExExpr *ae = (ExExpr*)lfirst(ai); + + if ( attno != ae->attno ) + continue; + + foreach(bi, b) { + ExExpr *be = (ExExpr*)lfirst(bi); + + if ( attno != be->attno ) + continue; + + if ((res=cmpExpr(ae, be))!=0) + return res; + } + } + + return 0; + } + + static IndexOptInfo *sortingIndex = NULL; + static bool volatile unableToDefine = false; + + /* + * Compare result of two indexquals. + * Warinig: it use PG_RE_THROW(), so any call should be wrapped with + * PG_TRY(). Try/catch construction is used here for minimize unneeded + * actions when sorting is impossible + */ + static int + cmpIndexPathEx(const void *a, const void *b) { + IndexPathEx *aipe = (IndexPathEx*)a; + IndexPathEx *bipe = (IndexPathEx*)b; + int attno, res = 0; + + for(attno=1; res==0 && attno<=sortingIndex->ncolumns; attno++) + res=cmpColumnQuals(aipe->preparedquals, bipe->preparedquals, attno); + + if ( res==0 ) { + unableToDefine = true; + PG_RE_THROW(); /* it should be PG_THROW(), but it's the same */ + } + + return res; + } + + /* + * Initialize lists of operation in useful form + */ + static List* + prepareQuals(IndexOptInfo *index, List *indexquals) { + ListCell *i; + List *res=NULL; + ExExpr *ex; + + foreach(i, indexquals) + { + RestrictInfo *rinfo = lfirst(i); + OpExpr *expr = (OpExpr*)rinfo->clause; + bool recheck; + + 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, &ex->opclass); + get_op_opclass_properties( ex->expr->opno, ex->opclass, + &ex->strategy, &ex->subtype, &recheck); + + ex->attno = ((Var*)linitial(ex->expr->args))->varattno; + + 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 ); + + if (ipe[j].preparedquals == NULL) + return NULL; + j++; + } + + sortingIndex = index; + unableToDefine = false; + PG_TRY(); { + qsort(ipe, list_length(ipaths), sizeof(IndexPathEx), cmpIndexPathEx); + } PG_CATCH(); { + if ( unableToDefine == false ) + PG_RE_THROW(); /* not our problem */ + } PG_END_TRY(); + + if ( unableToDefine == true ) + return NULL; + + for(j=0;jindexscandir = BackwardScanDirection; + + return n; + } + + static List* + reverseScanDirIdxPaths(List *indexPaths) { + List *idxpath = NIL; + ListCell *i; + + foreach(i, indexPaths) { + idxpath = lcons(reverseScanDirIdxPath( (IndexPath*)lfirst(i) ), idxpath); + } + + return idxpath; } diff -c -r -N src.orig/backend/optimizer/plan/createplan.c src/backend/optimizer/plan/createplan.c *** src.orig/backend/optimizer/plan/createplan.c Fri Jun 22 13:10:38 2007 --- src/backend/optimizer/plan/createplan.c Fri Jun 22 13:21:50 2007 *************** *** 18,23 **** --- 18,24 ---- #include + #include "access/skey.h" #include "nodes/makefuncs.h" #include "optimizer/clauses.h" #include "optimizer/cost.h" *************** *** 72,79 **** List **nonlossy_indexquals, List **indexstrategy, List **indexsubtype); - static Node *fix_indexqual_operand(Node *node, IndexOptInfo *index, - Oid *opclass); static List *get_switched_clauses(List *clauses, Relids outerrelids); static List *order_qual_clauses(PlannerInfo *root, List *clauses); static void copy_path_costsize(Plan *dest, Path *src); --- 73,78 ---- *************** *** 500,506 **** create_append_plan(PlannerInfo *root, AppendPath *best_path) { Append *plan; ! List *tlist = build_relation_tlist(best_path->path.parent); List *subplans = NIL; ListCell *subpaths; --- 499,505 ---- create_append_plan(PlannerInfo *root, AppendPath *best_path) { Append *plan; ! List *tlist; List *subplans = NIL; ListCell *subpaths; *************** *** 516,522 **** if (best_path->subpaths == NIL) { /* Generate a Result plan with constant-FALSE gating qual */ ! return (Plan *) make_result(tlist, (Node *) list_make1(makeBoolConst(false, false)), NULL); --- 515,522 ---- if (best_path->subpaths == NIL) { /* Generate a Result plan with constant-FALSE gating qual */ ! tlist = build_relation_tlist(best_path->path.parent); ! return (Plan *) make_result( tlist, (Node *) list_make1(makeBoolConst(false, false)), NULL); *************** *** 530,535 **** --- 530,539 ---- subplans = lappend(subplans, create_plan(root, subpath)); } + if ( best_path->pull_tlist ) + tlist = copyObject( ((Plan*)linitial(subplans))->targetlist ); + else + tlist = build_relation_tlist(best_path->path.parent); plan = make_append(subplans, false, tlist); return (Plan *) plan; *************** *** 1766,1771 **** --- 1770,1787 ---- &opclass); clause_op = saop->opno; } + else if (IsA(clause, NullTest)) + { + NullTest *nt = (NullTest*)clause; + nt->arg = (Expr *)fix_indexqual_operand( (Node *)nt->arg, index, &opclass ); + + /* + * if ( nt->nulltesttype == IS_NOTNULL ) the we should + * find all not null values, which are 'less' than NULL + */ + clause_op = get_opclass_member(opclass, InvalidOid /* default sub type */ + , (nt->nulltesttype == IS_NULL) ? BTEqualStrategyNumber : BTLessStrategyNumber ); + } else { elog(ERROR, "unsupported indexqual type: %d", *************** *** 1775,1780 **** --- 1791,1799 ---- *fixed_indexquals = lappend(*fixed_indexquals, clause); + if ( clause_op == InvalidOid ) + elog(ERROR,"Couldn't find operation"); + /* * Look up the (possibly commuted) operator in the operator class to * get its strategy numbers and the recheck indicator. This also *************** *** 1792,1798 **** } } ! static Node * fix_indexqual_operand(Node *node, IndexOptInfo *index, Oid *opclass) { /* --- 1811,1817 ---- } } ! Node * fix_indexqual_operand(Node *node, IndexOptInfo *index, Oid *opclass) { /* diff -c -r -N src.orig/backend/optimizer/plan/setrefs.c src/backend/optimizer/plan/setrefs.c *** src.orig/backend/optimizer/plan/setrefs.c Fri Jun 22 13:10:38 2007 --- src/backend/optimizer/plan/setrefs.c Fri Jun 22 13:21:50 2007 *************** *** 1226,1239 **** --- 1226,1247 ---- { Var *var = (Var *) node; + /* join_references_mutator already checks this node */ + if ( var->varno == OUTER ) + return (Node*)copyObject(var); + /* First look for the var in the input tlists */ newvar = search_indexed_tlist_for_var(var, context->outer_itlist, OUTER); if (newvar) return (Node *) newvar; + if (context->inner_itlist) { + if ( var->varno == INNER ) + return (Node*)copyObject(var); + newvar = search_indexed_tlist_for_var(var, context->inner_itlist, INNER); diff -c -r -N src.orig/backend/optimizer/util/pathnode.c src/backend/optimizer/util/pathnode.c *** src.orig/backend/optimizer/util/pathnode.c Fri Jun 22 13:10:38 2007 --- src/backend/optimizer/util/pathnode.c Fri Jun 22 13:21:50 2007 *************** *** 637,652 **** * pathnode. */ AppendPath * ! create_append_path(RelOptInfo *rel, List *subpaths) { AppendPath *pathnode = makeNode(AppendPath); ListCell *l; pathnode->path.pathtype = T_Append; pathnode->path.parent = rel; ! pathnode->path.pathkeys = NIL; /* result is always considered ! * unsorted */ pathnode->subpaths = subpaths; pathnode->path.startup_cost = 0; pathnode->path.total_cost = 0; --- 637,653 ---- * pathnode. */ AppendPath * ! create_append_path(RelOptInfo *rel, List *subpaths, bool pull_tlist, List *pathkeys) { AppendPath *pathnode = makeNode(AppendPath); ListCell *l; pathnode->path.pathtype = T_Append; pathnode->path.parent = rel; ! pathnode->path.pathkeys = pathkeys; /* !=NIL in case of append OR index scans */ ! pathnode->subpaths = subpaths; + pathnode->pull_tlist = pull_tlist; pathnode->path.startup_cost = 0; pathnode->path.total_cost = 0; diff -c -r -N src.orig/backend/parser/analyze.c src/backend/parser/analyze.c *** src.orig/backend/parser/analyze.c Fri Jun 22 13:10:38 2007 --- src/backend/parser/analyze.c Fri Jun 22 13:21:50 2007 *************** *** 2362,2367 **** --- 2362,2440 ---- } /* + * Check sort clause for existance of non-columnref + * quals + */ + static bool + isExpressionInSortClause(List *clause) { + ListCell *i; + + if ( list_length(clause) == 0 ) + return false; + + foreach(i, clause) { + SortBy *sortby = lfirst(i); + + if ( !IsA(sortby->node, ColumnRef) && !IsA(sortby->node, A_Const)) + return true; + } + + return false; + } + + /* + * transfrom 'select union' to 'select * from (select union)' + */ + static SelectStmt* + addSelectStmtLevel(SelectStmt *stmt) { + SelectStmt *leftmostSelect; + SelectStmt *newstmt = makeNode(SelectStmt); + RangeSubselect *rs = makeNode(RangeSubselect); + ColumnRef *cr = makeNode(ColumnRef); + ResTarget *rt = makeNode(ResTarget); + + Assert( stmt!=SETOP_NONE ); + + leftmostSelect = stmt->larg; + while (leftmostSelect && leftmostSelect->op != SETOP_NONE) + leftmostSelect = leftmostSelect->larg; + + *newstmt = *stmt; + if ( leftmostSelect->into ) { + newstmt->into = leftmostSelect->into; + newstmt->intoColNames = leftmostSelect->intoColNames; + newstmt->intoOptions = leftmostSelect->intoOptions; + newstmt->intoOnCommit = leftmostSelect->intoOnCommit; + newstmt->intoTableSpaceName = leftmostSelect->intoTableSpaceName; + + leftmostSelect->into = NULL; + leftmostSelect->intoColNames = NULL; + leftmostSelect->intoOptions = NULL; + leftmostSelect->intoOnCommit = ONCOMMIT_NOOP; + leftmostSelect->intoTableSpaceName = NULL; + } + + stmt->sortClause = NIL; + stmt->limitOffset = NULL; + stmt->limitCount = NULL; + stmt->lockingClause = NIL; + + newstmt->larg = newstmt->rarg = NULL; + newstmt->op = SETOP_NONE; + + rs->subquery = (Node*)stmt; + rs->alias = makeNode(Alias); + rs->alias->aliasname = pstrdup("?table?"); + newstmt->fromClause = list_make1( rs ); + + cr->fields = list_make1(makeString("*")); + rt->val = (Node *)cr; + newstmt->targetList = list_make1(rt); + + return newstmt; + } + + /* * transformSetOperationsStmt - * transforms a set-operations tree * *************** *** 2410,2415 **** --- 2483,2502 ---- leftmostSelect = leftmostSelect->larg; Assert(leftmostSelect && IsA(leftmostSelect, SelectStmt) && leftmostSelect->larg == NULL); + + /* + * rewrite query from 'select .. union .. order' to + * 'select * from (select .. union) order'. It + * allows to use expression in sortClause. For now + * it just a NullTest. + */ + + if ( isExpressionInSortClause(stmt->sortClause) ) { + SelectStmt *newstmt = addSelectStmtLevel(stmt); + + return transformSelectStmt(pstate, newstmt); + } + if (leftmostSelect->into) { qry->into = leftmostSelect->into; diff -c -r -N src.orig/backend/parser/gram.y src/backend/parser/gram.y *** src.orig/backend/parser/gram.y Fri Jun 22 13:10:38 2007 --- src/backend/parser/gram.y Fri Jun 22 13:21:50 2007 *************** *** 106,111 **** --- 106,112 ---- static Node *makeSetOp(SetOperation op, bool all, Node *larg, Node *rarg); static Node *doNegate(Node *n, int location); static void doNegateFloat(Value *v); + static int4 MakeTypeModFromExpr(List *l); %} *************** *** 231,237 **** aggr_args aggr_args_list old_aggr_definition old_aggr_list oper_argtypes RuleActionList RuleActionMulti opt_column_list columnList opt_name_list ! sort_clause opt_sort_clause sortby_list index_params name_list from_clause from_list opt_array_bounds qualified_name_list any_name any_name_list any_operator expr_list attrs --- 232,238 ---- aggr_args aggr_args_list old_aggr_definition old_aggr_list oper_argtypes RuleActionList RuleActionMulti opt_column_list columnList opt_name_list ! sort_clause null_sortby opt_sort_clause sortby_list index_params name_list from_clause from_list opt_array_bounds qualified_name_list any_name any_name_list any_operator expr_list attrs *************** *** 325,331 **** %type Sconst comment_text %type RoleId opt_granted_by opt_boolean ColId_or_Sconst %type var_list var_list_or_default ! %type ColId ColLabel var_name type_name param_name %type var_value zone_value %type unreserved_keyword func_name_keyword --- 326,332 ---- %type Sconst comment_text %type RoleId opt_granted_by opt_boolean ColId_or_Sconst %type var_list var_list_or_default ! %type ColId ColLabel var_name type_function_name param_name %type var_value zone_value %type unreserved_keyword func_name_keyword *************** *** 400,406 **** NAMES NATIONAL NATURAL NCHAR NEW NEXT NO NOCREATEDB NOCREATEROLE NOCREATEUSER NOINHERIT NOLOGIN_P NONE NOSUPERUSER ! NOT NOTHING NOTIFY NOTNULL NOWAIT NULL_P NULLIF NUMERIC OBJECT_P OF OFF OFFSET OIDS OLD ON ONLY OPERATOR OPTION OR ORDER OUT_P OUTER_P OVERLAPS OVERLAY OWNED OWNER --- 401,407 ---- NAMES NATIONAL NATURAL NCHAR NEW NEXT NO NOCREATEDB NOCREATEROLE NOCREATEUSER NOINHERIT NOLOGIN_P NONE NOSUPERUSER ! NOT NOTHING NOTIFY NOTNULL NOWAIT NULL_P NULLS_P NULLIF NUMERIC OBJECT_P OF OFF OFFSET OIDS OLD ON ONLY OPERATOR OPTION OR ORDER OUT_P OUTER_P OVERLAPS OVERLAY OWNED OWNER *************** *** 465,471 **** %left Op OPERATOR /* multi-character ops and user-defined operators */ %nonassoc NOTNULL %nonassoc ISNULL ! %nonassoc IS NULL_P TRUE_P FALSE_P UNKNOWN /* sets precedence for IS NULL, etc */ %left '+' '-' %left '*' '/' '%' %left '^' --- 466,472 ---- %left Op OPERATOR /* multi-character ops and user-defined operators */ %nonassoc NOTNULL %nonassoc ISNULL ! %nonassoc IS NULL_P NULLS_P TRUE_P FALSE_P UNKNOWN /* sets precedence for IS NULL, etc */ %left '+' '-' %left '*' '/' '%' %left '^' *************** *** 3861,3867 **** /* * Ideally param_name should be ColId, but that causes too many conflicts. */ ! param_name: function_name ; func_return: --- 3862,3869 ---- /* * Ideally param_name should be ColId, but that causes too many conflicts. */ ! param_name: type_function_name ! | function_name ; func_return: *************** *** 3877,3886 **** /* * We would like to make the %TYPE productions here be ColId attrs etc, ! * but that causes reduce/reduce conflicts. type_name is next best choice. */ func_type: Typename { $$ = $1; } ! | type_name attrs '%' TYPE_P { $$ = makeNode(TypeName); $$->names = lcons(makeString($1), $2); --- 3879,3889 ---- /* * We would like to make the %TYPE productions here be ColId attrs etc, ! * but that causes reduce/reduce conflicts. type_function_name or ! * function_name is next best choice. */ func_type: Typename { $$ = $1; } ! | type_function_name attrs '%' TYPE_P { $$ = makeNode(TypeName); $$->names = lcons(makeString($1), $2); *************** *** 3888,3894 **** $$->typmod = -1; $$->location = @1; } ! | SETOF type_name attrs '%' TYPE_P { $$ = makeNode(TypeName); $$->names = lcons(makeString($2), $3); --- 3891,3914 ---- $$->typmod = -1; $$->location = @1; } ! | SETOF type_function_name attrs '%' TYPE_P ! { ! $$ = makeNode(TypeName); ! $$->names = lcons(makeString($2), $3); ! $$->pct_type = true; ! $$->typmod = -1; ! $$->setof = TRUE; ! $$->location = @2; ! } ! | function_name attrs '%' TYPE_P ! { ! $$ = makeNode(TypeName); ! $$->names = lcons(makeString($1), $2); ! $$->pct_type = true; ! $$->typmod = -1; ! $$->location = @1; ! } ! | SETOF function_name attrs '%' TYPE_P { $$ = makeNode(TypeName); $$->names = lcons(makeString($2), $3); *************** *** 5815,5824 **** ; sortby_list: ! sortby { $$ = list_make1($1); } | sortby_list ',' sortby { $$ = lappend($1, $3); } ; sortby: a_expr USING qual_all_Op { $$ = makeNode(SortBy); --- 5835,5882 ---- ; sortby_list: ! null_sortby { $$ = $1; } ! | sortby { $$ = list_make1($1); } ! | sortby_list ',' null_sortby { $$ = list_concat($1, $3); } | sortby_list ',' sortby { $$ = lappend($1, $3); } ; + null_sortby: sortby NULLS_P FIRST_P + { + if ( $1->sortby_kind != SORTBY_DESC ) + { + NullTest *n = makeNode(NullTest); + SortBy *sb = makeNode(SortBy); + + n->arg = (Expr *) $1->node; + n->nulltesttype = IS_NOT_NULL; + sb->node = (Node*)n; + sb->sortby_kind = SORTBY_ASC; + sb->useOp = NIL; + + $$ = list_make2(sb, $1); + } else + $$ = list_make1($1); + } + | sortby NULLS_P LAST_P + { + if ( $1->sortby_kind != SORTBY_ASC ) + { + NullTest *n = makeNode(NullTest); + SortBy *sb = makeNode(SortBy); + + n->arg = (Expr *) $1->node; + n->nulltesttype = IS_NOT_NULL; + sb->node = (Node*)n; + sb->sortby_kind = SORTBY_DESC; + sb->useOp = NIL; + + $$ = list_make2(sb, $1); + } else + $$ = list_make1($1); + } + ; + sortby: a_expr USING qual_all_Op { $$ = makeNode(SortBy); *************** *** 6352,6365 **** { $$ = NIL; } ; - /* - * XXX ideally, the production for a qualified typename should be ColId attrs - * (there's no obvious reason why the first name should need to be restricted) - * and should be an alternative of GenericType (so that it can be used to - * specify a type for a literal in AExprConst). However doing either causes - * reduce/reduce conflicts that I haven't been able to find a workaround - * for. FIXME later. - */ SimpleTypename: GenericType { $$ = $1; } | Numeric { $$ = $1; } --- 6410,6415 ---- *************** *** 6390,6402 **** } $$->typmod = INTERVAL_TYPMOD($3, $5); } - | type_name attrs - { - $$ = makeNode(TypeName); - $$->names = lcons(makeString($1), $2); - $$->typmod = -1; - $$->location = @1; - } ; /* We have a separate ConstTypename to allow defaulting fixed-length --- 6440,6445 ---- *************** *** 6406,6425 **** * where there is an obvious better choice to make. * Note that ConstInterval is not included here since it must * be pushed up higher in the rules to accomodate the postfix ! * options (e.g. INTERVAL '1' YEAR). */ ConstTypename: ! GenericType { $$ = $1; } ! | Numeric { $$ = $1; } | ConstBit { $$ = $1; } | ConstCharacter { $$ = $1; } | ConstDatetime { $$ = $1; } ; GenericType: ! type_name { $$ = makeTypeName($1); $$->location = @1; } ; --- 6449,6490 ---- * where there is an obvious better choice to make. * Note that ConstInterval is not included here since it must * be pushed up higher in the rules to accomodate the postfix ! * options (e.g. INTERVAL '1' YEAR). Likewise, we have to handle ! * the generic-type-name case in AExprConst to avoid premature ! * reduce/reduce conflicts against function names. */ ConstTypename: ! Numeric { $$ = $1; } | ConstBit { $$ = $1; } | ConstCharacter { $$ = $1; } | ConstDatetime { $$ = $1; } ; GenericType: ! type_function_name { $$ = makeTypeName($1); + $$->typmod = -1; + $$->location = @1; + } + | type_function_name attrs + { + $$ = makeNode(TypeName); + $$->names = lcons(makeString($1), $2); + $$->typmod = -1; + $$->location = @1; + } + | type_function_name '(' expr_list ')' + { + $$ = makeTypeName($1); + $$->typmod = MakeTypeModFromExpr( $3 ); + $$->location = @1; + } + | type_function_name attrs '(' expr_list ')' + { + $$ = makeNode(TypeName); + $$->names = lcons(makeString($1), $2); + $$->typmod = MakeTypeModFromExpr( $4 ); $$->location = @1; } ; *************** *** 6938,6944 **** | a_expr LIKE a_expr ESCAPE a_expr { FuncCall *n = makeNode(FuncCall); ! n->funcname = SystemFuncName("like_escape"); n->args = list_make2($3, $5); n->agg_star = FALSE; n->agg_distinct = FALSE; --- 7003,7009 ---- | a_expr LIKE a_expr ESCAPE a_expr { FuncCall *n = makeNode(FuncCall); ! n->funcname = list_make1(makeString("like_escape")); n->args = list_make2($3, $5); n->agg_star = FALSE; n->agg_distinct = FALSE; *************** *** 6950,6956 **** | a_expr NOT LIKE a_expr ESCAPE a_expr { FuncCall *n = makeNode(FuncCall); ! n->funcname = SystemFuncName("like_escape"); n->args = list_make2($4, $6); n->agg_star = FALSE; n->agg_distinct = FALSE; --- 7015,7021 ---- | a_expr NOT LIKE a_expr ESCAPE a_expr { FuncCall *n = makeNode(FuncCall); ! n->funcname = list_make1(makeString("like_escape")); n->args = list_make2($4, $6); n->agg_star = FALSE; n->agg_distinct = FALSE; *************** *** 6962,6968 **** | a_expr ILIKE a_expr ESCAPE a_expr { FuncCall *n = makeNode(FuncCall); ! n->funcname = SystemFuncName("like_escape"); n->args = list_make2($3, $5); n->agg_star = FALSE; n->agg_distinct = FALSE; --- 7027,7033 ---- | a_expr ILIKE a_expr ESCAPE a_expr { FuncCall *n = makeNode(FuncCall); ! n->funcname = list_make1(makeString("like_escape")); n->args = list_make2($3, $5); n->agg_star = FALSE; n->agg_distinct = FALSE; *************** *** 6974,6980 **** | a_expr NOT ILIKE a_expr ESCAPE a_expr { FuncCall *n = makeNode(FuncCall); ! n->funcname = SystemFuncName("like_escape"); n->args = list_make2($4, $6); n->agg_star = FALSE; n->agg_distinct = FALSE; --- 7039,7045 ---- | a_expr NOT ILIKE a_expr ESCAPE a_expr { FuncCall *n = makeNode(FuncCall); ! n->funcname = list_make1(makeString("like_escape")); n->args = list_make2($4, $6); n->agg_star = FALSE; n->agg_distinct = FALSE; *************** *** 6987,6993 **** A_Const *c = makeNode(A_Const); FuncCall *n = makeNode(FuncCall); c->val.type = T_Null; ! n->funcname = SystemFuncName("similar_escape"); n->args = list_make2($4, (Node *) c); n->agg_star = FALSE; n->agg_distinct = FALSE; --- 7052,7058 ---- A_Const *c = makeNode(A_Const); FuncCall *n = makeNode(FuncCall); c->val.type = T_Null; ! n->funcname = list_make1(makeString("similar_escape")); n->args = list_make2($4, (Node *) c); n->agg_star = FALSE; n->agg_distinct = FALSE; *************** *** 6997,7003 **** | a_expr SIMILAR TO a_expr ESCAPE a_expr { FuncCall *n = makeNode(FuncCall); ! n->funcname = SystemFuncName("similar_escape"); n->args = list_make2($4, $6); n->agg_star = FALSE; n->agg_distinct = FALSE; --- 7062,7068 ---- | a_expr SIMILAR TO a_expr ESCAPE a_expr { FuncCall *n = makeNode(FuncCall); ! n->funcname = list_make1(makeString("similar_escape")); n->args = list_make2($4, $6); n->agg_star = FALSE; n->agg_distinct = FALSE; *************** *** 7009,7015 **** A_Const *c = makeNode(A_Const); FuncCall *n = makeNode(FuncCall); c->val.type = T_Null; ! n->funcname = SystemFuncName("similar_escape"); n->args = list_make2($5, (Node *) c); n->agg_star = FALSE; n->agg_distinct = FALSE; --- 7074,7080 ---- A_Const *c = makeNode(A_Const); FuncCall *n = makeNode(FuncCall); c->val.type = T_Null; ! n->funcname = list_make1(makeString("similar_escape")); n->args = list_make2($5, (Node *) c); n->agg_star = FALSE; n->agg_distinct = FALSE; *************** *** 7019,7025 **** | a_expr NOT SIMILAR TO a_expr ESCAPE a_expr { FuncCall *n = makeNode(FuncCall); ! n->funcname = SystemFuncName("similar_escape"); n->args = list_make2($5, $7); n->agg_star = FALSE; n->agg_distinct = FALSE; --- 7084,7090 ---- | a_expr NOT SIMILAR TO a_expr ESCAPE a_expr { FuncCall *n = makeNode(FuncCall); ! n->funcname = list_make1(makeString("similar_escape")); n->args = list_make2($5, $7); n->agg_star = FALSE; n->agg_distinct = FALSE; *************** *** 8353,8364 **** /* * The production for a qualified func_name has to exactly match the * production for a qualified columnref, because we cannot tell which we ! * are parsing until we see what comes after it ('(' for a func_name, * anything else for a columnref). Therefore we allow 'indirection' which * may contain subscripts, and reject that case in the C code. (If we * ever implement SQL99-like methods, such syntax may actually become legal!) */ ! func_name: function_name { $$ = list_make1(makeString($1)); } | relation_name indirection { $$ = check_func_name(lcons(makeString($1), $2)); } --- 8418,8431 ---- /* * The production for a qualified func_name has to exactly match the * production for a qualified columnref, because we cannot tell which we ! * are parsing until we see what comes after it ('(' or Sconst for a func_name, * anything else for a columnref). Therefore we allow 'indirection' which * may contain subscripts, and reject that case in the C code. (If we * ever implement SQL99-like methods, such syntax may actually become legal!) */ ! func_name: type_function_name ! { $$ = list_make1(makeString($1)); } ! | function_name { $$ = list_make1(makeString($1)); } | relation_name indirection { $$ = check_func_name(lcons(makeString($1), $2)); } *************** *** 8416,8421 **** --- 8483,8506 ---- n->val.val.str = $2; $$ = (Node *)n; } + | func_name Sconst + { + A_Const *n = makeNode(A_Const); + n->typename = makeTypeNameFromNameList( $1 ); + n->typename->typmod = -1; + n->val.type = T_String; + n->val.val.str = $2; + $$ = (Node *)n; + } + | func_name '(' expr_list ')' Sconst + { + A_Const *n = makeNode(A_Const); + n->typename = makeTypeNameFromNameList( $1 ); + n->typename->typmod = MakeTypeModFromExpr( $3 ); + n->val.type = T_String; + n->val.val.str = $5; + $$ = (Node *)n; + } | ConstInterval Sconst opt_interval { A_Const *n = makeNode(A_Const); *************** *** 8492,8509 **** | col_name_keyword { $$ = pstrdup($1); } ; ! /* Type identifier --- names that can be type names. */ ! type_name: IDENT { $$ = $1; } | unreserved_keyword { $$ = pstrdup($1); } - ; /* Function identifier --- names that can be function names. */ ! function_name: ! IDENT { $$ = $1; } ! | unreserved_keyword { $$ = pstrdup($1); } ! | func_name_keyword { $$ = pstrdup($1); } ; /* Column label --- allowed labels in "AS" clauses. --- 8577,8590 ---- | col_name_keyword { $$ = pstrdup($1); } ; ! /* Type/function identifier --- names that can be type or function names. */ ! type_function_name: IDENT { $$ = $1; } | unreserved_keyword { $$ = pstrdup($1); } /* Function identifier --- names that can be function names. */ ! function_name: func_name_keyword { $$ = pstrdup($1); } ; /* Column label --- allowed labels in "AS" clauses. *************** *** 8652,8657 **** --- 8733,8739 ---- | NOTHING | NOTIFY | NOWAIT + | NULLS_P | OBJECT_P | OF | OIDS *************** *** 9320,9325 **** --- 9402,9432 ---- strcpy(newval+1, oldval); v->val.str = newval; } + } + + static int4 + MakeTypeModFromExpr(List *l) { + A_Const *tm; + int4 typmod; + + if ( list_length(l)!=1 ) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("Invalid type modifier"))); + + tm = (A_Const*)lfirst( list_head(l) ); + + if ( tm->type != T_A_Const || tm->val.type != T_Integer ) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("Invalid type modifier"))); + + typmod = tm->val.val.ival; + if (typmod < 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("Invalid type modifier"))); + return typmod; } /* diff -c -r -N src.orig/backend/parser/keywords.c src/backend/parser/keywords.c *** src.orig/backend/parser/keywords.c Fri Jun 22 13:10:38 2007 --- src/backend/parser/keywords.c Fri Jun 22 13:21:50 2007 *************** *** 239,244 **** --- 239,245 ---- {"nowait", NOWAIT}, {"null", NULL_P}, {"nullif", NULLIF}, + {"nulls", NULLS_P}, {"numeric", NUMERIC}, {"object", OBJECT_P}, {"of", OF}, diff -c -r -N src.orig/backend/parser/parse_clause.c src/backend/parser/parse_clause.c *** src.orig/backend/parser/parse_clause.c Fri Jun 22 13:10:38 2007 --- src/backend/parser/parse_clause.c Fri Jun 22 13:21:50 2007 *************** *** 1129,1151 **** return qual; } - - /* - * findTargetlistEntry - - * Returns the targetlist entry matching the given (untransformed) node. - * If no matching entry exists, one is created and appended to the target - * list as a "resjunk" node. - * - * node the ORDER BY, GROUP BY, or DISTINCT ON expression to be matched - * tlist the target list (passed by reference so we can append to it) - * clause identifies clause type being processed - */ static TargetEntry * ! findTargetlistEntry(ParseState *pstate, Node *node, List **tlist, int clause) ! { TargetEntry *target_result = NULL; ListCell *tl; - Node *expr; /*---------- * Handle two special cases as mandated by the SQL92 spec: --- 1129,1138 ---- return qual; } static TargetEntry * ! findTargetlistSpecialEntry(ParseState *pstate, Node *node, List **tlist, int clause) { TargetEntry *target_result = NULL; ListCell *tl; /*---------- * Handle two special cases as mandated by the SQL92 spec: *************** *** 1178,1189 **** * GROUP BY column-number is not allowed by SQL92, but since * the standard has no other behavior defined for this syntax, * we may as well accept this common extension. ! * * Note that pre-existing resjunk targets must not be used in either case, * since the user didn't write them in his SELECT list. - * - * If neither special case applies, fall through to treat the item as - * an expression. *---------- */ if (IsA(node, ColumnRef) && --- 1165,1173 ---- * GROUP BY column-number is not allowed by SQL92, but since * the standard has no other behavior defined for this syntax, * we may as well accept this common extension. ! * * Note that pre-existing resjunk targets must not be used in either case, * since the user didn't write them in his SELECT list. *---------- */ if (IsA(node, ColumnRef) && *************** *** 1240,1247 **** /* Stay in loop to check for ambiguity */ } } - if (target_result != NULL) - return target_result; /* return the first match */ } } if (IsA(node, A_Const)) --- 1224,1229 ---- *************** *** 1274,1279 **** --- 1256,1334 ---- clauseText[clause], target_pos))); } + return target_result; + } + + /* + * findTargetlistEntry - + * Returns the targetlist entry matching the given (untransformed) node. + * If no matching entry exists, one is created and appended to the target + * list as a "resjunk" node. + * + * node the ORDER BY, GROUP BY, or DISTINCT ON expression to be matched + * tlist the target list (passed by reference so we can append to it) + * clause identifies clause type being processed + */ + static TargetEntry * + findTargetlistEntry(ParseState *pstate, Node *node, List **tlist, int clause) + { + TargetEntry *target_result = NULL; + ListCell *tl; + Node *expr; + + /* + * Also, we check T_NullTest ( IS [NOT] NULL or NULLS (FIRST|LAST) cases ) in + * order clause. + * + * If neither special case applies, fall through to treat the item as + * an expression. + */ + + target_result = findTargetlistSpecialEntry(pstate, node, tlist, clause); + if ( target_result != NULL ) + return target_result; + + if (clause == ORDER_CLAUSE && IsA(node, NullTest)) + { + NullTest *n = (NullTest*)node; + TargetEntry *subtarget; + + subtarget = findTargetlistSpecialEntry(pstate, (Node*)n->arg, tlist, clause); + /* + * Ok, column is found (but not a exact copy if node!), so we are going to + * make new target entry + */ + if ( subtarget != NULL ) { + NullTest *nexpr = makeNode(NullTest); + + /* + * We shouls transform our node, right now transformExpr + * does nothing with NullTest... + */ + nexpr = n; + nexpr->arg = NULL; + nexpr = (NullTest*)transformExpr(pstate, (Node*)nexpr); + + nexpr->arg = copyObject(subtarget->expr); + + foreach(tl, *tlist) + { + TargetEntry *tle = (TargetEntry *) lfirst(tl); + + if (equal(nexpr, tle->expr)) + /* + * Exact copy is found + */ + return tle; + } + + target_result = transformTargetEntry(pstate, node, (Node*)nexpr, NULL, true); + *tlist = lappend(*tlist, target_result); + + return target_result; + } + } + /* * Otherwise, we have an expression (this is a Postgres extension not * found in SQL92). Convert the untransformed node to a transformed *************** *** 1456,1461 **** --- 1511,1551 ---- } /* + * isNullTestOfNext - returns true if current ListCell + * is a NullTest of next item + */ + + static bool + isNullTestOfNext( ListCell *cell, List *targetList ) { + ListCell *nextcell = lnext(cell); + SortClause *item, *nextitem; + TargetEntry *tle, *nexttle; + Expr *expr; + + if ( nextcell == NULL ) + return false; + + item = (SortClause*)lfirst(cell); + tle = get_sortgroupclause_tle(item, targetList); + + if (!IsA( tle->expr, NullTest )) + return false; + + + nextitem = (SortClause*)lfirst(nextcell); + nexttle = get_sortgroupclause_tle(nextitem, targetList); + + if ( nexttle->resjunk ) + return false; + + expr = ((NullTest*)(tle->expr))->arg; + if ( !equal(expr, nexttle->expr) ) + return false; + + return true; + } + + /* * transformDistinctClause - * transform a DISTINCT or DISTINCT ON clause * *************** *** 1496,1508 **** * Otherwise, user wrote SELECT DISTINCT with an ORDER BY item that * does not appear anywhere in the SELECT targetlist, and we can't * implement that with only one sorting pass... */ foreach(slitem, *sortClause) { SortClause *scl = (SortClause *) lfirst(slitem); TargetEntry *tle = get_sortgroupclause_tle(scl, *targetlist); ! if (tle->resjunk) ereport(ERROR, (errcode(ERRCODE_INVALID_COLUMN_REFERENCE), errmsg("for SELECT DISTINCT, ORDER BY expressions must appear in select list"))); --- 1586,1601 ---- * Otherwise, user wrote SELECT DISTINCT with an ORDER BY item that * does not appear anywhere in the SELECT targetlist, and we can't * implement that with only one sorting pass... + * + * Note, NullTest for non-resjunk column doesn't change "uniqueness" + * of result, so we should not fail if it's a Nulltest. */ foreach(slitem, *sortClause) { SortClause *scl = (SortClause *) lfirst(slitem); TargetEntry *tle = get_sortgroupclause_tle(scl, *targetlist); ! if (tle->resjunk && !isNullTestOfNext( slitem, *targetlist )) ereport(ERROR, (errcode(ERRCODE_INVALID_COLUMN_REFERENCE), errmsg("for SELECT DISTINCT, ORDER BY expressions must appear in select list"))); *************** *** 1542,1551 **** { SortClause *scl = (SortClause *) lfirst(nextsortlist); ! if (tle->ressortgroupref != scl->tleSortGroupRef) ! ereport(ERROR, ! (errcode(ERRCODE_INVALID_COLUMN_REFERENCE), ! errmsg("SELECT DISTINCT ON expressions must match initial ORDER BY expressions"))); result = lappend(result, copyObject(scl)); nextsortlist = lnext(nextsortlist); } --- 1635,1657 ---- { SortClause *scl = (SortClause *) lfirst(nextsortlist); ! if (tle->ressortgroupref != scl->tleSortGroupRef) ! { ! /* ! * NullTest of next sort clause doesn't change ! * "uniqueness", so we can just skip it ! */ ! if ( isNullTestOfNext( nextsortlist, *targetlist ) ) ! { ! nextsortlist = lnext(nextsortlist); ! scl = (SortClause *) lfirst(nextsortlist); ! } ! ! if ( tle->ressortgroupref != scl->tleSortGroupRef ) ! ereport(ERROR, ! (errcode(ERRCODE_INVALID_COLUMN_REFERENCE), ! errmsg("SELECT DISTINCT ON expressions must match initial ORDER BY expressions"))); ! } result = lappend(result, copyObject(scl)); nextsortlist = lnext(nextsortlist); } diff -c -r -N src.orig/backend/utils/adt/format_type.c src/backend/utils/adt/format_type.c *** src.orig/backend/utils/adt/format_type.c Fri Jun 22 13:10:38 2007 --- src/backend/utils/adt/format_type.c Fri Jun 22 13:21:50 2007 *************** *** 396,401 **** --- 396,404 ---- typname = NameStr(typeform->typname); buf = quote_qualified_identifier(nspname, typname); + + if ( with_typemod ) + buf = psnprintf(strlen(buf) + MAX_INT32_LEN + 3 , "%s(%d)", buf, typemod); } if (is_array) diff -c -r -N src.orig/backend/utils/adt/selfuncs.c src/backend/utils/adt/selfuncs.c *** src.orig/backend/utils/adt/selfuncs.c Fri Jun 22 13:10:38 2007 --- src/backend/utils/adt/selfuncs.c Fri Jun 22 13:21:50 2007 *************** *** 5049,5060 **** --- 5049,5079 ---- clause_op = saop->opno; found_saop = true; } + else if ( IsA(clause, NullTest) ) + { + NullTest *nt = (NullTest*)clause; + /* + * NullTests don't need to do anything with commutators, + * but in any case we should check operand. + * We hope, that match_index_to_operand() will work with + * NULL pointer to operand and returns false + */ + leftop = (Node *) nt->arg; + rightop = NULL; + clause_op = get_opclass_member( index->classlist[indexcol], + InvalidOid /* default sub type */, + (nt->nulltesttype == IS_NULL) ? BTEqualStrategyNumber : BTLessStrategyNumber ); + + if ( clause_op == InvalidOid ) + continue; + } else { elog(ERROR, "unsupported indexqual type: %d", (int) nodeTag(clause)); continue; /* keep compiler quiet */ } + if (match_index_to_operand(leftop, indexcol, index)) { /* clause_op is correct */ *************** *** 5071,5076 **** --- 5090,5111 ---- break; /* done if no '=' qual for indexcol */ indexcol++; eqQualHere = false; + + /* + * re-fill clause_op because indexcol was changed + */ + if ( IsA(clause, NullTest) ) + { + NullTest *nt = (NullTest*)clause; + + clause_op = get_opclass_member( index->classlist[indexcol], + InvalidOid /* default sub type */, + (nt->nulltesttype == IS_NULL) ? BTEqualStrategyNumber : BTLessStrategyNumber ); + + if ( clause_op == InvalidOid ) + break; + } + if (match_index_to_operand(leftop, indexcol, index)) { /* clause_op is correct */ *************** *** 5086,5093 **** break; } } op_strategy = get_op_opclass_strategy(clause_op, ! index->classlist[indexcol]); Assert(op_strategy != 0); /* not a member of opclass?? */ if (op_strategy == BTEqualStrategyNumber) eqQualHere = true; --- 5121,5130 ---- break; } } + op_strategy = get_op_opclass_strategy(clause_op, ! index->classlist[indexcol]); ! Assert(op_strategy != 0); /* not a member of opclass?? */ if (op_strategy == BTEqualStrategyNumber) eqQualHere = true; diff -c -r -N src.orig/include/access/skey.h src/include/access/skey.h *** src.orig/include/access/skey.h Fri Jun 22 13:10:42 2007 --- src/include/access/skey.h Fri Jun 22 13:21:50 2007 *************** *** 106,116 **** * bits should be defined here). Bits 16-31 are reserved for use within * individual index access methods. */ ! #define SK_ISNULL 0x0001 /* sk_argument is NULL */ ! #define SK_UNARY 0x0002 /* unary operator (currently unsupported) */ ! #define SK_ROW_HEADER 0x0004 /* row comparison header (see above) */ ! #define SK_ROW_MEMBER 0x0008 /* row comparison member (see above) */ ! #define SK_ROW_END 0x0010 /* last row comparison member (see above) */ /* --- 106,117 ---- * bits should be defined here). Bits 16-31 are reserved for use within * individual index access methods. */ ! #define SK_ISNULL 0x0001 /* sk_argument is NULL */ ! #define SK_UNARY 0x0002 /* unary operator (currently unsupported) */ ! #define SK_ROW_HEADER 0x0004 /* row comparison header (see above) */ ! #define SK_ROW_MEMBER 0x0008 /* row comparison member (see above) */ ! #define SK_ROW_END 0x0010 /* last row comparison member (see above) */ ! #define SK_INDEXFINDNULL 0x0020 /* index can find NULL with SK_ISNULL here */ /* diff -c -r -N src.orig/include/nodes/relation.h src/include/nodes/relation.h *** src.orig/include/nodes/relation.h Fri Jun 22 13:10:42 2007 --- src/include/nodes/relation.h Fri Jun 22 13:21:50 2007 *************** *** 525,530 **** --- 525,535 ---- { Path path; List *subpaths; /* list of component Paths */ + bool pull_tlist; /* if = true, create_append_plan() + should get targetlist from any + subpath - they are the same, + because the only place - append + index scan for range OR */ } AppendPath; /* diff -c -r -N src.orig/include/optimizer/pathnode.h src/include/optimizer/pathnode.h *** src.orig/include/optimizer/pathnode.h Fri Jun 22 13:10:42 2007 --- src/include/optimizer/pathnode.h Fri Jun 22 13:21:50 2007 *************** *** 46,52 **** List *bitmapquals); extern TidPath *create_tidscan_path(PlannerInfo *root, RelOptInfo *rel, List *tidquals); ! extern AppendPath *create_append_path(RelOptInfo *rel, List *subpaths); extern ResultPath *create_result_path(List *quals); extern MaterialPath *create_material_path(RelOptInfo *rel, Path *subpath); extern UniquePath *create_unique_path(PlannerInfo *root, RelOptInfo *rel, --- 46,53 ---- List *bitmapquals); extern TidPath *create_tidscan_path(PlannerInfo *root, RelOptInfo *rel, List *tidquals); ! extern AppendPath *create_append_path(RelOptInfo *rel, List *subpaths, ! bool pull_tlist, List *pathkeys); extern ResultPath *create_result_path(List *quals); extern MaterialPath *create_material_path(RelOptInfo *rel, Path *subpath); extern UniquePath *create_unique_path(PlannerInfo *root, RelOptInfo *rel, diff -c -r -N src.orig/include/optimizer/paths.h src/include/optimizer/paths.h *** src.orig/include/optimizer/paths.h Fri Jun 22 13:10:42 2007 --- src/include/optimizer/paths.h Fri Jun 22 13:21:50 2007 *************** *** 65,70 **** --- 65,71 ---- * additional routines for indexable OR clauses */ extern bool create_or_index_quals(PlannerInfo *root, RelOptInfo *rel); + extern void keybased_rewrite_index_paths(PlannerInfo *root, RelOptInfo *rel); /* * tidpath.h diff -c -r -N src.orig/include/optimizer/planmain.h src/include/optimizer/planmain.h *** src.orig/include/optimizer/planmain.h Fri Jun 22 13:10:42 2007 --- src/include/optimizer/planmain.h Fri Jun 22 13:21:50 2007 *************** *** 60,66 **** List *distinctList, AttrNumber flagColIdx); extern Result *make_result(List *tlist, Node *resconstantqual, Plan *subplan); extern bool is_projection_capable_plan(Plan *plan); ! /* * prototypes for plan/initsplan.c */ --- 60,67 ---- List *distinctList, AttrNumber flagColIdx); extern Result *make_result(List *tlist, Node *resconstantqual, Plan *subplan); extern bool is_projection_capable_plan(Plan *plan); ! extern Node * fix_indexqual_operand(Node *node, IndexOptInfo *index, ! Oid *opclass); /* * prototypes for plan/initsplan.c */ diff -c -r -N src.orig/test/regress/expected/select.out src/test/regress/expected/select.out *** src.orig/test/regress/expected/select.out Fri Jun 22 13:10:45 2007 --- src/test/regress/expected/select.out Fri Jun 22 13:21:50 2007 *************** *** 513,515 **** --- 513,633 ---- 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; diff -c -r -N src.orig/test/regress/sql/select.sql src/test/regress/sql/select.sql *** src.orig/test/regress/sql/select.sql Fri Jun 22 13:10:45 2007 --- src/test/regress/sql/select.sql Fri Jun 22 13:21:50 2007 *************** *** 138,140 **** --- 138,167 ---- SELECT 2+2, 57 UNION ALL SELECT * FROM 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; diff -c -r -N contrib.orig/fasttrun/Makefile contrib/fasttrun/Makefile *** contrib.orig/fasttrun/Makefile Thu Jan 1 03:00:00 1970 --- contrib/fasttrun/Makefile Fri Jun 22 13:21:50 2007 *************** *** 0 **** --- 1,15 ---- + MODULE_big = fasttrun + OBJS = fasttrun.o + DATA_built = fasttrun.sql + DOCS = README.fasttrun + REGRESS = fasttrun + + ifdef USE_PGXS + PGXS := $(shell pg_config --pgxs) + include $(PGXS) + else + subdir = contrib/fasttrun + top_builddir = ../.. + include $(top_builddir)/src/Makefile.global + include $(top_srcdir)/contrib/contrib-global.mk + endif diff -c -r -N contrib.orig/fasttrun/README.fasttrun contrib/fasttrun/README.fasttrun *** contrib.orig/fasttrun/README.fasttrun Thu Jan 1 03:00:00 1970 --- contrib/fasttrun/README.fasttrun Fri Jun 22 13:21:50 2007 *************** *** 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 -c -r -N contrib.orig/fasttrun/expected/fasttrun.out contrib/fasttrun/expected/fasttrun.out *** contrib.orig/fasttrun/expected/fasttrun.out Thu Jan 1 03:00:00 1970 --- contrib/fasttrun/expected/fasttrun.out Fri Jun 22 13:21:50 2007 *************** *** 0 **** --- 1,115 ---- + \set ECHO none + create table persist ( a int ); + insert into persist values (1); + select fasttruncate('persist'); + ERROR: Relation isn't a temporary table + insert into persist values (2); + select * from persist order by a; + a + --- + 1 + 2 + (2 rows) + + create temp table temp1 (a int); + insert into temp1 values (1); + BEGIN; + create temp table temp2 (a int); + insert into temp2 values (1); + select * from temp1 order by a; + a + --- + 1 + (1 row) + + select * from temp2 order by a; + a + --- + 1 + (1 row) + + insert into temp1 (select * from generate_series(1,10000)); + insert into temp2 (select * from generate_series(1,11000)); + analyze temp2; + select relname, relpages>0, reltuples>0 from pg_class where relname in ('temp1', 'temp2') order by relname; + relname | ?column? | ?column? + ---------+----------+---------- + temp1 | f | f + temp2 | t | t + (2 rows) + + select fasttruncate('temp1'); + fasttruncate + -------------- + + (1 row) + + select fasttruncate('temp2'); + fasttruncate + -------------- + + (1 row) + + insert into temp1 values (-2); + insert into temp2 values (-2); + select * from temp1 order by a; + a + ---- + -2 + (1 row) + + select * from temp2 order by a; + a + ---- + -2 + (1 row) + + COMMIT; + select * from temp1 order by a; + a + ---- + -2 + (1 row) + + select * from temp2 order by a; + a + ---- + -2 + (1 row) + + select relname, relpages>0, reltuples>0 from pg_class where relname in ('temp1', 'temp2') order by relname; + relname | ?column? | ?column? + ---------+----------+---------- + temp1 | f | f + temp2 | f | f + (2 rows) + + select fasttruncate('temp1'); + fasttruncate + -------------- + + (1 row) + + select fasttruncate('temp2'); + fasttruncate + -------------- + + (1 row) + + select * from temp1 order by a; + a + --- + (0 rows) + + select * from temp2 order by a; + a + --- + (0 rows) + + select relname, relpages>0, reltuples>0 from pg_class where relname in ('temp1', 'temp2') order by relname; + relname | ?column? | ?column? + ---------+----------+---------- + temp1 | f | f + temp2 | f | f + (2 rows) + diff -c -r -N contrib.orig/fasttrun/fasttrun.c contrib/fasttrun/fasttrun.c *** contrib.orig/fasttrun/fasttrun.c Thu Jan 1 03:00:00 1970 --- contrib/fasttrun/fasttrun.c Fri Jun 22 13:21:50 2007 *************** *** 0 **** --- 1,71 ---- + #include "postgres.h" + + #include "access/genam.h" + #include "access/heapam.h" + #include "miscadmin.h" + #include "storage/lmgr.h" + #include "catalog/namespace.h" + #include "utils/lsyscache.h" + #include "utils/builtins.h" + #include + #include + #include + #include + #include + #include + + #ifdef PG_MODULE_MAGIC + PG_MODULE_MAGIC; + #endif + + PG_FUNCTION_INFO_V1(fasttruncate); + Datum fasttruncate(PG_FUNCTION_ARGS); + Datum + fasttruncate(PG_FUNCTION_ARGS) { + text *name=PG_GETARG_TEXT_P(0); + char *relname; + List *relname_list; + RangeVar *relvar; + Oid relOid; + Relation rel; + bool makeanalyze = false; + + relname = palloc( VARSIZE(name) + 1); + memcpy(relname, VARDATA(name), VARSIZE(name)-VARHDRSZ); + relname[ VARSIZE(name)-VARHDRSZ ] = '\0'; + + relname_list = stringToQualifiedNameList(relname, "fasttruncate"); + relvar = makeRangeVarFromNameList(relname_list); + relOid = RangeVarGetRelid(relvar, false); + + if ( get_rel_relkind(relOid) != RELKIND_RELATION ) + elog(ERROR,"Relation isn't a ordinary table"); + + rel = heap_open(relOid, NoLock); + + if ( !rel->rd_istemp ) + elog(ERROR,"Relation isn't a temporary table"); + + heap_truncate(list_make1_oid(relOid)); + + if ( rel->rd_rel->relpages > 0 || rel->rd_rel->reltuples > 0 ) + makeanalyze = true; + + /* + * heap_truncate doesn't unlock the table, + * so we should unlock it. + */ + + heap_close(rel, AccessExclusiveLock); + + if ( makeanalyze ) { + VacuumStmt *vac = makeNode(VacuumStmt); + + vac->analyze = true; + vac->relation = relvar; + + vacuum(vac, NIL); + } + + PG_RETURN_VOID(); + } diff -c -r -N contrib.orig/fasttrun/fasttrun.sql contrib/fasttrun/fasttrun.sql *** contrib.orig/fasttrun/fasttrun.sql Thu Jan 1 03:00:00 1970 --- contrib/fasttrun/fasttrun.sql Fri Jun 22 13:21:50 2007 *************** *** 0 **** --- 1,8 ---- + BEGIN; + + + CREATE OR REPLACE FUNCTION fasttruncate(text) + RETURNS void AS '$libdir/fasttrun' + LANGUAGE C RETURNS NULL ON NULL INPUT IMMUTABLE; + + COMMIT; diff -c -r -N contrib.orig/fasttrun/fasttrun.sql.in contrib/fasttrun/fasttrun.sql.in *** contrib.orig/fasttrun/fasttrun.sql.in Thu Jan 1 03:00:00 1970 --- contrib/fasttrun/fasttrun.sql.in Fri Jun 22 13:21:50 2007 *************** *** 0 **** --- 1,8 ---- + BEGIN; + + + CREATE OR REPLACE FUNCTION fasttruncate(text) + RETURNS void AS 'MODULE_PATHNAME' + LANGUAGE C RETURNS NULL ON NULL INPUT IMMUTABLE; + + COMMIT; diff -c -r -N contrib.orig/fasttrun/sql/fasttrun.sql contrib/fasttrun/sql/fasttrun.sql *** contrib.orig/fasttrun/sql/fasttrun.sql Thu Jan 1 03:00:00 1970 --- contrib/fasttrun/sql/fasttrun.sql Fri Jun 22 13:21:50 2007 *************** *** 0 **** --- 1,50 ---- + \set ECHO none + \i fasttrun.sql + \set ECHO all + + create table persist ( a int ); + insert into persist values (1); + select fasttruncate('persist'); + insert into persist values (2); + select * from persist order by a; + + create temp table temp1 (a int); + insert into temp1 values (1); + + BEGIN; + + create temp table temp2 (a int); + insert into temp2 values (1); + + select * from temp1 order by a; + select * from temp2 order by a; + + insert into temp1 (select * from generate_series(1,10000)); + insert into temp2 (select * from generate_series(1,11000)); + + analyze temp2; + select relname, relpages>0, reltuples>0 from pg_class where relname in ('temp1', 'temp2') order by relname; + + select fasttruncate('temp1'); + select fasttruncate('temp2'); + + insert into temp1 values (-2); + insert into temp2 values (-2); + + select * from temp1 order by a; + select * from temp2 order by a; + + COMMIT; + + select * from temp1 order by a; + select * from temp2 order by a; + + select relname, relpages>0, reltuples>0 from pg_class where relname in ('temp1', 'temp2') order by relname; + + select fasttruncate('temp1'); + select fasttruncate('temp2'); + + select * from temp1 order by a; + select * from temp2 order by a; + + select relname, relpages>0, reltuples>0 from pg_class where relname in ('temp1', 'temp2') order by relname; diff -c -r -N contrib.orig/fulleq/Makefile contrib/fulleq/Makefile *** contrib.orig/fulleq/Makefile Thu Jan 1 03:00:00 1970 --- contrib/fulleq/Makefile Fri Jun 22 13:21:50 2007 *************** *** 0 **** --- 1,32 ---- + MODULE_big = fulleq + OBJS = fulleq.o + DATA_built = fulleq.sql + DOCS = README.fulleq + REGRESS = fulleq + + ARGTYPE = bool bytea char name int8 int2 int2vector int4 text \ + oid xid cid oidvector float4 float8 abstime reltime macaddr \ + inet cidr varchar date time timestamp timestamptz \ + interval timetz + + EXTRA_CLEAN = fulleq.sql.in + + ifdef USE_PGXS + PGXS := $(shell pg_config --pgxs) + include $(PGXS) + else + subdir = contrib/fulleq + top_builddir = ../.. + include $(top_builddir)/src/Makefile.global + include $(top_srcdir)/contrib/contrib-global.mk + endif + + fulleq.sql.in: fulleq.sql.in.in + echo 'BEGIN;' > $@ + echo 'SET search_path = public;' >> $@ + for type in $(ARGTYPE); \ + do \ + sed -e "s/ARGTYPE/$$type/g" < $< >> $@; \ + done + echo 'COMMIT;' >> $@ + diff -c -r -N contrib.orig/fulleq/README.fulleq contrib/fulleq/README.fulleq *** contrib.orig/fulleq/README.fulleq Thu Jan 1 03:00:00 1970 --- contrib/fulleq/README.fulleq Fri Jun 22 13:21:50 2007 *************** *** 0 **** --- 1,3 ---- + Introduce operator == which returns true when + operands are equal or both are nulls. + diff -c -r -N contrib.orig/fulleq/expected/fulleq.out contrib/fulleq/expected/fulleq.out *** contrib.orig/fulleq/expected/fulleq.out Thu Jan 1 03:00:00 1970 --- contrib/fulleq/expected/fulleq.out Fri Jun 22 13:21:50 2007 *************** *** 0 **** --- 1,61 ---- + \set ECHO none + select 4::int == 4; + ?column? + ---------- + t + (1 row) + + select 4::int == 5; + ?column? + ---------- + f + (1 row) + + select 4::int == NULL; + ?column? + ---------- + f + (1 row) + + select NULL::int == 5; + ?column? + ---------- + f + (1 row) + + select NULL::int == NULL; + ?column? + ---------- + t + (1 row) + + select '4'::text == '4'; + ?column? + ---------- + t + (1 row) + + select '4'::text == '5'; + ?column? + ---------- + f + (1 row) + + select '4'::text == NULL; + ?column? + ---------- + f + (1 row) + + select NULL::text == '5'; + ?column? + ---------- + f + (1 row) + + select NULL::text == NULL; + ?column? + ---------- + t + (1 row) + diff -c -r -N contrib.orig/fulleq/fulleq.c contrib/fulleq/fulleq.c *** contrib.orig/fulleq/fulleq.c Thu Jan 1 03:00:00 1970 --- contrib/fulleq/fulleq.c Fri Jun 22 13:21:50 2007 *************** *** 0 **** --- 1,70 ---- + #include "postgres.h" + #include "fmgr.h" + #include "access/hash.h" + #include "utils/builtins.h" + #include "utils/int8.h" + #include "utils/nabstime.h" + #include "utils/date.h" + + #ifdef PG_MODULE_MAGIC + PG_MODULE_MAGIC; + #endif + + #define NULLHASHVALUE (-2147483647) + + #define FULLEQ_FUNC(type, cmpfunc, hashfunc) \ + PG_FUNCTION_INFO_V1( isfulleq_##type ); \ + Datum isfulleq_##type(PG_FUNCTION_ARGS); \ + Datum \ + isfulleq_##type(PG_FUNCTION_ARGS) { \ + if ( PG_ARGISNULL(0) && PG_ARGISNULL(1) ) \ + PG_RETURN_BOOL(true); \ + else if ( PG_ARGISNULL(0) || PG_ARGISNULL(1) ) \ + PG_RETURN_BOOL(false); \ + \ + PG_RETURN_DATUM( DirectFunctionCall2( cmpfunc, \ + PG_GETARG_DATUM(0), \ + PG_GETARG_DATUM(1) \ + ) ); \ + } \ + \ + PG_FUNCTION_INFO_V1( fullhash_##type ); \ + Datum fullhash_##type(PG_FUNCTION_ARGS); \ + Datum \ + fullhash_##type(PG_FUNCTION_ARGS) { \ + if ( PG_ARGISNULL(0) ) \ + PG_RETURN_INT32(NULLHASHVALUE); \ + \ + PG_RETURN_DATUM( DirectFunctionCall1( hashfunc, \ + PG_GETARG_DATUM(0) \ + ) ); \ + } + + + FULLEQ_FUNC( bool , booleq , hashchar ); + FULLEQ_FUNC( bytea , byteaeq , hashvarlena ); + FULLEQ_FUNC( char , chareq , hashchar ); + FULLEQ_FUNC( name , nameeq , hashname ); + FULLEQ_FUNC( int8 , int8eq , hashint8 ); + FULLEQ_FUNC( int2 , int2eq , hashint2 ); + FULLEQ_FUNC( int2vector , int2vectoreq , hashint2vector ); + FULLEQ_FUNC( int4 , int4eq , hashint4 ); + FULLEQ_FUNC( text , texteq , hashtext ); + FULLEQ_FUNC( oid , oideq , hashoid ); + FULLEQ_FUNC( xid , xideq , hashint4 ); + FULLEQ_FUNC( cid , cideq , hashint4 ); + FULLEQ_FUNC( oidvector , oidvectoreq , hashoidvector ); + FULLEQ_FUNC( float4 , float4eq , hashfloat4 ); + FULLEQ_FUNC( float8 , float8eq , hashfloat8 ); + FULLEQ_FUNC( abstime , abstimeeq , hashint4 ); + FULLEQ_FUNC( reltime , reltimeeq , hashint4 ); + FULLEQ_FUNC( macaddr , macaddr_eq , hashmacaddr ); + FULLEQ_FUNC( inet , network_eq , hashinet ); + FULLEQ_FUNC( cidr , network_eq , hashinet ); + FULLEQ_FUNC( varchar , texteq , hashtext ); + FULLEQ_FUNC( date , date_eq , hashint4 ); + FULLEQ_FUNC( time , time_eq , hashfloat8 ); + FULLEQ_FUNC( timestamp , timestamp_eq , hashfloat8 ); + FULLEQ_FUNC( timestamptz , timestamp_eq , hashfloat8 ); + FULLEQ_FUNC( interval , interval_eq , interval_hash ); + FULLEQ_FUNC( timetz , timetz_eq , timetz_hash ); diff -c -r -N contrib.orig/fulleq/fulleq.sql.in contrib/fulleq/fulleq.sql.in *** contrib.orig/fulleq/fulleq.sql.in Thu Jan 1 03:00:00 1970 --- contrib/fulleq/fulleq.sql.in Fri Jun 22 13:21:50 2007 *************** *** 0 **** --- 1,705 ---- + BEGIN; + SET search_path = public; + -- For bool + + CREATE OR REPLACE FUNCTION isfulleq_bool(bool, bool) + RETURNS bool AS 'MODULE_PATHNAME' + LANGUAGE C CALLED ON NULL INPUT IMMUTABLE; + + CREATE OR REPLACE FUNCTION fullhash_bool(bool) + RETURNS int4 AS 'MODULE_PATHNAME' + LANGUAGE C CALLED ON NULL INPUT IMMUTABLE; + + + CREATE OPERATOR == ( + LEFTARG = bool, + RIGHTARG = bool, + PROCEDURE = isfulleq_bool, + COMMUTATOR = '==', + RESTRICT = eqsel, + JOIN = eqjoinsel, + HASHES + ); + + CREATE OPERATOR CLASS bool_fill_ops + FOR TYPE bool USING hash AS + OPERATOR 1 ==, + FUNCTION 1 fullhash_bool(bool); + + -- For bytea + + CREATE OR REPLACE FUNCTION isfulleq_bytea(bytea, bytea) + RETURNS bool AS 'MODULE_PATHNAME' + LANGUAGE C CALLED ON NULL INPUT IMMUTABLE; + + CREATE OR REPLACE FUNCTION fullhash_bytea(bytea) + RETURNS int4 AS 'MODULE_PATHNAME' + LANGUAGE C CALLED ON NULL INPUT IMMUTABLE; + + + CREATE OPERATOR == ( + LEFTARG = bytea, + RIGHTARG = bytea, + PROCEDURE = isfulleq_bytea, + COMMUTATOR = '==', + RESTRICT = eqsel, + JOIN = eqjoinsel, + HASHES + ); + + CREATE OPERATOR CLASS bytea_fill_ops + FOR TYPE bytea USING hash AS + OPERATOR 1 ==, + FUNCTION 1 fullhash_bytea(bytea); + + -- For char + + CREATE OR REPLACE FUNCTION isfulleq_char(char, char) + RETURNS bool AS 'MODULE_PATHNAME' + LANGUAGE C CALLED ON NULL INPUT IMMUTABLE; + + CREATE OR REPLACE FUNCTION fullhash_char(char) + RETURNS int4 AS 'MODULE_PATHNAME' + LANGUAGE C CALLED ON NULL INPUT IMMUTABLE; + + + CREATE OPERATOR == ( + LEFTARG = char, + RIGHTARG = char, + PROCEDURE = isfulleq_char, + COMMUTATOR = '==', + RESTRICT = eqsel, + JOIN = eqjoinsel, + HASHES + ); + + CREATE OPERATOR CLASS char_fill_ops + FOR TYPE char USING hash AS + OPERATOR 1 ==, + FUNCTION 1 fullhash_char(char); + + -- For name + + CREATE OR REPLACE FUNCTION isfulleq_name(name, name) + RETURNS bool AS 'MODULE_PATHNAME' + LANGUAGE C CALLED ON NULL INPUT IMMUTABLE; + + CREATE OR REPLACE FUNCTION fullhash_name(name) + RETURNS int4 AS 'MODULE_PATHNAME' + LANGUAGE C CALLED ON NULL INPUT IMMUTABLE; + + + CREATE OPERATOR == ( + LEFTARG = name, + RIGHTARG = name, + PROCEDURE = isfulleq_name, + COMMUTATOR = '==', + RESTRICT = eqsel, + JOIN = eqjoinsel, + HASHES + ); + + CREATE OPERATOR CLASS name_fill_ops + FOR TYPE name USING hash AS + OPERATOR 1 ==, + FUNCTION 1 fullhash_name(name); + + -- For int8 + + CREATE OR REPLACE FUNCTION isfulleq_int8(int8, int8) + RETURNS bool AS 'MODULE_PATHNAME' + LANGUAGE C CALLED ON NULL INPUT IMMUTABLE; + + CREATE OR REPLACE FUNCTION fullhash_int8(int8) + RETURNS int4 AS 'MODULE_PATHNAME' + LANGUAGE C CALLED ON NULL INPUT IMMUTABLE; + + + CREATE OPERATOR == ( + LEFTARG = int8, + RIGHTARG = int8, + PROCEDURE = isfulleq_int8, + COMMUTATOR = '==', + RESTRICT = eqsel, + JOIN = eqjoinsel, + HASHES + ); + + CREATE OPERATOR CLASS int8_fill_ops + FOR TYPE int8 USING hash AS + OPERATOR 1 ==, + FUNCTION 1 fullhash_int8(int8); + + -- For int2 + + CREATE OR REPLACE FUNCTION isfulleq_int2(int2, int2) + RETURNS bool AS 'MODULE_PATHNAME' + LANGUAGE C CALLED ON NULL INPUT IMMUTABLE; + + CREATE OR REPLACE FUNCTION fullhash_int2(int2) + RETURNS int4 AS 'MODULE_PATHNAME' + LANGUAGE C CALLED ON NULL INPUT IMMUTABLE; + + + CREATE OPERATOR == ( + LEFTARG = int2, + RIGHTARG = int2, + PROCEDURE = isfulleq_int2, + COMMUTATOR = '==', + RESTRICT = eqsel, + JOIN = eqjoinsel, + HASHES + ); + + CREATE OPERATOR CLASS int2_fill_ops + FOR TYPE int2 USING hash AS + OPERATOR 1 ==, + FUNCTION 1 fullhash_int2(int2); + + -- For int2vector + + CREATE OR REPLACE FUNCTION isfulleq_int2vector(int2vector, int2vector) + RETURNS bool AS 'MODULE_PATHNAME' + LANGUAGE C CALLED ON NULL INPUT IMMUTABLE; + + CREATE OR REPLACE FUNCTION fullhash_int2vector(int2vector) + RETURNS int4 AS 'MODULE_PATHNAME' + LANGUAGE C CALLED ON NULL INPUT IMMUTABLE; + + + CREATE OPERATOR == ( + LEFTARG = int2vector, + RIGHTARG = int2vector, + PROCEDURE = isfulleq_int2vector, + COMMUTATOR = '==', + RESTRICT = eqsel, + JOIN = eqjoinsel, + HASHES + ); + + CREATE OPERATOR CLASS int2vector_fill_ops + FOR TYPE int2vector USING hash AS + OPERATOR 1 ==, + FUNCTION 1 fullhash_int2vector(int2vector); + + -- For int4 + + CREATE OR REPLACE FUNCTION isfulleq_int4(int4, int4) + RETURNS bool AS 'MODULE_PATHNAME' + LANGUAGE C CALLED ON NULL INPUT IMMUTABLE; + + CREATE OR REPLACE FUNCTION fullhash_int4(int4) + RETURNS int4 AS 'MODULE_PATHNAME' + LANGUAGE C CALLED ON NULL INPUT IMMUTABLE; + + + CREATE OPERATOR == ( + LEFTARG = int4, + RIGHTARG = int4, + PROCEDURE = isfulleq_int4, + COMMUTATOR = '==', + RESTRICT = eqsel, + JOIN = eqjoinsel, + HASHES + ); + + CREATE OPERATOR CLASS int4_fill_ops + FOR TYPE int4 USING hash AS + OPERATOR 1 ==, + FUNCTION 1 fullhash_int4(int4); + + -- For text + + CREATE OR REPLACE FUNCTION isfulleq_text(text, text) + RETURNS bool AS 'MODULE_PATHNAME' + LANGUAGE C CALLED ON NULL INPUT IMMUTABLE; + + CREATE OR REPLACE FUNCTION fullhash_text(text) + RETURNS int4 AS 'MODULE_PATHNAME' + LANGUAGE C CALLED ON NULL INPUT IMMUTABLE; + + + CREATE OPERATOR == ( + LEFTARG = text, + RIGHTARG = text, + PROCEDURE = isfulleq_text, + COMMUTATOR = '==', + RESTRICT = eqsel, + JOIN = eqjoinsel, + HASHES + ); + + CREATE OPERATOR CLASS text_fill_ops + FOR TYPE text USING hash AS + OPERATOR 1 ==, + FUNCTION 1 fullhash_text(text); + + -- For oid + + CREATE OR REPLACE FUNCTION isfulleq_oid(oid, oid) + RETURNS bool AS 'MODULE_PATHNAME' + LANGUAGE C CALLED ON NULL INPUT IMMUTABLE; + + CREATE OR REPLACE FUNCTION fullhash_oid(oid) + RETURNS int4 AS 'MODULE_PATHNAME' + LANGUAGE C CALLED ON NULL INPUT IMMUTABLE; + + + CREATE OPERATOR == ( + LEFTARG = oid, + RIGHTARG = oid, + PROCEDURE = isfulleq_oid, + COMMUTATOR = '==', + RESTRICT = eqsel, + JOIN = eqjoinsel, + HASHES + ); + + CREATE OPERATOR CLASS oid_fill_ops + FOR TYPE oid USING hash AS + OPERATOR 1 ==, + FUNCTION 1 fullhash_oid(oid); + + -- For xid + + CREATE OR REPLACE FUNCTION isfulleq_xid(xid, xid) + RETURNS bool AS 'MODULE_PATHNAME' + LANGUAGE C CALLED ON NULL INPUT IMMUTABLE; + + CREATE OR REPLACE FUNCTION fullhash_xid(xid) + RETURNS int4 AS 'MODULE_PATHNAME' + LANGUAGE C CALLED ON NULL INPUT IMMUTABLE; + + + CREATE OPERATOR == ( + LEFTARG = xid, + RIGHTARG = xid, + PROCEDURE = isfulleq_xid, + COMMUTATOR = '==', + RESTRICT = eqsel, + JOIN = eqjoinsel, + HASHES + ); + + CREATE OPERATOR CLASS xid_fill_ops + FOR TYPE xid USING hash AS + OPERATOR 1 ==, + FUNCTION 1 fullhash_xid(xid); + + -- For cid + + CREATE OR REPLACE FUNCTION isfulleq_cid(cid, cid) + RETURNS bool AS 'MODULE_PATHNAME' + LANGUAGE C CALLED ON NULL INPUT IMMUTABLE; + + CREATE OR REPLACE FUNCTION fullhash_cid(cid) + RETURNS int4 AS 'MODULE_PATHNAME' + LANGUAGE C CALLED ON NULL INPUT IMMUTABLE; + + + CREATE OPERATOR == ( + LEFTARG = cid, + RIGHTARG = cid, + PROCEDURE = isfulleq_cid, + COMMUTATOR = '==', + RESTRICT = eqsel, + JOIN = eqjoinsel, + HASHES + ); + + CREATE OPERATOR CLASS cid_fill_ops + FOR TYPE cid USING hash AS + OPERATOR 1 ==, + FUNCTION 1 fullhash_cid(cid); + + -- For oidvector + + CREATE OR REPLACE FUNCTION isfulleq_oidvector(oidvector, oidvector) + RETURNS bool AS 'MODULE_PATHNAME' + LANGUAGE C CALLED ON NULL INPUT IMMUTABLE; + + CREATE OR REPLACE FUNCTION fullhash_oidvector(oidvector) + RETURNS int4 AS 'MODULE_PATHNAME' + LANGUAGE C CALLED ON NULL INPUT IMMUTABLE; + + + CREATE OPERATOR == ( + LEFTARG = oidvector, + RIGHTARG = oidvector, + PROCEDURE = isfulleq_oidvector, + COMMUTATOR = '==', + RESTRICT = eqsel, + JOIN = eqjoinsel, + HASHES + ); + + CREATE OPERATOR CLASS oidvector_fill_ops + FOR TYPE oidvector USING hash AS + OPERATOR 1 ==, + FUNCTION 1 fullhash_oidvector(oidvector); + + -- For float4 + + CREATE OR REPLACE FUNCTION isfulleq_float4(float4, float4) + RETURNS bool AS 'MODULE_PATHNAME' + LANGUAGE C CALLED ON NULL INPUT IMMUTABLE; + + CREATE OR REPLACE FUNCTION fullhash_float4(float4) + RETURNS int4 AS 'MODULE_PATHNAME' + LANGUAGE C CALLED ON NULL INPUT IMMUTABLE; + + + CREATE OPERATOR == ( + LEFTARG = float4, + RIGHTARG = float4, + PROCEDURE = isfulleq_float4, + COMMUTATOR = '==', + RESTRICT = eqsel, + JOIN = eqjoinsel, + HASHES + ); + + CREATE OPERATOR CLASS float4_fill_ops + FOR TYPE float4 USING hash AS + OPERATOR 1 ==, + FUNCTION 1 fullhash_float4(float4); + + -- For float8 + + CREATE OR REPLACE FUNCTION isfulleq_float8(float8, float8) + RETURNS bool AS 'MODULE_PATHNAME' + LANGUAGE C CALLED ON NULL INPUT IMMUTABLE; + + CREATE OR REPLACE FUNCTION fullhash_float8(float8) + RETURNS int4 AS 'MODULE_PATHNAME' + LANGUAGE C CALLED ON NULL INPUT IMMUTABLE; + + + CREATE OPERATOR == ( + LEFTARG = float8, + RIGHTARG = float8, + PROCEDURE = isfulleq_float8, + COMMUTATOR = '==', + RESTRICT = eqsel, + JOIN = eqjoinsel, + HASHES + ); + + CREATE OPERATOR CLASS float8_fill_ops + FOR TYPE float8 USING hash AS + OPERATOR 1 ==, + FUNCTION 1 fullhash_float8(float8); + + -- For abstime + + CREATE OR REPLACE FUNCTION isfulleq_abstime(abstime, abstime) + RETURNS bool AS 'MODULE_PATHNAME' + LANGUAGE C CALLED ON NULL INPUT IMMUTABLE; + + CREATE OR REPLACE FUNCTION fullhash_abstime(abstime) + RETURNS int4 AS 'MODULE_PATHNAME' + LANGUAGE C CALLED ON NULL INPUT IMMUTABLE; + + + CREATE OPERATOR == ( + LEFTARG = abstime, + RIGHTARG = abstime, + PROCEDURE = isfulleq_abstime, + COMMUTATOR = '==', + RESTRICT = eqsel, + JOIN = eqjoinsel, + HASHES + ); + + CREATE OPERATOR CLASS abstime_fill_ops + FOR TYPE abstime USING hash AS + OPERATOR 1 ==, + FUNCTION 1 fullhash_abstime(abstime); + + -- For reltime + + CREATE OR REPLACE FUNCTION isfulleq_reltime(reltime, reltime) + RETURNS bool AS 'MODULE_PATHNAME' + LANGUAGE C CALLED ON NULL INPUT IMMUTABLE; + + CREATE OR REPLACE FUNCTION fullhash_reltime(reltime) + RETURNS int4 AS 'MODULE_PATHNAME' + LANGUAGE C CALLED ON NULL INPUT IMMUTABLE; + + + CREATE OPERATOR == ( + LEFTARG = reltime, + RIGHTARG = reltime, + PROCEDURE = isfulleq_reltime, + COMMUTATOR = '==', + RESTRICT = eqsel, + JOIN = eqjoinsel, + HASHES + ); + + CREATE OPERATOR CLASS reltime_fill_ops + FOR TYPE reltime USING hash AS + OPERATOR 1 ==, + FUNCTION 1 fullhash_reltime(reltime); + + -- For macaddr + + CREATE OR REPLACE FUNCTION isfulleq_macaddr(macaddr, macaddr) + RETURNS bool AS 'MODULE_PATHNAME' + LANGUAGE C CALLED ON NULL INPUT IMMUTABLE; + + CREATE OR REPLACE FUNCTION fullhash_macaddr(macaddr) + RETURNS int4 AS 'MODULE_PATHNAME' + LANGUAGE C CALLED ON NULL INPUT IMMUTABLE; + + + CREATE OPERATOR == ( + LEFTARG = macaddr, + RIGHTARG = macaddr, + PROCEDURE = isfulleq_macaddr, + COMMUTATOR = '==', + RESTRICT = eqsel, + JOIN = eqjoinsel, + HASHES + ); + + CREATE OPERATOR CLASS macaddr_fill_ops + FOR TYPE macaddr USING hash AS + OPERATOR 1 ==, + FUNCTION 1 fullhash_macaddr(macaddr); + + -- For inet + + CREATE OR REPLACE FUNCTION isfulleq_inet(inet, inet) + RETURNS bool AS 'MODULE_PATHNAME' + LANGUAGE C CALLED ON NULL INPUT IMMUTABLE; + + CREATE OR REPLACE FUNCTION fullhash_inet(inet) + RETURNS int4 AS 'MODULE_PATHNAME' + LANGUAGE C CALLED ON NULL INPUT IMMUTABLE; + + + CREATE OPERATOR == ( + LEFTARG = inet, + RIGHTARG = inet, + PROCEDURE = isfulleq_inet, + COMMUTATOR = '==', + RESTRICT = eqsel, + JOIN = eqjoinsel, + HASHES + ); + + CREATE OPERATOR CLASS inet_fill_ops + FOR TYPE inet USING hash AS + OPERATOR 1 ==, + FUNCTION 1 fullhash_inet(inet); + + -- For cidr + + CREATE OR REPLACE FUNCTION isfulleq_cidr(cidr, cidr) + RETURNS bool AS 'MODULE_PATHNAME' + LANGUAGE C CALLED ON NULL INPUT IMMUTABLE; + + CREATE OR REPLACE FUNCTION fullhash_cidr(cidr) + RETURNS int4 AS 'MODULE_PATHNAME' + LANGUAGE C CALLED ON NULL INPUT IMMUTABLE; + + + CREATE OPERATOR == ( + LEFTARG = cidr, + RIGHTARG = cidr, + PROCEDURE = isfulleq_cidr, + COMMUTATOR = '==', + RESTRICT = eqsel, + JOIN = eqjoinsel, + HASHES + ); + + CREATE OPERATOR CLASS cidr_fill_ops + FOR TYPE cidr USING hash AS + OPERATOR 1 ==, + FUNCTION 1 fullhash_cidr(cidr); + + -- For varchar + + CREATE OR REPLACE FUNCTION isfulleq_varchar(varchar, varchar) + RETURNS bool AS 'MODULE_PATHNAME' + LANGUAGE C CALLED ON NULL INPUT IMMUTABLE; + + CREATE OR REPLACE FUNCTION fullhash_varchar(varchar) + RETURNS int4 AS 'MODULE_PATHNAME' + LANGUAGE C CALLED ON NULL INPUT IMMUTABLE; + + + CREATE OPERATOR == ( + LEFTARG = varchar, + RIGHTARG = varchar, + PROCEDURE = isfulleq_varchar, + COMMUTATOR = '==', + RESTRICT = eqsel, + JOIN = eqjoinsel, + HASHES + ); + + CREATE OPERATOR CLASS varchar_fill_ops + FOR TYPE varchar USING hash AS + OPERATOR 1 ==, + FUNCTION 1 fullhash_varchar(varchar); + + -- For date + + CREATE OR REPLACE FUNCTION isfulleq_date(date, date) + RETURNS bool AS 'MODULE_PATHNAME' + LANGUAGE C CALLED ON NULL INPUT IMMUTABLE; + + CREATE OR REPLACE FUNCTION fullhash_date(date) + RETURNS int4 AS 'MODULE_PATHNAME' + LANGUAGE C CALLED ON NULL INPUT IMMUTABLE; + + + CREATE OPERATOR == ( + LEFTARG = date, + RIGHTARG = date, + PROCEDURE = isfulleq_date, + COMMUTATOR = '==', + RESTRICT = eqsel, + JOIN = eqjoinsel, + HASHES + ); + + CREATE OPERATOR CLASS date_fill_ops + FOR TYPE date USING hash AS + OPERATOR 1 ==, + FUNCTION 1 fullhash_date(date); + + -- For time + + CREATE OR REPLACE FUNCTION isfulleq_time(time, time) + RETURNS bool AS 'MODULE_PATHNAME' + LANGUAGE C CALLED ON NULL INPUT IMMUTABLE; + + CREATE OR REPLACE FUNCTION fullhash_time(time) + RETURNS int4 AS 'MODULE_PATHNAME' + LANGUAGE C CALLED ON NULL INPUT IMMUTABLE; + + + CREATE OPERATOR == ( + LEFTARG = time, + RIGHTARG = time, + PROCEDURE = isfulleq_time, + COMMUTATOR = '==', + RESTRICT = eqsel, + JOIN = eqjoinsel, + HASHES + ); + + CREATE OPERATOR CLASS time_fill_ops + FOR TYPE time USING hash AS + OPERATOR 1 ==, + FUNCTION 1 fullhash_time(time); + + -- For timestamp + + CREATE OR REPLACE FUNCTION isfulleq_timestamp(timestamp, timestamp) + RETURNS bool AS 'MODULE_PATHNAME' + LANGUAGE C CALLED ON NULL INPUT IMMUTABLE; + + CREATE OR REPLACE FUNCTION fullhash_timestamp(timestamp) + RETURNS int4 AS 'MODULE_PATHNAME' + LANGUAGE C CALLED ON NULL INPUT IMMUTABLE; + + + CREATE OPERATOR == ( + LEFTARG = timestamp, + RIGHTARG = timestamp, + PROCEDURE = isfulleq_timestamp, + COMMUTATOR = '==', + RESTRICT = eqsel, + JOIN = eqjoinsel, + HASHES + ); + + CREATE OPERATOR CLASS timestamp_fill_ops + FOR TYPE timestamp USING hash AS + OPERATOR 1 ==, + FUNCTION 1 fullhash_timestamp(timestamp); + + -- For timestamptz + + CREATE OR REPLACE FUNCTION isfulleq_timestamptz(timestamptz, timestamptz) + RETURNS bool AS 'MODULE_PATHNAME' + LANGUAGE C CALLED ON NULL INPUT IMMUTABLE; + + CREATE OR REPLACE FUNCTION fullhash_timestamptz(timestamptz) + RETURNS int4 AS 'MODULE_PATHNAME' + LANGUAGE C CALLED ON NULL INPUT IMMUTABLE; + + + CREATE OPERATOR == ( + LEFTARG = timestamptz, + RIGHTARG = timestamptz, + PROCEDURE = isfulleq_timestamptz, + COMMUTATOR = '==', + RESTRICT = eqsel, + JOIN = eqjoinsel, + HASHES + ); + + CREATE OPERATOR CLASS timestamptz_fill_ops + FOR TYPE timestamptz USING hash AS + OPERATOR 1 ==, + FUNCTION 1 fullhash_timestamptz(timestamptz); + + -- For interval + + CREATE OR REPLACE FUNCTION isfulleq_interval(interval, interval) + RETURNS bool AS 'MODULE_PATHNAME' + LANGUAGE C CALLED ON NULL INPUT IMMUTABLE; + + CREATE OR REPLACE FUNCTION fullhash_interval(interval) + RETURNS int4 AS 'MODULE_PATHNAME' + LANGUAGE C CALLED ON NULL INPUT IMMUTABLE; + + + CREATE OPERATOR == ( + LEFTARG = interval, + RIGHTARG = interval, + PROCEDURE = isfulleq_interval, + COMMUTATOR = '==', + RESTRICT = eqsel, + JOIN = eqjoinsel, + HASHES + ); + + CREATE OPERATOR CLASS interval_fill_ops + FOR TYPE interval USING hash AS + OPERATOR 1 ==, + FUNCTION 1 fullhash_interval(interval); + + -- For timetz + + CREATE OR REPLACE FUNCTION isfulleq_timetz(timetz, timetz) + RETURNS bool AS 'MODULE_PATHNAME' + LANGUAGE C CALLED ON NULL INPUT IMMUTABLE; + + CREATE OR REPLACE FUNCTION fullhash_timetz(timetz) + RETURNS int4 AS 'MODULE_PATHNAME' + LANGUAGE C CALLED ON NULL INPUT IMMUTABLE; + + + CREATE OPERATOR == ( + LEFTARG = timetz, + RIGHTARG = timetz, + PROCEDURE = isfulleq_timetz, + COMMUTATOR = '==', + RESTRICT = eqsel, + JOIN = eqjoinsel, + HASHES + ); + + CREATE OPERATOR CLASS timetz_fill_ops + FOR TYPE timetz USING hash AS + OPERATOR 1 ==, + FUNCTION 1 fullhash_timetz(timetz); + + COMMIT; diff -c -r -N contrib.orig/fulleq/fulleq.sql.in.in contrib/fulleq/fulleq.sql.in.in *** contrib.orig/fulleq/fulleq.sql.in.in Thu Jan 1 03:00:00 1970 --- contrib/fulleq/fulleq.sql.in.in Fri Jun 22 13:21:50 2007 *************** *** 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 -c -r -N contrib.orig/fulleq/sql/fulleq.sql contrib/fulleq/sql/fulleq.sql *** contrib.orig/fulleq/sql/fulleq.sql Thu Jan 1 03:00:00 1970 --- contrib/fulleq/sql/fulleq.sql Fri Jun 22 13:21:50 2007 *************** *** 0 **** --- 1,16 ---- + \set ECHO none + \i fulleq.sql + \set ECHO all + + select 4::int == 4; + select 4::int == 5; + select 4::int == NULL; + select NULL::int == 5; + select NULL::int == NULL; + + select '4'::text == '4'; + select '4'::text == '5'; + select '4'::text == NULL; + select NULL::text == '5'; + select NULL::text == NULL; + diff -c -r -N contrib.orig/mchar/Changes contrib/mchar/Changes *** contrib.orig/mchar/Changes Thu Jan 1 03:00:00 1970 --- contrib/mchar/Changes Fri Jun 22 13:21:50 2007 *************** *** 0 **** --- 1,19 ---- + 0.17 add == operation: + a == b => ( a = b or a is null and b is null ) + 0.16 fix pg_dump - now mchar in pg_catalog scheme, not public + fix bug in mvarchar_substr() + 0.15 add upper()/lower() + 0.14 Add ESCAPE for LIKE, SIMILAR TO [ESCAPE], POSIX regexp + 0.13 Outer binary format is now different from + inner: it's just a UTF-16 string + 0.12 Fix copy binary + 0.11 Force UTF-8 convertor if server_encoding='UTF8' + 0.10 add (mchar|mvarchar)_(send|recv) functions to + allow binary copying. Note: that functions + don't recode values. + 0.9 index support for like, improve recoding functions + 0.8 initial suport for like optimizioation with index: + still thres no algo to find the nearest greater string + 0.7 hash indexes and enable a hash joins + 0.6 implicit casting mchar-mvarchar + cross type comparison operations diff -c -r -N contrib.orig/mchar/Makefile contrib/mchar/Makefile *** contrib.orig/mchar/Makefile Thu Jan 1 03:00:00 1970 --- contrib/mchar/Makefile Fri Jun 22 13:21:50 2007 *************** *** 0 **** --- 1,21 ---- + MODULE_big = mchar + OBJS = mchar_io.o mchar_proc.o mchar_op.o mchar_recode.o \ + mchar_like.o + DATA_built = mchar.sql + DATA = uninstall_mchar.sql + DOCS = README.mchar + REGRESS = init mchar mvarchar mm like compat + + PG_CPPFLAGS=-I/usr/local/include + + ifdef USE_PGXS + PGXS := $(shell pg_config --pgxs) + include $(PGXS) + else + subdir = contrib/mchar + top_builddir = ../.. + include $(top_builddir)/src/Makefile.global + include $(top_srcdir)/contrib/contrib-global.mk + endif + + SHLIB_LINK += -L/usr/local/lib -licuuc -Wl,-rpath,'$$ORIGIN' diff -c -r -N contrib.orig/mchar/README.mchar contrib/mchar/README.mchar *** contrib.orig/mchar/README.mchar Thu Jan 1 03:00:00 1970 --- contrib/mchar/README.mchar Fri Jun 22 13:21:50 2007 *************** *** 0 **** --- 1,20 ---- + MCHAR & VARCHAR + type modifier + length() + substr(str, pos[, length]) + || - concatenation with any (mchar,mvarchar) arguments + < <= = >= > - case-insensitive comparisons (libICU) + &< &<= &= &>= &> - case-sensitive comparisons (libICU) + implicit casting mchar<->mvarchar + B-tree and hash index + LIKE [ESCAPE] + SIMILAR TO [ESCAPE] + ~ (POSIX regexp) + index support for LIKE + + + Authors: + Oleg Bartunov + Teodor Sigaev + + diff -c -r -N contrib.orig/mchar/data/ch.sql contrib/mchar/data/ch.sql *** contrib.orig/mchar/data/ch.sql Thu Jan 1 03:00:00 1970 --- contrib/mchar/data/ch.sql Fri Jun 22 13:21:50 2007 *************** *** 0 **** --- 1,11 ---- + 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'); diff -c -r -N contrib.orig/mchar/data/chvch.sql contrib/mchar/data/chvch.sql *** contrib.orig/mchar/data/chvch.sql Thu Jan 1 03:00:00 1970 --- contrib/mchar/data/chvch.sql Fri Jun 22 13:21:50 2007 *************** *** 0 **** --- 1,9 ---- + 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 -c -r -N contrib.orig/mchar/expected/compat.out contrib/mchar/expected/compat.out *** contrib.orig/mchar/expected/compat.out Thu Jan 1 03:00:00 1970 --- contrib/mchar/expected/compat.out Fri Jun 22 13:21:50 2007 *************** *** 0 **** --- 1,66 ---- + --- table based checks + select '<' || ch || '>', '<' || vch || '>' from chvch; + ?column? | ?column? + ----------------+-------------- + | + | + <1 space > | <1 space > + (3 rows) + + select * from chvch where vch = 'One space'; + ch | vch + --------------+------------ + One space | One space + (1 row) + + select * from chvch where vch = 'One space '; + ch | vch + --------------+------------ + One space | One space + (1 row) + + select * from ch where chcol = 'abcd' order by chcol; + chcol + ---------------------------------- + abcd + AbcD + (2 rows) + + select * from ch t1 join ch t2 on t1.chcol = t2.chcol order by t1.chcol, t2.chcol; + chcol | chcol + ----------------------------------+---------------------------------- + abcd | abcd + abcd | AbcD + AbcD | AbcD + AbcD | abcd + abcz | abcz + defg | defg + defg | dEfg + dEfg | dEfg + dEfg | defg + ee | Ee + ee | ee + Ee | Ee + Ee | ee + (13 rows) + + select * from ch where chcol > 'abcd' and chcol<'ee'; + chcol + ---------------------------------- + abcz + defg + dEfg + (3 rows) + + select * from ch order by chcol; + chcol + ---------------------------------- + abcd + AbcD + abcz + defg + dEfg + ee + Ee + (7 rows) + diff -c -r -N contrib.orig/mchar/expected/init.out contrib/mchar/expected/init.out *** contrib.orig/mchar/expected/init.out Thu Jan 1 03:00:00 1970 --- contrib/mchar/expected/init.out Fri Jun 22 13:21:50 2007 *************** *** 0 **** --- 1,15 ---- + -- + -- first, define the datatype. Turn off echoing so that expected file + -- does not depend on contents of mchar.sql. + -- + \set ECHO none + psql:mchar.sql:10: NOTICE: type "mchar" is not yet defined + DETAIL: Creating a shell type definition. + psql:mchar.sql:15: NOTICE: argument type mchar is only a shell + psql:mchar.sql:20: NOTICE: argument type mchar is only a shell + psql:mchar.sql:25: NOTICE: return type mchar is only a shell + psql:mchar.sql:47: NOTICE: type "mvarchar" is not yet defined + DETAIL: Creating a shell type definition. + psql:mchar.sql:52: NOTICE: argument type mvarchar is only a shell + psql:mchar.sql:57: NOTICE: argument type mvarchar is only a shell + psql:mchar.sql:62: NOTICE: return type mvarchar is only a shell diff -c -r -N contrib.orig/mchar/expected/like.out contrib/mchar/expected/like.out *** contrib.orig/mchar/expected/like.out Thu Jan 1 03:00:00 1970 --- contrib/mchar/expected/like.out Fri Jun 22 15:48:10 2007 *************** *** 0 **** --- 1,780 ---- + -- 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 + ------------ + Abc-000001 + Abc-000002 + 0000000001 + 0000000002 + (4 rows) + + set enable_seqscan = on; + drop table testt; diff -c -r -N contrib.orig/mchar/expected/mchar.out contrib/mchar/expected/mchar.out *** contrib.orig/mchar/expected/mchar.out Thu Jan 1 03:00:00 1970 --- contrib/mchar/expected/mchar.out Fri Jun 22 13:21:50 2007 *************** *** 0 **** --- 1,363 ---- + -- I/O tests + select '1'::mchar; + mchar + ------- + 1 + (1 row) + + select '2 '::mchar; + mchar + ------- + 2 + (1 row) + + select '10 '::mchar; + mchar + ------- + 10 + (1 row) + + select '1'::mchar(2); + mchar + ------- + 1 + (1 row) + + select '2 '::mchar(2); + mchar + ------- + 2 + (1 row) + + select '3 '::mchar(2); + mchar + ------- + 3 + (1 row) + + select '10 '::mchar(2); + mchar + ------- + 10 + (1 row) + + select ' '::mchar(10); + mchar + ------------ + + (1 row) + + select ' '::mchar; + mchar + ------- + + (1 row) + + -- operations & functions + select length('1'::mchar); + length + -------- + 1 + (1 row) + + select length('2 '::mchar); + length + -------- + 1 + (1 row) + + select length('10 '::mchar); + length + -------- + 2 + (1 row) + + select length('1'::mchar(2)); + length + -------- + 1 + (1 row) + + select length('2 '::mchar(2)); + length + -------- + 1 + (1 row) + + select length('3 '::mchar(2)); + length + -------- + 1 + (1 row) + + select length('10 '::mchar(2)); + length + -------- + 2 + (1 row) + + select length(' '::mchar(10)); + length + -------- + 0 + (1 row) + + select length(' '::mchar); + length + -------- + 0 + (1 row) + + select 'asd'::mchar(10) || '>'::mchar(10); + ?column? + ---------------------- + asd > + (1 row) + + select length('asd'::mchar(10) || '>'::mchar(10)); + length + -------- + 11 + (1 row) + + select 'asd'::mchar(2) || '>'::mchar(10); + ?column? + -------------- + as> + (1 row) + + select length('asd'::mchar(2) || '>'::mchar(10)); + length + -------- + 3 + (1 row) + + -- Comparisons + select 'asdf'::mchar = 'aSdf'::mchar; + ?column? + ---------- + t + (1 row) + + select 'asdf'::mchar = 'aSdf '::mchar; + ?column? + ---------- + t + (1 row) + + select 'asdf'::mchar = 'aSdf 1'::mchar(4); + ?column? + ---------- + t + (1 row) + + select 'asdf'::mchar = 'aSdf 1'::mchar(5); + ?column? + ---------- + t + (1 row) + + select 'asdf'::mchar = 'aSdf 1'::mchar(6); + ?column? + ---------- + f + (1 row) + + select 'asdf'::mchar(3) = 'aSdf 1'::mchar(5); + ?column? + ---------- + f + (1 row) + + select 'asdf'::mchar(3) = 'aSdf 1'::mchar(3); + ?column? + ---------- + t + (1 row) + + select 'asdf'::mchar < 'aSdf'::mchar; + ?column? + ---------- + f + (1 row) + + select 'asdf'::mchar < 'aSdf '::mchar; + ?column? + ---------- + f + (1 row) + + select 'asdf'::mchar < 'aSdf 1'::mchar(4); + ?column? + ---------- + f + (1 row) + + select 'asdf'::mchar < 'aSdf 1'::mchar(5); + ?column? + ---------- + f + (1 row) + + select 'asdf'::mchar < 'aSdf 1'::mchar(6); + ?column? + ---------- + t + (1 row) + + select 'asdf'::mchar <= 'aSdf'::mchar; + ?column? + ---------- + t + (1 row) + + select 'asdf'::mchar <= 'aSdf '::mchar; + ?column? + ---------- + t + (1 row) + + select 'asdf'::mchar <= 'aSdf 1'::mchar(4); + ?column? + ---------- + t + (1 row) + + select 'asdf'::mchar <= 'aSdf 1'::mchar(5); + ?column? + ---------- + t + (1 row) + + select 'asdf'::mchar <= 'aSdf 1'::mchar(6); + ?column? + ---------- + t + (1 row) + + select 'asdf'::mchar >= 'aSdf'::mchar; + ?column? + ---------- + t + (1 row) + + select 'asdf'::mchar >= 'aSdf '::mchar; + ?column? + ---------- + t + (1 row) + + select 'asdf'::mchar >= 'aSdf 1'::mchar(4); + ?column? + ---------- + t + (1 row) + + select 'asdf'::mchar >= 'aSdf 1'::mchar(5); + ?column? + ---------- + t + (1 row) + + select 'asdf'::mchar >= 'aSdf 1'::mchar(6); + ?column? + ---------- + f + (1 row) + + select 'asdf'::mchar > 'aSdf'::mchar; + ?column? + ---------- + f + (1 row) + + select 'asdf'::mchar > 'aSdf '::mchar; + ?column? + ---------- + f + (1 row) + + select 'asdf'::mchar > 'aSdf 1'::mchar(4); + ?column? + ---------- + f + (1 row) + + select 'asdf'::mchar > 'aSdf 1'::mchar(5); + ?column? + ---------- + f + (1 row) + + select 'asdf'::mchar > 'aSdf 1'::mchar(6); + ?column? + ---------- + f + (1 row) + + select max(ch) from chvch; + max + -------------- + One space + (1 row) + + select min(ch) from chvch; + min + -------------- + 1 space + (1 row) + + select substr('1234567890'::mchar, 3) = '34567890' as "34567890"; + 34567890 + ---------- + f + (1 row) + + select substr('1234567890'::mchar, 4, 3) = '456' as "456"; + 456 + ----- + t + (1 row) + + select lower('asdfASDF'::mchar); + lower + ---------- + asdfasdf + (1 row) + + select upper('asdfASDF'::mchar); + upper + ---------- + ASDFASDF + (1 row) + + select 'asd'::mchar == 'aSd'::mchar; + ?column? + ---------- + t + (1 row) + + select 'asd'::mchar == 'aCd'::mchar; + ?column? + ---------- + f + (1 row) + + select 'asd'::mchar == NULL; + ?column? + ---------- + f + (1 row) + + select NULL == 'aCd'::mchar; + ?column? + ---------- + f + (1 row) + + select NULL::mchar == NULL; + ?column? + ---------- + t + (1 row) + diff -c -r -N contrib.orig/mchar/expected/mm.out contrib/mchar/expected/mm.out *** contrib.orig/mchar/expected/mm.out Thu Jan 1 03:00:00 1970 --- contrib/mchar/expected/mm.out Fri Jun 22 13:21:50 2007 *************** *** 0 **** --- 1,790 ---- + 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 diff -c -r -N contrib.orig/mchar/expected/mvarchar.out contrib/mchar/expected/mvarchar.out *** contrib.orig/mchar/expected/mvarchar.out Thu Jan 1 03:00:00 1970 --- contrib/mchar/expected/mvarchar.out Fri Jun 22 13:21:50 2007 *************** *** 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 -c -r -N contrib.orig/mchar/mchar.h contrib/mchar/mchar.h *** contrib.orig/mchar/mchar.h Thu Jan 1 03:00:00 1970 --- contrib/mchar/mchar.h Fri Jun 22 13:21:50 2007 *************** *** 0 **** --- 1,58 ---- + #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 { + int4 len; + int4 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 { + int4 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); + + void FillWhiteSpace( UChar *dst, int n ); + + int lengthWithoutSpace(MVarChar *m); + + extern Datum mchar_hash(PG_FUNCTION_ARGS); + extern Datum mvarchar_hash(PG_FUNCTION_ARGS); + + #endif diff -c -r -N contrib.orig/mchar/mchar.sql.in contrib/mchar/mchar.sql.in *** contrib.orig/mchar/mchar.sql.in Thu Jan 1 03:00:00 1970 --- contrib/mchar/mchar.sql.in Fri Jun 22 13:21:50 2007 *************** *** 0 **** --- 1,1287 ---- + SET search_path = public; + + BEGIN; + + -- I/O functions + + 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, + 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, + 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, mchar) + RETURNS bool + AS 'MODULE_PATHNAME' + LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + + CREATE FUNCTION like_escape(mchar, mchar) + RETURNS mchar + AS 'MODULE_PATHNAME', 'mchar_like_escape' + LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + + CREATE FUNCTION mchar_notlike(mchar, mchar) + RETURNS bool + AS 'MODULE_PATHNAME' + LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + + CREATE OPERATOR ~~ ( + LEFTARG = mchar, + RIGHTARG = mchar, + PROCEDURE = mchar_like, + RESTRICT = likesel, + JOIN = likejoinsel, + NEGATOR = '!~~' + ); + + CREATE OPERATOR !~~ ( + LEFTARG = mchar, + RIGHTARG = mchar, + 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 CLASS mchar_icase_ops + DEFAULT FOR TYPE mchar USING btree 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_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 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_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(bool, internal, internal, internal, internal) + RETURNS int4 + AS 'MODULE_PATHNAME' + LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + + CREATE FUNCTION mchar_greaterstring(bool, internal) + RETURNS internal + AS 'MODULE_PATHNAME' + LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT; + + CREATE OR REPLACE FUNCTION isfulleq_mchar(mchar, mchar) + RETURNS bool AS 'MODULE_PATHNAME' + LANGUAGE C CALLED ON NULL INPUT IMMUTABLE; + + CREATE OR REPLACE FUNCTION fullhash_mchar(mchar) + RETURNS int4 AS 'MODULE_PATHNAME' + LANGUAGE C CALLED ON NULL INPUT IMMUTABLE; + + + CREATE OPERATOR == ( + LEFTARG = mchar, + RIGHTARG = mchar, + PROCEDURE = isfulleq_mchar, + COMMUTATOR = '==', + RESTRICT = eqsel, + JOIN = eqjoinsel, + HASHES + ); + + CREATE OPERATOR CLASS mchar_fill_ops + FOR TYPE mchar USING hash AS + OPERATOR 1 ==, + FUNCTION 1 fullhash_mchar(mchar); + + CREATE OR REPLACE FUNCTION isfulleq_mvarchar(mvarchar, mvarchar) + RETURNS bool AS 'MODULE_PATHNAME' + LANGUAGE C CALLED ON NULL INPUT IMMUTABLE; + + CREATE OR REPLACE FUNCTION fullhash_mvarchar(mvarchar) + RETURNS int4 AS 'MODULE_PATHNAME' + LANGUAGE C CALLED ON NULL INPUT IMMUTABLE; + + + CREATE OPERATOR == ( + LEFTARG = mvarchar, + RIGHTARG = mvarchar, + PROCEDURE = isfulleq_mvarchar, + COMMUTATOR = '==', + RESTRICT = eqsel, + JOIN = eqjoinsel, + HASHES + ); + + CREATE OPERATOR CLASS mvarchar_fill_ops + FOR TYPE mvarchar USING hash AS + OPERATOR 1 ==, + FUNCTION 1 fullhash_mvarchar(mvarchar); + + COMMIT; + SET search_path = public; + diff -c -r -N contrib.orig/mchar/mchar_io.c contrib/mchar/mchar_io.c *** contrib.orig/mchar/mchar_io.c Thu Jan 1 03:00:00 1970 --- contrib/mchar/mchar_io.c Fri Jun 22 13:21:50 2007 *************** *** 0 **** --- 1,332 ---- + #include "mchar.h" + #include "mb/pg_wchar.h" + #include "fmgr.h" + #include "libpq/pqformat.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); + + 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); + VARATT_SIZEP(m) = sizeof(UChar) * i + MCHARHDRSZ; + } + } + + m->typmod = atttypmod; + + maxlen = UCHARLENGTH(m); + while( maxlen>0 && u_isspace( m->data[ maxlen-1 ] ) ) + maxlen--; + + VARATT_SIZEP(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; + int4 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 ); + VARATT_SIZEP(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; + int4 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 ); + VARATT_SIZEP(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); + VARATT_SIZEP(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 ); + VARATT_SIZEP(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; + } + VARATT_SIZEP(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; + 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; + pq_copymsgbytes(buf, (char*)res->data, nbytes); + + mvarchar_strip( res, atttypmod ); + + PG_RETURN_MVARCHAR(res); + } + + diff -c -r -N contrib.orig/mchar/mchar_like.c contrib/mchar/mchar_like.c *** contrib.orig/mchar/mchar_like.c Thu Jan 1 03:00:00 1970 --- contrib/mchar/mchar_like.c Fri Jun 22 15:48:10 2007 *************** *** 0 **** --- 1,884 ---- + #include "mchar.h" + #include "mb/pg_wchar.h" + + #include "utils/selfuncs.h" + #include "nodes/primnodes.h" + #include "nodes/makefuncs.h" + #include "regex/regex.h" + + /* + ** Originally written by Rich $alz, mirror!rs, Wed Nov 26 19:03:17 EST 1986. + ** Rich $alz is now . + ** Special thanks to Lars Mathiesen for the LABORT code. + ** + ** This code was shamelessly stolen from the "pql" code by myself and + ** slightly modified :) + ** + ** All references to the word "star" were replaced by "percent" + ** All references to the word "wild" were replaced by "like" + ** + ** All the nice shell RE matching stuff was replaced by just "_" and "%" + ** + ** As I don't have a copy of the SQL standard handy I wasn't sure whether + ** to leave in the '\' escape character handling. + ** + ** Keith Parks. + ** + ** SQL92 lets you specify the escape character by saying + ** LIKE ESCAPE . We are a small operation + ** so we force you to use '\'. - ay 7/95 + ** + ** Now we have the like_escape() function that converts patterns with + ** any specified escape character (or none at all) to the internal + ** default escape character, which is still '\'. - tgl 9/2000 + ** + ** The code is rewritten to avoid requiring null-terminated strings, + ** which in turn allows us to leave out some memcpy() operations. + ** This code should be faster and take less memory, but no promises... + ** - thomas 2000-08-06 + ** + ** Adopted for UTF-16 by teodor + */ + + #define LIKE_TRUE 1 + #define LIKE_FALSE 0 + #define LIKE_ABORT (-1) + + + static int + uchareq(UChar *p1, UChar *p2) { + int l1=0, l2=0; + UErrorCode err = 0; + int res; + /* + * Count length of char: + * We suppose that string is correct!! + */ + U16_FWD_1(p1, l1, 2); + U16_FWD_1(p2, l2, 2); + + res = (u_strCaseCompare(p1, l1, p2, l2, 0, &err)==0) ? 1 : 0; + + if ( U_FAILURE(err) ) + elog(ERROR,"ICU u_strCaseCompare returns %d", err); + + return res; + } + + #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; + + #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, '"' ); \ + } + + static int + MatchUChar(UChar *t, int tlen, UChar *p, int plen) { + SET_UCHAR; + + /* Fast path for match-everything pattern */ + if ((plen == 1) && (*p == UCharPercent)) + return LIKE_TRUE; + + while ((tlen > 0) && (plen > 0)) { + if (*p == UCharBackSlesh) { + /* Next pattern char must match literally, whatever it is */ + NextChar(p, plen); + if ((plen <= 0) || !uchareq(t, p)) + return LIKE_FALSE; + } else if (*p == UCharPercent) { + /* %% is the same as % according to the SQL standard */ + /* Advance past all %'s */ + while ((plen > 0) && (*p == UCharPercent)) + NextChar(p, plen); + /* Trailing percent matches everything. */ + if (plen <= 0) + return LIKE_TRUE; + + /* + * Otherwise, scan for a text position at which we can match the + * rest of the pattern. + */ + while (tlen > 0) { + /* + * Optimization to prevent most recursion: don't recurse + * unless first pattern char might match this text char. + */ + if (uchareq(t, p) || (*p == UCharBackSlesh) || (*p == UCharUnderLine)) { + int matched = MatchUChar(t, tlen, p, plen); + + if (matched != LIKE_FALSE) + return matched; /* TRUE or ABORT */ + } + + NextChar(t, tlen); + } + + /* + * End of text with no match, so no point in trying later places + * to start matching this pattern. + */ + return LIKE_ABORT; + } if ((*p != UCharUnderLine) && !uchareq(t, p)) { + /* + * Not the single-character wildcard and no explicit match? Then + * time to quit... + */ + return LIKE_FALSE; + } + + NextChar(t, tlen); + NextChar(p, plen); + } + + if (tlen > 0) + return LIKE_FALSE; /* end of pattern, but not of text */ + + /* End of input string. Do we have matching pattern remaining? */ + while ((plen > 0) && (*p == UCharPercent)) /* allow multiple %'s at end of + * pattern */ + NextChar(p, plen); + if (plen <= 0) + return LIKE_TRUE; + + /* + * End of text with no match, so no point in trying later places to start + * matching this pattern. + */ + + return LIKE_ABORT; + } + + PG_FUNCTION_INFO_V1( mvarchar_like ); + Datum mvarchar_like( PG_FUNCTION_ARGS ); + Datum + mvarchar_like( PG_FUNCTION_ARGS ) { + MVarChar *str = PG_GETARG_MVARCHAR(0); + MVarChar *pat = PG_GETARG_MVARCHAR(1); + bool result; + + result = MatchUChar( str->data, UVARCHARLENGTH(str), pat->data, UVARCHARLENGTH(pat) ); + + PG_FREE_IF_COPY(str,0); + PG_FREE_IF_COPY(pat,1); + + PG_RETURN_BOOL(result == LIKE_TRUE); + } + + PG_FUNCTION_INFO_V1( mvarchar_notlike ); + Datum mvarchar_notlike( PG_FUNCTION_ARGS ); + Datum + mvarchar_notlike( PG_FUNCTION_ARGS ) { + bool res = DatumGetBool( DirectFunctionCall2( + mvarchar_like, + PG_GETARG_DATUM(0), + PG_GETARG_DATUM(1) + )); + PG_RETURN_BOOL( !res ); + } + + /* + * Removes trailing spaces in '111 %' pattern + */ + static UChar * + removeTrailingSpaces( UChar *src, int srclen, int *dstlen) { + UChar* dst = src; + UChar *ptr, *dptr, *markptr; + + *dstlen = srclen; + ptr = src + srclen-1; + SET_UCHAR; + + while( ptr>=src ) { + if ( *ptr == UCharPercent || *ptr == UCharUnderLine ) { + if ( ptr>src && *(ptr-1) == UCharBackSlesh ) + return dst; /* use src as is */ + } else if ( u_isspace(*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; + } + + PG_FUNCTION_INFO_V1( mchar_like ); + Datum mchar_like( PG_FUNCTION_ARGS ); + Datum + mchar_like( PG_FUNCTION_ARGS ) { + MChar *str = PG_GETARG_MCHAR(0); + MChar *pat = PG_GETARG_MCHAR(1); + bool result; + UChar *cleaned; + int clen=0; + + cleaned = removeTrailingSpaces(pat->data, UCHARLENGTH(pat), &clen); + + result = MatchUChar( str->data, UCHARLENGTH(str), cleaned, clen ); + + if ( pat->data != cleaned ) + pfree( cleaned ); + + PG_FREE_IF_COPY(str,0); + PG_FREE_IF_COPY(pat,1); + + + PG_RETURN_BOOL(result == LIKE_TRUE); + } + + PG_FUNCTION_INFO_V1( mchar_notlike ); + Datum mchar_notlike( PG_FUNCTION_ARGS ); + Datum + mchar_notlike( PG_FUNCTION_ARGS ) { + bool res = DatumGetBool( DirectFunctionCall2( + mchar_like, + PG_GETARG_DATUM(0), + PG_GETARG_DATUM(1) + )); + + PG_RETURN_BOOL( !res ); + } + + + + PG_FUNCTION_INFO_V1( mchar_pattern_fixed_prefix ); + Datum mchar_pattern_fixed_prefix( PG_FUNCTION_ARGS ); + Datum + mchar_pattern_fixed_prefix( PG_FUNCTION_ARGS ) { + bool isMChar = PG_GETARG_BOOL(0); + Const *patt = (Const*)PG_GETARG_POINTER(1); + Pattern_Type ptype = (Pattern_Type)PG_GETARG_INT32(2); + Const **prefix = (Const**)PG_GETARG_POINTER(3); + Const **rest = (Const**)PG_GETARG_POINTER(4); + UChar *spatt; + int4 slen, prefixlen=0, restlen=0, i=0; + MVarChar *sprefix; + MVarChar *srest; + Pattern_Prefix_Status status = Pattern_Prefix_None; + + *prefix = *rest = NULL; + + if ( ptype != Pattern_Type_Like ) + PG_RETURN_INT32(Pattern_Prefix_None); + + SET_UCHAR; + + if ( isMChar ) { + spatt = ((MChar*)DatumGetPointer(patt->constvalue))->data; + slen = UCHARLENGTH( DatumGetPointer(patt->constvalue) ); + } else { + spatt = ((MVarChar*)DatumGetPointer(patt->constvalue))->data; + slen = UVARCHARLENGTH( DatumGetPointer(patt->constvalue) ); + } + + sprefix = (MVarChar*)palloc( MCHARHDRSZ /*The biggest hdr!! */ + sizeof(UChar) * slen ); + srest = (MVarChar*)palloc( MCHARHDRSZ /*The biggest hdr!! */ + sizeof(UChar) * slen ); + + while( prefixlen < slen && i < slen ) { + if ( spatt[i] == UCharPercent || spatt[i] == UCharUnderLine ) + break; + else if ( spatt[i] == UCharBackSlesh ) { + i++; + if ( i>= slen ) + break; + } + sprefix->data[ prefixlen++ ] = spatt[i++]; + } + + while( prefixlen > 0 ) { + if ( ! u_isspace( sprefix->data[ prefixlen-1 ] ) ) + break; + prefixlen--; + } + + if ( prefixlen == 0 ) + PG_RETURN_INT32(Pattern_Prefix_None); + + for(;idata[ restlen++ ] = spatt[i]; + + if ( isMChar ) { + MChar *tmp; + + tmp = (MChar*)sprefix; + VARATT_SIZEP(tmp) = sizeof(UChar) * prefixlen + MCHARHDRSZ; + memmove( tmp->data, sprefix->data, sizeof(UChar) * prefixlen ); + tmp->typmod = -1; + + tmp = (MChar*)srest; + VARATT_SIZEP(tmp) = sizeof(UChar) * restlen + MCHARHDRSZ; + memmove( tmp->data, srest->data, sizeof(UChar) * restlen ); + tmp->typmod = -1; + } else { + VARATT_SIZEP(sprefix) = sizeof(UChar) * prefixlen + MVARCHARHDRSZ; + VARATT_SIZEP(srest) = sizeof(UChar) * restlen + MVARCHARHDRSZ; + } + + *prefix = makeConst( patt->consttype, -1, PointerGetDatum(sprefix), false, false ); + *rest = makeConst( patt->consttype, -1, PointerGetDatum(srest), 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 ) { + bool res; + UErrorCode err = 0; + + res = (u_strCaseCompare( + left, leftlen, + right, rightlen, + 0, &err + ) < 0 ) ? true : false; + + if ( U_FAILURE(err) ) + res = false; + + return res; + } + + + PG_FUNCTION_INFO_V1( mchar_greaterstring ); + Datum mchar_greaterstring( PG_FUNCTION_ARGS ); + Datum + mchar_greaterstring( PG_FUNCTION_ARGS ) { + bool isMChar = PG_GETARG_BOOL(0); + Const *patt = (Const*)PG_GETARG_POINTER(1); + char *src = (char*)DatumGetPointer( patt->constvalue ); + int dstlen, srclen = VARSIZE(src); + char *dst = palloc( srclen ); + UChar *ptr, *srcptr; + + memcpy( dst, src, srclen ); + + if ( isMChar ) { + srclen = dstlen = UCHARLENGTH( dst ); + ptr = ((MChar*)dst)->data; + srcptr = ((MChar*)src)->data; + } else { + 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 { + if ( isMChar ) + VARATT_SIZEP(dst) = sizeof(UChar) * dstlen + MCHARHDRSZ; + else + VARATT_SIZEP(dst) = sizeof(UChar) * dstlen + MVARCHARHDRSZ; + + PG_RETURN_POINTER( makeConst( patt->consttype, -1, 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( mchar_like_escape ); + Datum mchar_like_escape( PG_FUNCTION_ARGS ); + Datum + mchar_like_escape( PG_FUNCTION_ARGS ) { + MChar *pat = PG_GETARG_MCHAR(0); + MChar *esc = PG_GETARG_MCHAR(1); + MChar *result; + + result = (MChar*)palloc( MCHARHDRSZ + sizeof(UChar)*2*UCHARLENGTH(pat) ); + result->len = MCHARHDRSZ + do_like_escape( pat->data, UCHARLENGTH(pat), + esc->data, UCHARLENGTH(esc), + result->data ) * sizeof(UChar); + result->typmod=-1; + + PG_FREE_IF_COPY(pat,0); + PG_FREE_IF_COPY(esc,1); + + PG_RETURN_MCHAR(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); + + 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 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); + 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), + 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), + 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), + 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), + 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; + + 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); + + PG_FREE_IF_COPY(pat,0); + if ( esc ) + PG_FREE_IF_COPY(esc,1); + + PG_RETURN_MVARCHAR(result); + } + + #define RE_CACHE_SIZE 32 diff -c -r -N contrib.orig/mchar/mchar_op.c contrib/mchar/mchar_op.c *** contrib.orig/mchar/mchar_op.c Thu Jan 1 03:00:00 1970 --- contrib/mchar/mchar_op.c Fri Jun 22 13:21:50 2007 *************** *** 0 **** --- 1,495 ---- + #include "mchar.h" + + + static inline int + mchar_icase_compare( MChar *a, MChar *b ) { + int res; + UErrorCode err = 0; + + res = u_strCaseCompare( + a->data, UCHARLENGTH(a), + b->data, UCHARLENGTH(b), + 0, &err + ); + + if ( U_FAILURE(err) ) + elog(ERROR,"ICU u_strCaseCompare returns %d (%s)", err, u_errorName(err)); + + return res; + } + + static inline int + mchar_case_compare( MChar *a, MChar *b ) { + int res; + + res = u_strCompare( + a->data, UCHARLENGTH(a), + b->data, UCHARLENGTH(b), + TRUE + ); + + return res; + } + + #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; + } + + + VARATT_SIZEP(result) = sizeof(UChar) * curlen + MCHARHDRSZ; + result->typmod = -1; + + PG_FREE_IF_COPY(a,0); + PG_FREE_IF_COPY(b,1); + + PG_RETURN_MCHAR(result); + } + + int + lengthWithoutSpace(MVarChar *m) { + int l = UVARCHARLENGTH(m); + + while( l>0 && u_isspace( m->data[ l-1 ] ) ) + l--; + + return l; + } + + static inline int + mvarchar_icase_compare( MVarChar *a, MVarChar *b ) { + int res; + UErrorCode err = 0; + + res = u_strCaseCompare( + a->data, lengthWithoutSpace(a), + b->data, lengthWithoutSpace(b), + 0, &err + ); + + if ( U_FAILURE(err) ) + elog(ERROR,"ICU u_strCaseCompare returns %d (%s)", err, u_errorName(err)); + + return res; + } + + static inline int + mvarchar_case_compare( MVarChar *a, MVarChar *b ) { + int res; + + res = u_strCompare( + a->data, lengthWithoutSpace(a), + b->data, lengthWithoutSpace(b), + TRUE + ); + + return res; + } + + #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 ); + } + + VARATT_SIZEP(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 ); + } + + VARATT_SIZEP(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; + } + + VARATT_SIZEP(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 ) { + int res; + UErrorCode err = 0; + + res = u_strCaseCompare( + a->data, UCHARLENGTH(a), + b->data, lengthWithoutSpace(b), + 0, &err + ); + + if ( U_FAILURE(err) ) + elog(ERROR,"ICU u_strCaseCompare returns %d (%s)", err, u_errorName(err)); + + return res; + } + + static inline int + mc_mv_case_compare( MChar *a, MVarChar *b ) { + int res; + + res = u_strCompare( + a->data, UCHARLENGTH(a), + b->data, lengthWithoutSpace(b), + TRUE + ); + + return res; + } + + #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 ) { + int res; + UErrorCode err = 0; + + res = u_strCaseCompare( + a->data, lengthWithoutSpace(a), + b->data, UCHARLENGTH(b), + 0, &err + ); + + if ( U_FAILURE(err) ) + elog(ERROR,"ICU u_strCaseCompare returns %d (%s)", err, u_errorName(err)); + + return res; + } + + static inline int + mv_mc_case_compare( MVarChar *a, MChar *b ) { + int res; + + res = u_strCompare( + a->data, lengthWithoutSpace(a), + b->data, UCHARLENGTH(b), + TRUE + ); + + return res; + } + + #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 -c -r -N contrib.orig/mchar/mchar_proc.c contrib/mchar/mchar_proc.c *** contrib.orig/mchar/mchar_proc.c Thu Jan 1 03:00:00 1970 --- contrib/mchar/mchar_proc.c Fri Jun 22 13:21:50 2007 *************** *** 0 **** --- 1,335 ---- + #include "mchar.h" + #include "mb/pg_wchar.h" + #include "access/hash.h" + + PG_FUNCTION_INFO_V1(mchar_length); + Datum mchar_length(PG_FUNCTION_ARGS); + + Datum + mchar_length(PG_FUNCTION_ARGS) { + MChar *m = PG_GETARG_MCHAR(0); + int4 l = UCHARLENGTH(m); + + while( l>0 && u_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); + int4 l = UVARCHARLENGTH(m); + + while( l>0 && u_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; + VARATT_SIZEP(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; + VARATT_SIZEP(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); + + VARATT_SIZEP(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); + + VARATT_SIZEP(dst) = MVARCHARHDRSZ + length *sizeof(UChar); + + PG_FREE_IF_COPY(src, 0); + PG_RETURN_MVARCHAR(dst); + } + + static Datum + hash_uchar( UChar *s, int len ) { + int32 length; + UErrorCode err = 0; + UChar *d; + Datum res; + + if ( len == 0 ) + return hash_any( NULL, 0 ); + + err = 0; + d = (UChar*) palloc( sizeof(UChar) * len * 2 ); + length = u_strFoldCase(d, len*2, s, len, U_FOLD_CASE_DEFAULT, &err); + + if ( U_FAILURE(err) ) + elog(ERROR,"ICU u_strFoldCase fails and returns %d (%s)", err, u_errorName(err)); + + res = hash_any( (unsigned char*) d, length * sizeof(UChar) ); + + pfree(d); + return res; + } + + PG_FUNCTION_INFO_V1(mvarchar_hash); + Datum + mvarchar_hash(PG_FUNCTION_ARGS) { + MVarChar *src = PG_GETARG_MVARCHAR(0); + Datum res; + + res = hash_uchar( src->data, lengthWithoutSpace(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, UCHARLENGTH(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( src->len * 2 ); + + dst->len = MCHARHDRSZ; + dst->typmod = src->typmod; + if ( UCHARLENGTH(src) != 0 ) { + int length; + UErrorCode err=0; + + length = u_strToUpper( dst->data, src->len * 2 - MCHARHDRSZ, + src->data, UCHARLENGTH(src), + NULL, &err ); + + Assert( length <= src->len * 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; + } + + 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( src->len * 2 ); + + dst->len = MCHARHDRSZ; + dst->typmod = src->typmod; + if ( UCHARLENGTH(src) != 0 ) { + int length; + UErrorCode err=0; + + length = u_strToLower( dst->data, src->len * 2 - MCHARHDRSZ, + src->data, UCHARLENGTH(src), + NULL, &err ); + + Assert( length <= src->len * 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; + } + + 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( src->len * 2 ); + + dst->len = MVARCHARHDRSZ; + + if ( UVARCHARLENGTH(src) != 0 ) { + int length; + UErrorCode err=0; + + length = u_strToUpper( dst->data, src->len * 2 - MVARCHARHDRSZ, + src->data, UVARCHARLENGTH(src), + NULL, &err ); + + Assert( length <= src->len * 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; + } + + 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( src->len * 2 ); + + dst->len = MVARCHARHDRSZ; + + if ( UVARCHARLENGTH(src) != 0 ) { + int length; + UErrorCode err=0; + + length = u_strToLower( dst->data, src->len * 2 - MVARCHARHDRSZ, + src->data, UVARCHARLENGTH(src), + NULL, &err ); + + Assert( length <= src->len * 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; + } + + PG_FREE_IF_COPY(src, 0); + PG_RETURN_MVARCHAR( dst ); + } + + diff -c -r -N contrib.orig/mchar/mchar_recode.c contrib/mchar/mchar_recode.c *** contrib.orig/mchar/mchar_recode.c Thu Jan 1 03:00:00 1970 --- contrib/mchar/mchar_recode.c Fri Jun 22 13:21:50 2007 *************** *** 0 **** --- 1,76 ---- + #include "mchar.h" + + #include "unicode/ucnv.h" + + static UConverter *cnvDB = NULL; + + static void + openConverter() { + 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)); + } + } + + int + Char2UChar(const char * src, int srclen, UChar *dst) { + int dstlen=0; + UErrorCode err = 0; + + openConverter(); + 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; + + openConverter(); + 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; + } + diff -c -r -N contrib.orig/mchar/sql/compat.sql contrib/mchar/sql/compat.sql *** contrib.orig/mchar/sql/compat.sql Thu Jan 1 03:00:00 1970 --- contrib/mchar/sql/compat.sql Fri Jun 22 13:21:50 2007 *************** *** 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 -c -r -N contrib.orig/mchar/sql/init.sql contrib/mchar/sql/init.sql *** contrib.orig/mchar/sql/init.sql Thu Jan 1 03:00:00 1970 --- contrib/mchar/sql/init.sql Fri Jun 22 13:21:50 2007 *************** *** 0 **** --- 1,14 ---- + + -- + -- first, define the datatype. Turn off echoing so that expected file + -- does not depend on contents of mchar.sql. + -- + + \set ECHO none + \i mchar.sql + --- load for table based checks + SET search_path = public; + \i data/chvch.sql + \i data/ch.sql + \set ECHO all + diff -c -r -N contrib.orig/mchar/sql/like.sql contrib/mchar/sql/like.sql *** contrib.orig/mchar/sql/like.sql Thu Jan 1 03:00:00 1970 --- contrib/mchar/sql/like.sql Fri Jun 22 15:48:10 2007 *************** *** 0 **** --- 1,207 ---- + -- 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; + + diff -c -r -N contrib.orig/mchar/sql/mchar.sql contrib/mchar/sql/mchar.sql *** contrib.orig/mchar/sql/mchar.sql Thu Jan 1 03:00:00 1970 --- contrib/mchar/sql/mchar.sql Fri Jun 22 13:21:50 2007 *************** *** 0 **** --- 1,81 ---- + -- I/O tests + + select '1'::mchar; + select '2 '::mchar; + select '10 '::mchar; + + select '1'::mchar(2); + select '2 '::mchar(2); + select '3 '::mchar(2); + select '10 '::mchar(2); + + select ' '::mchar(10); + select ' '::mchar; + + -- operations & functions + + select length('1'::mchar); + select length('2 '::mchar); + select length('10 '::mchar); + + select length('1'::mchar(2)); + select length('2 '::mchar(2)); + select length('3 '::mchar(2)); + select length('10 '::mchar(2)); + + select length(' '::mchar(10)); + select length(' '::mchar); + + select 'asd'::mchar(10) || '>'::mchar(10); + select length('asd'::mchar(10) || '>'::mchar(10)); + select 'asd'::mchar(2) || '>'::mchar(10); + select length('asd'::mchar(2) || '>'::mchar(10)); + + -- Comparisons + + select 'asdf'::mchar = 'aSdf'::mchar; + select 'asdf'::mchar = 'aSdf '::mchar; + select 'asdf'::mchar = 'aSdf 1'::mchar(4); + select 'asdf'::mchar = 'aSdf 1'::mchar(5); + select 'asdf'::mchar = 'aSdf 1'::mchar(6); + select 'asdf'::mchar(3) = 'aSdf 1'::mchar(5); + select 'asdf'::mchar(3) = 'aSdf 1'::mchar(3); + + select 'asdf'::mchar < 'aSdf'::mchar; + select 'asdf'::mchar < 'aSdf '::mchar; + select 'asdf'::mchar < 'aSdf 1'::mchar(4); + select 'asdf'::mchar < 'aSdf 1'::mchar(5); + select 'asdf'::mchar < 'aSdf 1'::mchar(6); + + select 'asdf'::mchar <= 'aSdf'::mchar; + select 'asdf'::mchar <= 'aSdf '::mchar; + select 'asdf'::mchar <= 'aSdf 1'::mchar(4); + select 'asdf'::mchar <= 'aSdf 1'::mchar(5); + select 'asdf'::mchar <= 'aSdf 1'::mchar(6); + + select 'asdf'::mchar >= 'aSdf'::mchar; + select 'asdf'::mchar >= 'aSdf '::mchar; + select 'asdf'::mchar >= 'aSdf 1'::mchar(4); + select 'asdf'::mchar >= 'aSdf 1'::mchar(5); + select 'asdf'::mchar >= 'aSdf 1'::mchar(6); + + select 'asdf'::mchar > 'aSdf'::mchar; + select 'asdf'::mchar > 'aSdf '::mchar; + select 'asdf'::mchar > 'aSdf 1'::mchar(4); + select 'asdf'::mchar > 'aSdf 1'::mchar(5); + select 'asdf'::mchar > 'aSdf 1'::mchar(6); + + select max(ch) from chvch; + select min(ch) from chvch; + + select substr('1234567890'::mchar, 3) = '34567890' as "34567890"; + select substr('1234567890'::mchar, 4, 3) = '456' as "456"; + + select lower('asdfASDF'::mchar); + select upper('asdfASDF'::mchar); + + select 'asd'::mchar == 'aSd'::mchar; + select 'asd'::mchar == 'aCd'::mchar; + select 'asd'::mchar == NULL; + select NULL == 'aCd'::mchar; + select NULL::mchar == NULL; diff -c -r -N contrib.orig/mchar/sql/mm.sql contrib/mchar/sql/mm.sql *** contrib.orig/mchar/sql/mm.sql Thu Jan 1 03:00:00 1970 --- contrib/mchar/sql/mm.sql Fri Jun 22 13:21:50 2007 *************** *** 0 **** --- 1,176 ---- + 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 diff -c -r -N contrib.orig/mchar/sql/mvarchar.sql contrib/mchar/sql/mvarchar.sql *** contrib.orig/mchar/sql/mvarchar.sql Thu Jan 1 03:00:00 1970 --- contrib/mchar/sql/mvarchar.sql Fri Jun 22 13:21:50 2007 *************** *** 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 -c -r -N contrib.orig/mchar/uninstall_mchar.sql contrib/mchar/uninstall_mchar.sql *** contrib.orig/mchar/uninstall_mchar.sql Thu Jan 1 03:00:00 1970 --- contrib/mchar/uninstall_mchar.sql Fri Jun 22 13:21:50 2007 *************** *** 0 **** --- 1,9 ---- + SET search_path = public; + BEGIN; + + DROP FUNCTION mchar_pattern_fixed_prefix(bool, internal, internal, internal, internal); + DROP FUNCTION mchar_greaterstring(bool, internal); + DROP TYPE MCHAR CASCADE; + DROP TYPE MVARCHAR CASCADE; + + COMMIT; diff -c -r -N doc.orig/src/sgml/ref/select.sgml doc/src/sgml/ref/select.sgml *** doc.orig/src/sgml/ref/select.sgml Fri Jun 22 13:10:46 2007 --- doc/src/sgml/ref/select.sgml Fri Jun 22 13:21:50 2007 *************** *** 27,33 **** [ GROUP BY expression [, ...] ] [ HAVING condition [, ...] ] [ { UNION | INTERSECT | EXCEPT } [ ALL ] select ] ! [ ORDER BY expression [ ASC | DESC | USING operator ] [, ...] ] [ LIMIT { count | ALL } ] [ OFFSET start ] [ FOR { UPDATE | SHARE } [ OF table_name [, ...] ] [ NOWAIT ] [...] ] --- 27,33 ---- [ GROUP BY expression [, ...] ] [ HAVING condition [, ...] ] [ { UNION | INTERSECT | EXCEPT } [ ALL ] select ] ! [ ORDER BY expression [ ASC | DESC | USING operator ] [ NULLS ( FIRST | LAST ) ] [, ...] ] [ LIMIT { count | ALL } ] [ OFFSET start ] [ FOR { UPDATE | SHARE } [ OF table_name [, ...] ] [ NOWAIT ] [...] ] *************** *** 642,648 **** The optional ORDER BY clause has this general form: ! ORDER BY expression [ ASC | DESC | USING operator ] [, ...] expression can be the name or ordinal number of an output column --- 642,648 ---- The optional ORDER BY clause has this general form: ! ORDER BY expression [ ASC | DESC | USING operator ] [ NULLS ( FIRST | LAST ) ] [, ...] expression can be the name or ordinal number of an output column *************** *** 705,713 **** ! The null value sorts higher than any other value. In other words, ! with ascending sort order, null values sort at the end, and with ! descending sort order, null values sort at the beginning. --- 705,714 ---- ! By default, the null value sorts higher than any other value. ! In other words, with ascending sort order, null values sort at the end, ! and with descending sort order, null values sort at the beginning. Thats ! can be changed by using NULLS ( FIRST | LAST ) options.