diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index ba2b8d3b898..89bda3d82df 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -11,7 +11,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/parser/gram.y,v 2.181 2000/07/30 22:13:50 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/parser/gram.y,v 2.182 2000/08/06 18:05:21 thomas Exp $ * * HISTORY * AUTHOR DATE MAJOR EVENT @@ -123,7 +123,7 @@ static void doNegateFloat(Value *v); AlterSchemaStmt, AlterTableStmt, ClosePortalStmt, CopyStmt, CreateStmt, CreateAsStmt, CreateSchemaStmt, CreateSeqStmt, DefineStmt, DropStmt, TruncateStmt, CommentStmt, - ExtendStmt, FetchStmt, GrantStmt, CreateTrigStmt, DropTrigStmt, + ExtendStmt, FetchStmt, GrantStmt, CreateTrigStmt, DropSchemaStmt, DropTrigStmt, CreatePLangStmt, DropPLangStmt, IndexStmt, ListenStmt, UnlistenStmt, LockStmt, OptimizableStmt, ProcedureStmt, ReindexStmt, RemoveAggrStmt, RemoveOperStmt, @@ -191,7 +191,7 @@ static void doNegateFloat(Value *v); %type for_update_clause, update_list %type opt_all %type opt_table -%type opt_trans +%type opt_chain, opt_trans %type from_expr, join_clause, join_expr %type join_clause_with_union, join_expr_with_union @@ -252,7 +252,7 @@ static void doNegateFloat(Value *v); %type Typename, SimpleTypename, ConstTypename Generic, Numeric, Geometric, Character, ConstDatetime, ConstInterval, Bit -%type typename, generic, numeric, geometric, character, datetime, bit +%type generic, character, datetime, bit %type extract_arg %type opt_charset, opt_collate %type opt_float @@ -302,7 +302,7 @@ static void doNegateFloat(Value *v); CURRENT_TIME, CURRENT_TIMESTAMP, CURRENT_USER, CURSOR, DAY_P, DEC, DECIMAL, DECLARE, DEFAULT, DELETE, DESC, DISTINCT, DOUBLE, DROP, - ELSE, END_TRANS, EXCEPT, EXECUTE, EXISTS, EXTRACT, + ELSE, END_TRANS, ESCAPE, EXCEPT, EXECUTE, EXISTS, EXTRACT, FALSE_P, FETCH, FLOAT, FOR, FOREIGN, FROM, FULL, GLOBAL, GRANT, GROUP, HAVING, HOUR_P, IN, INNER_P, INSENSITIVE, INSERT, INTERSECT, INTERVAL, INTO, IS, @@ -320,7 +320,7 @@ static void doNegateFloat(Value *v); WHEN, WHERE, WITH, WORK, YEAR_P, ZONE /* Keywords (in SQL3 reserved words) */ -%token CHARACTERISTICS, +%token CHAIN, CHARACTERISTICS, DEFERRABLE, DEFERRED, IMMEDIATE, INITIALLY, INOUT, OFF, OUT, @@ -345,7 +345,7 @@ static void doNegateFloat(Value *v); DATABASE, DELIMITERS, DO, EACH, ENCODING, EXCLUSIVE, EXPLAIN, EXTEND, FORCE, FORWARD, FUNCTION, HANDLER, - INCREMENT, INDEX, INHERITS, INSTEAD, ISNULL, + ILIKE, INCREMENT, INDEX, INHERITS, INSTEAD, ISNULL, LANCOMPILER, LIMIT, LISTEN, LOAD, LOCATION, LOCK_P, MAXVALUE, MINVALUE, MODE, MOVE, NEW, NOCREATEDB, NOCREATEUSER, NONE, NOTHING, NOTIFY, NOTNULL, @@ -368,7 +368,7 @@ static void doNegateFloat(Value *v); %right NOT %right '=' %nonassoc '<' '>' -%nonassoc LIKE +%nonassoc LIKE ILIKE %nonassoc OVERLAPS %nonassoc BETWEEN %nonassoc IN @@ -382,13 +382,14 @@ static void doNegateFloat(Value *v); %left '^' %left '|' /* this is the relation union op, not logical or */ /* Unary Operators */ -%right ':' -%left ';' /* end of statement or natural log */ +%right ':' /* delimiter for array ranges */ +%left ';' /* end of statement */ %right UMINUS %left '.' %left '[' ']' %left TYPECAST %left UNION INTERSECT EXCEPT +%left ESCAPE %% /* @@ -432,6 +433,7 @@ stmt : AlterSchemaStmt | ClusterStmt | DefineStmt | DropStmt + | DropSchemaStmt | TruncateStmt | CommentStmt | DropGroupStmt @@ -678,7 +680,16 @@ DropGroupStmt: DROP GROUP UserId CreateSchemaStmt: CREATE SCHEMA UserId { - elog(ERROR, "CREATE SCHEMA not yet supported"); + /* for now, just make this the same as CREATE DATABASE */ + CreatedbStmt *n = makeNode(CreatedbStmt); + n->dbname = $3; + n->dbpath = NULL; +#ifdef MULTIBYTE + n->encoding = GetTemplateEncoding(); +#else + n->encoding = 0; +#endif + $$ = (Node *)n; } ; @@ -688,6 +699,13 @@ AlterSchemaStmt: ALTER SCHEMA UserId } ; +DropSchemaStmt: DROP SCHEMA UserId + { + DropdbStmt *n = makeNode(DropdbStmt); + n->dbname = $3; + $$ = (Node *)n; + } + /***************************************************************************** * @@ -2648,7 +2666,7 @@ opt_force: FORCE { $$ = TRUE; } *****************************************************************************/ RenameStmt: ALTER TABLE relation_name opt_inh_star - /* "*" deprecated */ + /* "*" deprecated */ RENAME opt_column opt_name TO name { RenameStmt *n = makeNode(RenameStmt); @@ -2823,6 +2841,12 @@ TransactionStmt: ABORT_TRANS opt_trans n->command = COMMIT; $$ = (Node *)n; } + | COMMIT opt_trans opt_chain + { + TransactionStmt *n = makeNode(TransactionStmt); + n->command = COMMIT; + $$ = (Node *)n; + } | END_TRANS opt_trans { TransactionStmt *n = makeNode(TransactionStmt); @@ -2835,6 +2859,12 @@ TransactionStmt: ABORT_TRANS opt_trans n->command = ROLLBACK; $$ = (Node *)n; } + | ROLLBACK opt_trans opt_chain + { + TransactionStmt *n = makeNode(TransactionStmt); + n->command = ROLLBACK; + $$ = (Node *)n; + } ; opt_trans: WORK { $$ = TRUE; } @@ -2842,6 +2872,19 @@ opt_trans: WORK { $$ = TRUE; } | /*EMPTY*/ { $$ = TRUE; } ; +opt_chain: AND NO CHAIN + { $$ = FALSE; } + | AND CHAIN + { + /* SQL99 asks that conforming dbs reject AND CHAIN + * if they don't support it. So we can't just ignore it. + * - thomas 2000-08-06 + */ + elog(ERROR, "COMMIT/CHAIN not yet supported"); + $$ = TRUE; + } + ; + /***************************************************************************** * @@ -2891,11 +2934,11 @@ LoadStmt: LOAD file_name *****************************************************************************/ CreatedbStmt: CREATE DATABASE database_name WITH createdb_opt_location createdb_opt_encoding - { - CreatedbStmt *n; + { + CreatedbStmt *n; - if ($5 == NULL && $6 == -1) - elog(ERROR, "CREATE DATABASE WITH requires at least one option."); + if ($5 == NULL && $6 == -1) + elog(ERROR, "CREATE DATABASE WITH requires at least one option."); n = makeNode(CreatedbStmt); n->dbname = $3; @@ -2918,50 +2961,49 @@ CreatedbStmt: CREATE DATABASE database_name WITH createdb_opt_location createdb ; createdb_opt_location: LOCATION '=' Sconst { $$ = $3; } - | LOCATION '=' DEFAULT { $$ = NULL; } + | LOCATION '=' DEFAULT { $$ = NULL; } | /*EMPTY*/ { $$ = NULL; } ; -createdb_opt_encoding: - ENCODING '=' Sconst - { +createdb_opt_encoding: ENCODING '=' Sconst + { #ifdef MULTIBYTE - int i; - i = pg_char_to_encoding($3); - if (i == -1) - elog(ERROR, "%s is not a valid encoding name", $3); - $$ = i; + int i; + i = pg_char_to_encoding($3); + if (i == -1) + elog(ERROR, "%s is not a valid encoding name", $3); + $$ = i; #else - elog(ERROR, "Multi-byte support is not enabled"); + elog(ERROR, "Multi-byte support is not enabled"); #endif - } - | ENCODING '=' Iconst - { + } + | ENCODING '=' Iconst + { #ifdef MULTIBYTE - if (!pg_get_encent_by_encoding($3)) - elog(ERROR, "%d is not a valid encoding code", $3); - $$ = $3; + if (!pg_get_encent_by_encoding($3)) + elog(ERROR, "%d is not a valid encoding code", $3); + $$ = $3; #else - elog(ERROR, "Multi-byte support is not enabled"); + elog(ERROR, "Multi-byte support is not enabled"); #endif - } - | ENCODING '=' DEFAULT - { + } + | ENCODING '=' DEFAULT + { #ifdef MULTIBYTE - $$ = GetTemplateEncoding(); + $$ = GetTemplateEncoding(); #else - $$ = 0; + $$ = 0; #endif - } - | /*EMPTY*/ - { + } + | /*EMPTY*/ + { #ifdef MULTIBYTE - $$ = GetTemplateEncoding(); + $$ = GetTemplateEncoding(); #else - $$= 0; + $$= 0; #endif - } - ; + } + ; /***************************************************************************** @@ -3255,7 +3297,7 @@ UpdateStmt: UPDATE opt_only relation_name where_clause { UpdateStmt *n = makeNode(UpdateStmt); - n->inh = $2; + n->inh = $2; n->relname = $3; n->targetList = $5; n->fromClause = $6; @@ -3353,7 +3395,7 @@ SelectStmt: select_clause sort_clause for_update_clause opt_select_limit List *select_list = NIL; SelectStmt *first_select; bool intersect_present = FALSE, - unionall_present = FALSE; + unionall_present = FALSE; /* Take the operator tree as an argument and create a * list of all SelectStmt Nodes found in the tree. @@ -3429,21 +3471,21 @@ select_clause: '(' select_clause ')' | select_clause EXCEPT select_clause { $$ = (Node *)makeA_Expr(AND,NULL,$1, - makeA_Expr(NOT,NULL,NULL,$3)); + makeA_Expr(NOT,NULL,NULL,$3)); } | select_clause UNION opt_all select_clause { if (IsA($4, SelectStmt)) - { - SelectStmt *n = (SelectStmt *)$4; - n->unionall = $3; - /* NOTE: if UNION ALL appears with a parenthesized set - * operation to its right, the ALL is silently discarded. - * Should we generate an error instead? I think it may - * be OK since ALL with UNION to its right is ignored - * anyway... - */ - } + { + SelectStmt *n = (SelectStmt *)$4; + n->unionall = $3; + /* NOTE: if UNION ALL appears with a parenthesized set + * operation to its right, the ALL is silently discarded. + * Should we generate an error instead? I think it may + * be OK since ALL with UNION to its right is ignored + * anyway... + */ + } $$ = (Node *)makeA_Expr(OR,NULL,$1,$4); } | select_clause INTERSECT select_clause @@ -3899,21 +3941,21 @@ relation_expr: relation_name $$->relname = $1; $$->inh = SQL_inheritance; } - | relation_name '*' %prec '=' + | relation_name '*' %prec '=' { /* inheritance query */ $$ = makeNode(RelExpr); $$->relname = $1; $$->inh = TRUE; } - | ONLY relation_name %prec '=' - { + | ONLY relation_name %prec '=' + { /* no inheritance */ $$ = makeNode(RelExpr); $$->relname = $2; $$->inh = FALSE; } - ; + ; opt_array_bounds: '[' ']' opt_array_bounds { $$ = lcons(makeInteger(-1), $3); } @@ -3975,14 +4017,6 @@ ConstTypename: Generic | ConstDatetime ; -typename: generic { $$ = $1; } - | numeric { $$ = $1; } - | geometric { $$ = $1; } - | bit { $$ = $1; } - | character { $$ = $1; } - | datetime { $$ = $1; } - ; - Generic: generic { $$ = makeNode(TypeName); @@ -4032,13 +4066,6 @@ Numeric: FLOAT opt_float } ; -numeric: FLOAT { $$ = xlateSqlType("float"); } - | DOUBLE PRECISION { $$ = xlateSqlType("float8"); } - | DECIMAL { $$ = xlateSqlType("decimal"); } - | DEC { $$ = xlateSqlType("decimal"); } - | NUMERIC { $$ = xlateSqlType("numeric"); } - ; - Geometric: PATH_P { $$ = makeNode(TypeName); @@ -4047,9 +4074,6 @@ Geometric: PATH_P } ; -geometric: PATH_P { $$ = xlateSqlType("path"); } - ; - opt_float: '(' Iconst ')' { if ($2 < 1) @@ -4435,16 +4459,6 @@ a_expr: c_expr { $$ = makeA_Expr(OP, "^", NULL, $2); } | '|' a_expr { $$ = makeA_Expr(OP, "|", NULL, $2); } - | ':' a_expr - { $$ = makeA_Expr(OP, ":", NULL, $2); - elog(NOTICE, "The ':' operator is deprecated. Use exp(x) instead." - "\n\tThis operator will be removed in a future release."); - } - | ';' a_expr - { $$ = makeA_Expr(OP, ";", NULL, $2); - elog(NOTICE, "The ';' operator is deprecated. Use ln(x) instead." - "\n\tThis operator will be removed in a future release."); - } | a_expr '%' { $$ = makeA_Expr(OP, "%", $1, NULL); } | a_expr '^' @@ -4499,9 +4513,77 @@ a_expr: c_expr { $$ = makeA_Expr(NOT, NULL, NULL, $2); } | a_expr LIKE a_expr - { $$ = makeA_Expr(OP, "~~", $1, $3); } + { + FuncCall *n = makeNode(FuncCall); + n->funcname = "like"; + n->args = makeList($1, $3, -1); + n->agg_star = FALSE; + n->agg_distinct = FALSE; + $$ = (Node *)n; + } + | a_expr LIKE a_expr ESCAPE a_expr + { + FuncCall *n = makeNode(FuncCall); + n->funcname = "like"; + n->args = makeList($1, $3, $5, -1); + n->agg_star = FALSE; + n->agg_distinct = FALSE; + $$ = (Node *)n; + } | a_expr NOT LIKE a_expr - { $$ = makeA_Expr(OP, "!~~", $1, $4); } + { + FuncCall *n = makeNode(FuncCall); + n->funcname = "notlike"; + n->args = makeList($1, $4, -1); + n->agg_star = FALSE; + n->agg_distinct = FALSE; + $$ = (Node *)n; + } + | a_expr NOT LIKE a_expr ESCAPE a_expr + { + FuncCall *n = makeNode(FuncCall); + n->funcname = "notlike"; + n->args = makeList($1, $4, $6, -1); + n->agg_star = FALSE; + n->agg_distinct = FALSE; + $$ = (Node *)n; + } + | a_expr ILIKE a_expr + { + FuncCall *n = makeNode(FuncCall); + n->funcname = "ilike"; + n->args = makeList($1, $3, -1); + n->agg_star = FALSE; + n->agg_distinct = FALSE; + $$ = (Node *)n; + } + | a_expr ILIKE a_expr ESCAPE a_expr + { + FuncCall *n = makeNode(FuncCall); + n->funcname = "ilike"; + n->args = makeList($1, $3, $5, -1); + n->agg_star = FALSE; + n->agg_distinct = FALSE; + $$ = (Node *)n; + } + | a_expr NOT ILIKE a_expr + { + FuncCall *n = makeNode(FuncCall); + n->funcname = "inotlike"; + n->args = makeList($1, $4, -1); + n->agg_star = FALSE; + n->agg_distinct = FALSE; + $$ = (Node *)n; + } + | a_expr NOT ILIKE a_expr ESCAPE a_expr + { + FuncCall *n = makeNode(FuncCall); + n->funcname = "inotlike"; + n->args = makeList($1, $4, $6, -1); + n->agg_star = FALSE; + n->agg_distinct = FALSE; + $$ = (Node *)n; + } | a_expr ISNULL { $$ = makeA_Expr(ISNULL, NULL, $1, NULL); } @@ -4659,16 +4741,6 @@ b_expr: c_expr { $$ = makeA_Expr(OP, "^", NULL, $2); } | '|' b_expr { $$ = makeA_Expr(OP, "|", NULL, $2); } - | ':' b_expr - { $$ = makeA_Expr(OP, ":", NULL, $2); - elog(NOTICE, "The ':' operator is deprecated. Use exp(x) instead." - "\n\tThis operator will be removed in a future release."); - } - | ';' b_expr - { $$ = makeA_Expr(OP, ";", NULL, $2); - elog(NOTICE, "The ';' operator is deprecated. Use ln(x) instead." - "\n\tThis operator will be removed in a future release."); - } | b_expr '%' { $$ = makeA_Expr(OP, "%", $1, NULL); } | b_expr '^' @@ -5496,6 +5568,7 @@ TokenId: ABSOLUTE { $$ = "absolute"; } | BY { $$ = "by"; } | CACHE { $$ = "cache"; } | CASCADE { $$ = "cascade"; } + | CHAIN { $$ = "chain"; } | CLOSE { $$ = "close"; } | COMMENT { $$ = "comment"; } | COMMIT { $$ = "commit"; } @@ -5515,6 +5588,7 @@ TokenId: ABSOLUTE { $$ = "absolute"; } | DROP { $$ = "drop"; } | EACH { $$ = "each"; } | ENCODING { $$ = "encoding"; } + | ESCAPE { $$ = "escape"; } | EXCLUSIVE { $$ = "exclusive"; } | EXECUTE { $$ = "execute"; } | FETCH { $$ = "fetch"; } @@ -5661,6 +5735,7 @@ ColLabel: ColId { $$ = $1; } | GLOBAL { $$ = "global"; } | GROUP { $$ = "group"; } | HAVING { $$ = "having"; } + | ILIKE { $$ = "ilike"; } | INITIALLY { $$ = "initially"; } | IN { $$ = "in"; } | INNER_P { $$ = "inner"; } diff --git a/src/backend/parser/keywords.c b/src/backend/parser/keywords.c index e173805a79c..848f1dc8d6b 100644 --- a/src/backend/parser/keywords.c +++ b/src/backend/parser/keywords.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/parser/keywords.c,v 1.79 2000/07/14 15:43:32 thomas Exp $ + * $Header: /cvsroot/pgsql/src/backend/parser/keywords.c,v 1.80 2000/08/06 18:05:22 thomas Exp $ * *------------------------------------------------------------------------- */ @@ -55,6 +55,7 @@ static ScanKeyword ScanKeywords[] = { {"cascade", CASCADE}, {"case", CASE}, {"cast", CAST}, + {"chain", CHAIN}, {"char", CHAR}, {"character", CHARACTER}, {"characteristics", CHARACTERISTICS}, @@ -101,6 +102,7 @@ static ScanKeyword ScanKeywords[] = { {"else", ELSE}, {"encoding", ENCODING}, {"end", END_TRANS}, + {"escape", ESCAPE}, {"except", EXCEPT}, {"exclusive", EXCLUSIVE}, {"execute", EXECUTE}, @@ -124,6 +126,7 @@ static ScanKeyword ScanKeywords[] = { {"handler", HANDLER}, {"having", HAVING}, {"hour", HOUR_P}, + {"ilike", ILIKE}, {"immediate", IMMEDIATE}, {"in", IN}, {"increment", INCREMENT}, diff --git a/src/backend/utils/adt/like.c b/src/backend/utils/adt/like.c index 5a7b8473392..058fb1d9656 100644 --- a/src/backend/utils/adt/like.c +++ b/src/backend/utils/adt/like.c @@ -11,7 +11,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/utils/adt/like.c,v 1.37 2000/07/07 21:12:50 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/utils/adt/like.c,v 1.38 2000/08/06 18:05:41 thomas Exp $ * *------------------------------------------------------------------------- */ @@ -20,70 +20,30 @@ #include "mb/pg_wchar.h" #include "utils/builtins.h" -static bool like(pg_wchar * text, pg_wchar * p); + +#define LIKE_TRUE 1 +#define LIKE_FALSE 0 +#define LIKE_ABORT (-1) + + +static int MatchText(pg_wchar * t, int tlen, pg_wchar * p, int plen, char *e); +static int MatchTextLower(pg_wchar * t, int tlen, pg_wchar * p, int plen, char *e); + /* * interface routines called by the function manager */ -/* - fixedlen_like: - - a generic fixed length like routine - s - the string to match against (not necessarily null-terminated) - p - the pattern (as text*) - charlen - the length of the string -*/ -static bool -fixedlen_like(char *s, text *p, int charlen) -{ - pg_wchar *sterm, - *pterm; - bool result; - int len; - - /* be sure sterm is null-terminated */ -#ifdef MULTIBYTE - sterm = (pg_wchar *) palloc((charlen + 1) * sizeof(pg_wchar)); - (void) pg_mb2wchar_with_len((unsigned char *) s, sterm, charlen); -#else - sterm = (char *) palloc(charlen + 1); - memcpy(sterm, s, charlen); - sterm[charlen] = '\0'; -#endif - - /* - * p is a text, not a string so we have to make a string - * from the vl_data field of the struct. - */ - - /* palloc the length of the text + the null character */ - len = VARSIZE(p) - VARHDRSZ; -#ifdef MULTIBYTE - pterm = (pg_wchar *) palloc((len + 1) * sizeof(pg_wchar)); - (void) pg_mb2wchar_with_len((unsigned char *) VARDATA(p), pterm, len); -#else - pterm = (char *) palloc(len + 1); - memcpy(pterm, VARDATA(p), len); - *(pterm + len) = '\0'; -#endif - - /* do the regexp matching */ - result = like(sterm, pterm); - - pfree(sterm); - pfree(pterm); - - return result; -} - Datum namelike(PG_FUNCTION_ARGS) { Name n = PG_GETARG_NAME(0); text *p = PG_GETARG_TEXT_P(1); - PG_RETURN_BOOL(fixedlen_like(NameStr(*n), p, strlen(NameStr(*n)))); + PG_RETURN_BOOL(MatchText(NameStr(*n), strlen(NameStr(*n)), + VARDATA(p), (VARSIZE(p)-VARHDRSZ), + NULL) + == LIKE_TRUE); } Datum @@ -92,7 +52,36 @@ namenlike(PG_FUNCTION_ARGS) Name n = PG_GETARG_NAME(0); text *p = PG_GETARG_TEXT_P(1); - PG_RETURN_BOOL(! fixedlen_like(NameStr(*n), p, strlen(NameStr(*n)))); + PG_RETURN_BOOL(MatchText(NameStr(*n), strlen(NameStr(*n)), + VARDATA(p), (VARSIZE(p)-VARHDRSZ), + NULL) + != LIKE_TRUE); +} + +Datum +namelike_escape(PG_FUNCTION_ARGS) +{ + Name n = PG_GETARG_NAME(0); + text *p = PG_GETARG_TEXT_P(1); + text *e = PG_GETARG_TEXT_P(2); + + PG_RETURN_BOOL(MatchText(NameStr(*n), strlen(NameStr(*n)), + VARDATA(p), (VARSIZE(p)-VARHDRSZ), + ((VARSIZE(e)-VARHDRSZ) > 0? VARDATA(e): NULL)) + == LIKE_TRUE); +} + +Datum +namenlike_escape(PG_FUNCTION_ARGS) +{ + Name n = PG_GETARG_NAME(0); + text *p = PG_GETARG_TEXT_P(1); + text *e = PG_GETARG_TEXT_P(2); + + PG_RETURN_BOOL(MatchText(NameStr(*n), strlen(NameStr(*n)), + VARDATA(p), (VARSIZE(p)-VARHDRSZ), + ((VARSIZE(e)-VARHDRSZ) > 0? VARDATA(e): NULL)) + != LIKE_TRUE); } Datum @@ -101,7 +90,10 @@ textlike(PG_FUNCTION_ARGS) text *s = PG_GETARG_TEXT_P(0); text *p = PG_GETARG_TEXT_P(1); - PG_RETURN_BOOL(fixedlen_like(VARDATA(s), p, VARSIZE(s) - VARHDRSZ)); + PG_RETURN_BOOL(MatchText(VARDATA(s), (VARSIZE(s)-VARHDRSZ), + VARDATA(p), (VARSIZE(p)-VARHDRSZ), + NULL) + == LIKE_TRUE); } Datum @@ -110,7 +102,140 @@ textnlike(PG_FUNCTION_ARGS) text *s = PG_GETARG_TEXT_P(0); text *p = PG_GETARG_TEXT_P(1); - PG_RETURN_BOOL(! fixedlen_like(VARDATA(s), p, VARSIZE(s) - VARHDRSZ)); + PG_RETURN_BOOL(MatchText(VARDATA(s), (VARSIZE(s)-VARHDRSZ), + VARDATA(p), (VARSIZE(p)-VARHDRSZ), + NULL) + != LIKE_TRUE); +} + +Datum +textlike_escape(PG_FUNCTION_ARGS) +{ + text *s = PG_GETARG_TEXT_P(0); + text *p = PG_GETARG_TEXT_P(1); + text *e = PG_GETARG_TEXT_P(2); + + PG_RETURN_BOOL(MatchText(VARDATA(s), (VARSIZE(s)-VARHDRSZ), + VARDATA(p), (VARSIZE(p)-VARHDRSZ), + ((VARSIZE(e)-VARHDRSZ) > 0? VARDATA(e): NULL)) + == LIKE_TRUE); +} + +Datum +textnlike_escape(PG_FUNCTION_ARGS) +{ + text *s = PG_GETARG_TEXT_P(0); + text *p = PG_GETARG_TEXT_P(1); + text *e = PG_GETARG_TEXT_P(2); + + PG_RETURN_BOOL(MatchText(VARDATA(s), (VARSIZE(s)-VARHDRSZ), + VARDATA(p), (VARSIZE(p)-VARHDRSZ), + ((VARSIZE(e)-VARHDRSZ) > 0? VARDATA(e): NULL)) + != LIKE_TRUE); +} + +/* + * Case-insensitive versions + */ + +Datum +inamelike(PG_FUNCTION_ARGS) +{ + Name n = PG_GETARG_NAME(0); + text *p = PG_GETARG_TEXT_P(1); + + PG_RETURN_BOOL(MatchTextLower(NameStr(*n), strlen(NameStr(*n)), + VARDATA(p), (VARSIZE(p)-VARHDRSZ), + NULL) + == LIKE_TRUE); +} + +Datum +inamenlike(PG_FUNCTION_ARGS) +{ + Name n = PG_GETARG_NAME(0); + text *p = PG_GETARG_TEXT_P(1); + + PG_RETURN_BOOL(MatchTextLower(NameStr(*n), strlen(NameStr(*n)), + VARDATA(p), (VARSIZE(p)-VARHDRSZ), + NULL) + != LIKE_TRUE); +} + +Datum +inamelike_escape(PG_FUNCTION_ARGS) +{ + Name n = PG_GETARG_NAME(0); + text *p = PG_GETARG_TEXT_P(1); + text *e = PG_GETARG_TEXT_P(2); + + PG_RETURN_BOOL(MatchTextLower(NameStr(*n), strlen(NameStr(*n)), + VARDATA(p), (VARSIZE(p)-VARHDRSZ), + ((VARSIZE(e)-VARHDRSZ) > 0? VARDATA(e): NULL)) + == LIKE_TRUE); +} + +Datum +inamenlike_escape(PG_FUNCTION_ARGS) +{ + Name n = PG_GETARG_NAME(0); + text *p = PG_GETARG_TEXT_P(1); + text *e = PG_GETARG_TEXT_P(2); + + PG_RETURN_BOOL(MatchTextLower(NameStr(*n), strlen(NameStr(*n)), + VARDATA(p), (VARSIZE(p)-VARHDRSZ), + ((VARSIZE(e)-VARHDRSZ) > 0? VARDATA(e): NULL)) + != LIKE_TRUE); +} + +Datum +itextlike(PG_FUNCTION_ARGS) +{ + text *s = PG_GETARG_TEXT_P(0); + text *p = PG_GETARG_TEXT_P(1); + + PG_RETURN_BOOL(MatchTextLower(VARDATA(s), (VARSIZE(s)-VARHDRSZ), + VARDATA(p), (VARSIZE(p)-VARHDRSZ), + NULL) + == LIKE_TRUE); +} + +Datum +itextnlike(PG_FUNCTION_ARGS) +{ + text *s = PG_GETARG_TEXT_P(0); + text *p = PG_GETARG_TEXT_P(1); + + PG_RETURN_BOOL(MatchTextLower(VARDATA(s), (VARSIZE(s)-VARHDRSZ), + VARDATA(p), (VARSIZE(p)-VARHDRSZ), + NULL) + != LIKE_TRUE); +} + +Datum +itextlike_escape(PG_FUNCTION_ARGS) +{ + text *s = PG_GETARG_TEXT_P(0); + text *p = PG_GETARG_TEXT_P(1); + text *e = PG_GETARG_TEXT_P(2); + + PG_RETURN_BOOL(MatchTextLower(VARDATA(s), (VARSIZE(s)-VARHDRSZ), + VARDATA(p), (VARSIZE(p)-VARHDRSZ), + ((VARSIZE(e)-VARHDRSZ) > 0? VARDATA(e): NULL)) + == LIKE_TRUE); +} + +Datum +itextnlike_escape(PG_FUNCTION_ARGS) +{ + text *s = PG_GETARG_TEXT_P(0); + text *p = PG_GETARG_TEXT_P(1); + text *e = PG_GETARG_TEXT_P(2); + + PG_RETURN_BOOL(MatchTextLower(VARDATA(s), (VARSIZE(s)-VARHDRSZ), + VARDATA(p), (VARSIZE(p)-VARHDRSZ), + ((VARSIZE(e)-VARHDRSZ) > 0? VARDATA(e): NULL)) + != LIKE_TRUE); } @@ -136,12 +261,16 @@ textnlike(PG_FUNCTION_ARGS) ** LIKE ESCAPE . We are a small operation ** so we force you to use '\'. - ay 7/95] ** +** OK, we now support the SQL9x LIKE ESCAPE syntax. +** We should kill the backslash escaping mechanism since it is non-standard +** and undocumented afaik. +** 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 +** */ -#define LIKE_TRUE 1 -#define LIKE_FALSE 0 -#define LIKE_ABORT (-1) - /*-------------------- * Match text and p, return LIKE_TRUE, LIKE_FALSE, or LIKE_ABORT. * @@ -153,69 +282,97 @@ textnlike(PG_FUNCTION_ARGS) * pattern either, so an upper-level % scan can stop scanning now. *-------------------- */ -static int -DoMatch(pg_wchar * text, pg_wchar * p) -{ - for (; *p && *text; text ++, p++) - { - switch (*p) - { - case '\\': - /* Literal match with following character. */ - p++; - /* FALLTHROUGH */ - default: - if (*text !=*p) - return LIKE_FALSE; - break; - case '_': - /* Match any single character. */ - break; - case '%': - /* %% is the same as % according to the SQL standard */ - /* Advance past all %'s */ - while (*p == '%') - p++; - /* Trailing percent matches everything. */ - if (*p == '\0') - return LIKE_TRUE; - /* - * Otherwise, scan for a text position at which we can - * match the rest of the pattern. - */ - for (; *text; text ++) - { +#define NextChar(p, plen) (p)++, (plen)-- + +static int +MatchText(pg_wchar * t, int tlen, pg_wchar * p, int plen, char *e) +{ + /* Fast path for match-everything pattern + * Include weird case of escape character as a percent sign or underscore, + * when presumably that wildcard character becomes a literal. + */ + if ((plen == 1) && (*p == '%') + && ! ((e != NULL) && (*e == '%'))) + return LIKE_TRUE; + + while ((tlen > 0) && (plen > 0)) + { + /* If an escape character was specified and we find it here in the pattern, + * then we'd better have an exact match for the next character. + */ + if ((e != NULL) && (*p == *e)) + { + NextChar(p, plen); + if ((plen <= 0) || (*t != *p)) + return LIKE_FALSE; + } + else + { + switch (*p) + { + case '\\': + /* Literal match with following character. */ + NextChar(p, plen); + /* FALLTHROUGH */ + default: + if (*t != *p) + return LIKE_FALSE; + break; + case '_': + /* Match any single character. */ + break; + case '%': + /* %% is the same as % according to the SQL standard */ + /* Advance past all %'s */ + while ((plen > 0) && (*p == '%')) + NextChar(p, plen); + /* Trailing percent matches everything. */ + if (plen <= 0) + return LIKE_TRUE; /* - * Optimization to prevent most recursion: don't - * recurse unless first pattern char might match this - * text char. + * Otherwise, scan for a text position at which we can + * match the rest of the pattern. */ - if (*text == *p || *p == '\\' || *p == '_') + while (tlen > 0) { - int matched = DoMatch(text, p); + /* + * Optimization to prevent most recursion: don't + * recurse unless first pattern char might match this + * text char. + */ + if ((*t == *p) || (*p == '\\') || (*p == '_') + || ((e != NULL) && (*p == *e))) + { + int matched = MatchText(t, tlen, p, plen, e); - if (matched != LIKE_FALSE) - return matched; /* TRUE or ABORT */ + 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; + /* + * End of text with no match, so no point in trying later + * places to start matching this pattern. + */ + return LIKE_ABORT; + } } + + NextChar(t, tlen); + NextChar(p, plen); } - if (*text !='\0') + if (tlen > 0) return LIKE_FALSE; /* end of pattern, but not of text */ /* End of input string. Do we have matching pattern remaining? */ - while (*p == '%') /* allow multiple %'s at end of pattern */ - p++; - if (*p == '\0') + while ((plen > 0) && (*p == '%')) /* allow multiple %'s at end of pattern */ + NextChar(p, plen); + if (plen <= 0) return LIKE_TRUE; /* @@ -223,16 +380,101 @@ DoMatch(pg_wchar * text, pg_wchar * p) * start matching this pattern. */ return LIKE_ABORT; -} +} /* MatchText() */ -/* -** User-level routine. Returns TRUE or FALSE. -*/ -static bool -like(pg_wchar * text, pg_wchar * p) +static int +MatchTextLower(pg_wchar * t, int tlen, pg_wchar * p, int plen, char *e) { - /* Fast path for match-everything pattern */ - if (p[0] == '%' && p[1] == '\0') - return true; - return DoMatch(text, p) == LIKE_TRUE; -} + /* Fast path for match-everything pattern + * Include weird case of escape character as a percent sign or underscore, + * when presumably that wildcard character becomes a literal. + */ + if ((plen == 1) && (*p == '%') + && ! ((e != NULL) && (*e == '%'))) + return LIKE_TRUE; + + while ((tlen > 0) && (plen > 0)) + { + /* If an escape character was specified and we find it here in the pattern, + * then we'd better have an exact match for the next character. + */ + if ((e != NULL) && (tolower(*p) == tolower(*e))) + { + NextChar(p, plen); + if ((plen <= 0) || (tolower(*t) != tolower(*p))) + return LIKE_FALSE; + } + else + { + switch (*p) + { + case '\\': + /* Literal match with following character. */ + NextChar(p, plen); + /* FALLTHROUGH */ + default: + if (tolower(*t) != tolower(*p)) + return LIKE_FALSE; + break; + case '_': + /* Match any single character. */ + break; + case '%': + /* %% is the same as % according to the SQL standard */ + /* Advance past all %'s */ + while ((plen > 0) && (*p == '%')) + 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 ((tolower(*t) == tolower(*p)) || (*p == '\\') || (*p == '_') + || ((e != NULL) && (tolower(*p) == tolower(*e)))) + { + int matched = MatchText(t, tlen, p, plen, e); + + 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; + } + } + + 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 == '%')) /* 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; +} /* MatchTextLower() */ diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h index c9ac2c39aba..d0c0b8e2bf9 100644 --- a/src/include/catalog/catversion.h +++ b/src/include/catalog/catversion.h @@ -37,7 +37,7 @@ * Portions Copyright (c) 1996-2000, PostgreSQL, Inc * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: catversion.h,v 1.39 2000/08/04 04:16:17 tgl Exp $ + * $Id: catversion.h,v 1.40 2000/08/06 18:06:13 thomas Exp $ * *------------------------------------------------------------------------- */ @@ -53,6 +53,6 @@ */ /* yyyymmddN */ -#define CATALOG_VERSION_NO 200008031 +#define CATALOG_VERSION_NO 200008061 #endif diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h index 4bc8609521f..9f616dbc101 100644 --- a/src/include/catalog/pg_proc.h +++ b/src/include/catalog/pg_proc.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2000, PostgreSQL, Inc * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: pg_proc.h,v 1.160 2000/08/05 14:59:19 momjian Exp $ + * $Id: pg_proc.h,v 1.161 2000/08/06 18:06:13 thomas Exp $ * * NOTES * The script catalog/genbki.sh reads this file and generates .bki @@ -2039,6 +2039,31 @@ DATA(insert OID = 1623 ( varchar PGUID 12 f t t t 1 f 1043 "20" 100 0 0 100 DESCR("convert int8 to varchar"); DATA(insert OID = 1624 ( mul_d_interval PGUID 12 f t t t 2 f 1186 "701 1186" 100 0 0 100 mul_d_interval - )); +DATA(insert OID = 1625 ( like PGUID 12 f t t t 3 f 16 "19 25 25" 100 0 1 0 namelike_escape - )); +DESCR("matches LIKE expression"); +DATA(insert OID = 1626 ( notlike PGUID 12 f t t t 3 f 16 "19 25 25" 100 0 1 0 namenlike_escape - )); +DESCR("does not match LIKE expression"); +DATA(insert OID = 1627 ( ilike PGUID 12 f t t t 3 f 16 "19 25 25" 100 0 1 0 inamelike_escape - )); +DESCR("matches case-insensitive LIKE expression"); +DATA(insert OID = 1628 ( inotlike PGUID 12 f t t t 3 f 16 "19 25 25" 100 0 1 0 inamenlike_escape - )); +DESCR("does not match case-insensitive LIKE expression"); +DATA(insert OID = 1629 ( like PGUID 12 f t t t 3 f 16 "25 25 25" 100 0 1 0 textlike_escape - )); +DESCR("matches LIKE expression"); +DATA(insert OID = 1630 ( notlike PGUID 12 f t t t 3 f 16 "25 25 25" 100 0 1 0 textnlike_escape - )); +DESCR("does not match LIKE expression"); +DATA(insert OID = 1631 ( ilike PGUID 12 f t t t 3 f 16 "25 25 25" 100 0 1 0 itextlike_escape - )); +DESCR("matches case-insensitive LIKE expression"); +DATA(insert OID = 1632 ( inotlike PGUID 12 f t t t 3 f 16 "25 25 25" 100 0 1 0 itextnlike_escape - )); +DESCR("does not match case-insensitive LIKE expression"); +DATA(insert OID = 1633 ( ilike PGUID 12 f t t t 2 f 16 "25 25" 100 0 1 0 itextlike - )); +DESCR("matches case-insensitive LIKE expression"); +DATA(insert OID = 1634 ( inotlike PGUID 12 f t t t 2 f 16 "25 25" 100 0 1 0 itextnlike - )); +DESCR("does not match case-insensitive LIKE expression"); +DATA(insert OID = 1635 ( ilike PGUID 12 f t t t 2 f 16 "19 25" 100 0 0 100 inamelike - )); +DESCR("matches case-insensitive LIKE expression"); +DATA(insert OID = 1636 ( inotlike PGUID 12 f t t t 2 f 16 "19 25" 100 0 0 100 inamenlike - )); +DESCR("does not match case-insensitive LIKE expression"); + DATA(insert OID = 1689 ( update_pg_pwd PGUID 12 f t f t 0 f 0 "" 100 0 0 100 update_pg_pwd - )); DESCR("update pg_pwd file"); @@ -2291,9 +2316,9 @@ DESCR("greater-than"); DATA(insert OID = 1721 ( numeric_ge PGUID 12 f t t t 2 f 16 "1700 1700" 100 0 0 100 numeric_ge - )); DESCR("greater-than-or-equal"); DATA(insert OID = 1722 ( numeric_lt PGUID 12 f t t t 2 f 16 "1700 1700" 100 0 0 100 numeric_lt - )); -DESCR("lower-than"); +DESCR("less-than"); DATA(insert OID = 1723 ( numeric_le PGUID 12 f t t t 2 f 16 "1700 1700" 100 0 0 100 numeric_le - )); -DESCR("lower-than-or-equal"); +DESCR("less-than-or-equal"); DATA(insert OID = 1724 ( numeric_add PGUID 12 f t t t 2 f 1700 "1700 1700" 100 0 0 100 numeric_add - )); DESCR("add"); DATA(insert OID = 1725 ( numeric_sub PGUID 12 f t t t 2 f 1700 "1700 1700" 100 0 0 100 numeric_sub - )); diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h index 4fa7481f042..f9d3679e27a 100644 --- a/src/include/utils/builtins.h +++ b/src/include/utils/builtins.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2000, PostgreSQL, Inc * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: builtins.h,v 1.130 2000/08/03 23:07:51 tgl Exp $ + * $Id: builtins.h,v 1.131 2000/08/06 18:06:44 thomas Exp $ * *------------------------------------------------------------------------- */ @@ -431,8 +431,12 @@ extern Datum pgsql_version(PG_FUNCTION_ARGS); /* like.c */ extern Datum namelike(PG_FUNCTION_ARGS); extern Datum namenlike(PG_FUNCTION_ARGS); +extern Datum namelike_escape(PG_FUNCTION_ARGS); +extern Datum namenlike_escape(PG_FUNCTION_ARGS); extern Datum textlike(PG_FUNCTION_ARGS); extern Datum textnlike(PG_FUNCTION_ARGS); +extern Datum textlike_escape(PG_FUNCTION_ARGS); +extern Datum textnlike_escape(PG_FUNCTION_ARGS); /* oracle_compat.c */ extern Datum lower(PG_FUNCTION_ARGS);