Commit 570620c5 authored by Thomas G. Lockhart's avatar Thomas G. Lockhart

Add SQL92 string handling features (SUBSTRING, TRIM, EXTRACT).

Add parsing for UNION and outer JOINs.
Implement SQL92 "WITH TIME ZONE".
Allow some reserved words as identifiers and column labels.
Clean up indentation and "orphan spaces and tabs".
parent 05cdb99b
......@@ -10,7 +10,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/parser/gram.y,v 1.40 1997/08/28 05:02:01 vadim Exp $
* $Header: /cvsroot/pgsql/src/backend/parser/gram.y,v 1.41 1997/09/01 06:00:35 thomas Exp $
*
* HISTORY
* AUTHOR DATE MAJOR EVENT
......@@ -116,6 +116,13 @@ static Node *makeA_Expr(int oper, char *opname, Node *lexpr, Node *rexpr);
ReplaceStmt, AppendStmt, NotifyStmt, DeleteStmt, ClusterStmt,
ExplainStmt, VariableSetStmt, VariableShowStmt, VariableResetStmt
%type <str> txname
%type <node> SubSelect
%type <str> join_clause, join_type, join_outer, join_spec
%type <boolean> join_qual
%type <str> datetime
%type <str> relation_name, copy_file_name, copy_delimiter, def_name,
database_name, access_method_clause, access_method, attr_name,
class, index_name, name, file_name, recipe_name,
......@@ -142,7 +149,15 @@ static Node *makeA_Expr(int oper, char *opname, Node *lexpr, Node *rexpr);
expr_list, default_expr_list, attrs, res_target_list, res_target_list2,
def_list, opt_indirection, group_clause, groupby_list
%type <boolean> opt_inh_star, opt_binary, opt_instead, opt_with_copy,
%type <list> union_clause, select_list
%type <list> join_list
%type <sortgroupby> join_using
%type <list> extract_list, position_list
%type <list> substr_list, substr_from, substr_for, trim_list
%type <list> interval_opts
%type <boolean> opt_inh_star, opt_binary, opt_instead, opt_with_col, opt_with_copy,
index_opt_unique, opt_verbose, opt_analyze, opt_null
%type <ival> copy_dirn, archive_type, OptArchiveType, OptArchiveLocation,
......@@ -178,7 +193,8 @@ static Node *makeA_Expr(int oper, char *opname, Node *lexpr, Node *rexpr);
%type <ival> Iconst
%type <str> Sconst
%type <str> Id, date, var_value
%type <str> Id, date, var_value, zone_value
%type <str> ColId
/*
......@@ -190,20 +206,23 @@ static Node *makeA_Expr(int oper, char *opname, Node *lexpr, Node *rexpr);
/* Keywords */
%token ABORT_TRANS, ACL, ADD, AFTER, AGGREGATE, ALL, ALTER, ANALYZE,
AND, APPEND, ARCHIVE, ARCH_STORE, AS, ASC,
BACKWARD, BEFORE, BEGIN_TRANS, BETWEEN, BINARY, BY,
CAST, CHANGE, CHECK, CLOSE, CLUSTER, COLUMN, COMMIT, CONSTRAINT,
COPY, CREATE, CURRENT, CURSOR, DATABASE, DECLARE, DEFAULT, DELETE,
DELIMITERS, DESC, DISTINCT, DO, DROP, END_TRANS,
EXTEND, FETCH, FOR, FORWARD, FROM, FUNCTION, GRANT, GROUP,
HAVING, HEAVY, IN, INDEX, INHERITS, INSERT, INSTEAD, INTO, IS,
ISNULL, LANGUAGE, LIGHT, LISTEN, LOAD, MERGE, MOVE, NEW,
NONE, NOT, NOTHING, NOTIFY, NOTNULL,
OIDS, ON, OPERATOR, OPTION, OR, ORDER,
PNULL, PRIVILEGES, PUBLIC, PURGE, P_TYPE,
RENAME, REPLACE, RESET, RETRIEVE, RETURNS, REVOKE, ROLLBACK, RULE,
SELECT, SET, SETOF, SHOW, STDIN, STDOUT, STORE,
TABLE, TO, TRANSACTION, UNIQUE, UPDATE, USING, VACUUM, VALUES
VERBOSE, VERSION, VIEW, WHERE, WITH, WORK
BACKWARD, BEFORE, BEGIN_TRANS, BETWEEN, BINARY, BOTH, BY,
CAST, CHANGE, CHECK, CLOSE, CLUSTER, COLUMN, COMMIT, CONSTRAINT, COPY, CREATE, CROSS,
CURRENT, CURSOR, DATABASE, DAYINTERVAL, DECLARE, DEFAULT, DELETE, DELIMITERS, DESC,
DISTINCT, DO, DROP, END_TRANS, EXISTS, EXTEND, EXTRACT,
FETCH, FOR, FORWARD, FROM, FULL, FUNCTION, GRANT, GROUP,
HAVING, HEAVY, HOURINTERVAL,
IN, INDEX, INHERITS, INNERJOIN, INSERT, INSTEAD, INTERVAL, INTO, IS, ISNULL,
JOIN, LANGUAGE, LEADING, LEFT, LIGHT, LISTEN, LOAD, LOCAL,
MERGE, MINUTEINTERVAL, MONTHINTERVAL, MOVE,
NATURAL, NEW, NONE, NOT, NOTHING, NOTIFY, NOTNULL,
OIDS, ON, OPERATOR, OPTION, OR, ORDER, OUTERJOIN,
PNULL, POSITION, PRIVILEGES, PUBLIC, PURGE, P_TYPE,
RENAME, REPLACE, RESET, RETRIEVE, RETURNS, REVOKE, RIGHT, ROLLBACK, RULE,
SECONDINTERVAL, SELECT, SET, SETOF, SHOW, STDIN, STDOUT, STORE, SUBSTRING,
TABLE, TIME, TO, TRAILING, TRANSACTION, TRIM,
UNION, UNIQUE, UPDATE, USING, VACUUM, VALUES,
VERBOSE, VERSION, VIEW, WHERE, WITH, WORK, YEARINTERVAL, ZONE
%token EXECUTE, RECIPE, EXPLAIN, LIKE, SEQUENCE
/* Special keywords, not in the query language - see the "lex" file */
......@@ -237,6 +256,7 @@ static Node *makeA_Expr(int oper, char *opname, Node *lexpr, Node *rexpr);
%left '[' ']'
%nonassoc TYPECAST
%nonassoc REDUCE
%left UNION
%%
stmtblock: stmtmulti
......@@ -304,11 +324,29 @@ VariableSetStmt: SET var_name TO var_value
n->value = $4;
$$ = (Node *) n;
}
| SET var_name '=' var_value
{
VariableSetStmt *n = makeNode(VariableSetStmt);
n->name = $2;
n->value = $4;
$$ = (Node *) n;
}
| SET TIME ZONE zone_value
{
VariableSetStmt *n = makeNode(VariableSetStmt);
n->name = "timezone";
n->value = $4;
$$ = (Node *) n;
}
;
var_value: Sconst { $$ = $1; }
;
zone_value: Sconst { $$ = $1; }
| LOCAL { $$ = NULL; }
;
VariableShowStmt: SHOW var_name
{
VariableShowStmt *n = makeNode(VariableShowStmt);
......@@ -342,13 +380,22 @@ AddAttrStmt: ALTER TABLE relation_name opt_inh_star ADD COLUMN columnDef
}
;
columnDef: Id Typename OptDefault opt_null
/* Column definition might include WITH TIME ZONE, but only for the data types
* called out in SQL92 date/time definitions. So, check explicitly for "timestamp"
* and "time". - thomas 1997-07-14
*/
columnDef: Id Typename opt_with_col OptDefault opt_null
{
$$ = makeNode(ColumnDef);
$$->colname = $1;
$$->typename = $2;
$$->defval = $3;
$$->is_not_null = $4;
$$->typename->timezone = $3;
$$->defval = $4;
$$->is_not_null = $5;
if ($$->typename->timezone
&& (strcasecmp($$->typename->name, "timestamp")
&& strcasecmp($$->typename->name, "time")))
elog(NOTICE,"%s does not use WITH TIME ZONE",$$->typename->name);
}
;
......@@ -459,6 +506,9 @@ opt_null: NOT PNULL { $$ = true; }
| /* EMPTY */ { $$ = false; }
;
opt_with_col: WITH TIME ZONE { $$ = TRUE; }
| /* EMPTY */ { $$ = FALSE; }
;
/*****************************************************************************
*
......@@ -1032,7 +1082,7 @@ def_name_list: name_list;
* QUERY:
* purge <relname> [before <date>] [after <date>]
* or
* purge <relname> [after<date>][before <date>]
* purge <relname> [after <date>] [before <date>]
*
*****************************************************************************/
......@@ -1682,6 +1732,7 @@ CursorStmt: DECLARE name opt_binary CURSOR FOR
*
*****************************************************************************/
/******************************************************************************
RetrieveStmt: SELECT opt_unique res_target_list2
result from_clause where_clause
group_clause having_clause
......@@ -1700,6 +1751,69 @@ RetrieveStmt: SELECT opt_unique res_target_list2
}
;
RetrieveStmt: Select UNION select_list sort_clause
| Select sort_clause
Select: SELECT opt_unique res_target_list2
result from_clause where_clause
group_clause having_clause
{
Select *n = makeNode(Select);
n->unique = $2;
n->targetList = $3;
n->into = $4;
n->fromClause = $5;
n->whereClause = $6;
n->groupClause = $7;
n->havingClause = $8;
$$ = (Node *)n;
}
;
******************************************************************************/
RetrieveStmt: SELECT opt_unique res_target_list2
result from_clause where_clause
group_clause having_clause
union_clause sort_clause
{
RetrieveStmt *n = makeNode(RetrieveStmt);
n->unique = $2;
n->targetList = $3;
n->into = $4;
n->fromClause = $5;
n->whereClause = $6;
n->groupClause = $7;
n->havingClause = $8;
n->selectClause = $9;
n->sortClause = $10;
$$ = (Node *)n;
}
;
union_clause: UNION select_list { $$ = $2; }
| /*EMPTY*/ { $$ = NIL; }
;
select_list: select_list UNION SubSelect
{ $$ = lappend($1, $3); }
| SubSelect
{ $$ = lcons($1, NIL); }
;
SubSelect: SELECT opt_unique res_target_list2
result from_clause where_clause
group_clause having_clause
{
SubSelect *n = makeNode(SubSelect);
n->unique = $2;
n->targetList = $3;
n->fromClause = $5;
n->whereClause = $6;
n->groupClause = $7;
n->havingClause = $8;
$$ = (Node *)n;
}
;
result: INTO TABLE relation_name
{ $$= $3; /* should check for archive level */ }
| /*EMPTY*/
......@@ -1862,12 +1976,19 @@ having_clause: HAVING a_expr { $$ = $2; }
*
*****************************************************************************/
from_clause: FROM from_list { $$ = $2; }
from_clause: FROM '(' relation_expr join_clause relation_expr join_spec ')'
{
$$ = NIL;
elog(WARN,"JOIN not yet implemented",NULL);
}
| FROM from_list { $$ = $2; }
| /*EMPTY*/ { $$ = NIL; }
;
from_list: from_list ',' from_val
{ $$ = lappend($1, $3); }
| from_val CROSS JOIN from_val
{ elog(WARN,"CROSS JOIN not yet implemented",NULL); }
| from_val
{ $$ = lcons($1, NIL); }
;
......@@ -1892,6 +2013,70 @@ from_val: relation_expr AS var_name
}
;
join_clause: join_qual join_type JOIN
{
$$ = NULL;
}
;
join_qual: NATURAL { $$ = TRUE; }
| /*EMPTY*/ { $$ = FALSE; }
;
join_type: FULL join_outer
{ elog(WARN,"FULL OUTER JOIN not yet implemented",NULL); }
| LEFT join_outer
{ elog(WARN,"LEFT OUTER JOIN not yet implemented",NULL); }
| RIGHT join_outer
{ elog(WARN,"RIGHT OUTER JOIN not yet implemented",NULL); }
| join_outer
{ elog(WARN,"OUTER JOIN not yet implemented",NULL); }
| INNERJOIN
{ elog(WARN,"INNER JOIN not yet implemented",NULL); }
| UNION
{ elog(WARN,"UNION JOIN not yet implemented",NULL); }
| /*EMPTY*/ { $$ = NULL; /* no qualifiers */ }
;
join_outer: OUTERJOIN { $$ = NULL; }
| /*EMPTY*/ { $$ = NULL; /* no qualifiers */ }
;
join_spec: ON '(' a_expr ')' { $$ = NULL; }
| USING '(' join_list ')' { $$ = NULL; }
| /*EMPTY*/ { $$ = NULL; /* no qualifiers */ }
;
join_list: join_using { $$ = lcons($1, NIL); }
| join_list ',' join_using { $$ = lappend($1, $3); }
;
join_using: Id
{
$$ = makeNode(SortGroupBy);
$$->resno = 0;
$$->range = NULL;
$$->name = $1;
$$->useOp = NULL;
}
| Id '.' Id
{
$$ = makeNode(SortGroupBy);
$$->resno = 0;
$$->range = $1;
$$->name = $3;
$$->useOp = NULL;
}
| Iconst
{
$$ = makeNode(SortGroupBy);
$$->resno = $1;
$$->range = NULL;
$$->name = NULL;
$$->useOp = NULL;
}
;
where_clause: WHERE a_expr { $$ = $2; }
| /*EMPTY*/ { $$ = NULL; /* no qualifiers */ }
;
......@@ -1961,7 +2146,7 @@ nest_array_bounds: '[' ']' nest_array_bounds
{ $$ = NIL; }
;
typname: name
typname: txname
{
char *tname = xlateSqlType($1);
$$ = makeNode(TypeName);
......@@ -1986,21 +2171,51 @@ typname: name
$$->setof = FALSE;
}
}
| SETOF name
| SETOF txname
{
char *tname = xlateSqlType($2);
$$ = makeNode(TypeName);
$$->name = $2;
$$->name = tname;
$$->setof = TRUE;
}
;
txname: Id { $$ = $1; }
| TIME { $$ = "time"; }
| INTERVAL interval_opts { $$ = "interval"; }
;
interval_opts: YEARINTERVAL { $$ = lcons("year", NIL); }
| MONTHINTERVAL { $$ = NIL; }
| DAYINTERVAL { $$ = NIL; }
| HOURINTERVAL { $$ = NIL; }
| MINUTEINTERVAL { $$ = NIL; }
| SECONDINTERVAL { $$ = NIL; }
| YEARINTERVAL TO MONTHINTERVAL { $$ = NIL; }
| DAYINTERVAL TO HOURINTERVAL { $$ = NIL; }
| DAYINTERVAL TO MINUTEINTERVAL { $$ = NIL; }
| DAYINTERVAL TO SECONDINTERVAL { $$ = NIL; }
| HOURINTERVAL TO MINUTEINTERVAL { $$ = NIL; }
| HOURINTERVAL TO SECONDINTERVAL { $$ = NIL; }
| /* EMPTY */ { $$ = NIL; }
;
Typename: typname opt_array_bounds
{
$$ = $1;
$$->arrayBounds = $2;
}
| name '(' Iconst ')'
| txname '(' Iconst ')'
{
/*
* This block gets hit when the parser is passed a query
* which contains only spaces (e.g. from psql type " \g").
* Let's check explicitly for a zero-length argument
* here, and do nothing if so. This seems to fix the problem.
* - thomas 1997-07-13
*/
if (strlen($1) > 0) {
/*
* The following implements char() and varchar().
* We do it here instead of the 'typname:' production
......@@ -2021,16 +2236,21 @@ Typename: typname opt_array_bounds
$1);
} else if ($3 > 4096) {
/* we can store a char() of length up to the size
of a page (8KB) - page headers and friends but
just to be safe here... - ay 6/95 */
* of a page (8KB) - page headers and friends but
* just to be safe here... - ay 6/95
* XXX note this hardcoded limit - thomas 1997-07-13
*/
elog(WARN, "length for '%s' type cannot exceed 4096",
$1);
}
/* we actually implement this sort of like a varlen, so
the first 4 bytes is the length. (the difference
between this and "text" is that we blank-pad and
truncate where necessary */
* the first 4 bytes is the length. (the difference
* between this and "text" is that we blank-pad and
* truncate where necessary
*/
$$->typlen = 4 + $3;
}
}
;
......@@ -2136,6 +2356,66 @@ a_expr: attr opt_indirection
n->args = NIL;
$$ = (Node *)n;
}
/* We probably need to define an "exists" node,
* since the optimizer could choose to find only one match.
* Perhaps the first implementation could just check for
* count(*) > 0? - thomas 1997-07-19
*/
| EXISTS '(' SubSelect ')'
{
elog(WARN,"EXISTS not yet supported",NULL);
$$ = $3;
}
| EXTRACT '(' extract_list ')'
{
FuncCall *n = makeNode(FuncCall);
n->funcname = "date_part";
n->args = $3;
$$ = (Node *)n;
}
| POSITION '(' position_list ')'
{
FuncCall *n = makeNode(FuncCall);
n->funcname = "strpos";
n->args = $3;
$$ = (Node *)n;
}
| SUBSTRING '(' substr_list ')'
{
FuncCall *n = makeNode(FuncCall);
n->funcname = "substr";
n->args = $3;
$$ = (Node *)n;
}
/* various trim expressions are defined in SQL92 - thomas 1997-07-19 */
| TRIM '(' BOTH trim_list ')'
{
FuncCall *n = makeNode(FuncCall);
n->funcname = "btrim";
n->args = $4;
$$ = (Node *)n;
}
| TRIM '(' LEADING trim_list ')'
{
FuncCall *n = makeNode(FuncCall);
n->funcname = "ltrim";
n->args = $4;
$$ = (Node *)n;
}
| TRIM '(' TRAILING trim_list ')'
{
FuncCall *n = makeNode(FuncCall);
n->funcname = "rtrim";
n->args = $4;
$$ = (Node *)n;
}
| TRIM '(' trim_list ')'
{
FuncCall *n = makeNode(FuncCall);
n->funcname = "btrim";
n->args = $3;
$$ = (Node *)n;
}
| name '(' expr_list ')'
{
FuncCall *n = makeNode(FuncCall);
......@@ -2154,7 +2434,7 @@ a_expr: attr opt_indirection
| a_expr BETWEEN AexprConst AND AexprConst
{ $$ = makeA_Expr(AND, NULL,
makeA_Expr(OP, ">=", $1, $3),
makeA_Expr(OP, "<=", $1,$5));
makeA_Expr(OP, "<=", $1, $5));
}
| a_expr NOT BETWEEN AexprConst AND AexprConst
{ $$ = makeA_Expr(OR, NULL,
......@@ -2195,6 +2475,58 @@ expr_list: a_expr_or_null
{ $$ = lcons($1, NIL); }
| expr_list ',' a_expr_or_null
{ $$ = lappend($1, $3); }
| expr_list USING a_expr
{ $$ = lappend($1, $3); }
;
extract_list: datetime FROM a_expr
{
A_Const *n = makeNode(A_Const);
n->val.type = T_String;
n->val.val.str = $1;
printf( "string is %s\n", $1);
$$ = lappend(lcons((Node *)n,NIL), $3);
}
| /* EMPTY */
{ $$ = NIL; }
;
position_list: a_expr IN expr_list
{
$$ = lappend($3, $1);
}
| /* EMPTY */
{ $$ = NIL; }
;
substr_list: expr_list substr_from substr_for
{
$$ = $1;
if ($2 != NULL) $$ = lappend($$, $2);
if ($3 != NULL) $$ = lappend($$, $3);
}
| /* EMPTY */
{ $$ = NIL; }
;
substr_from: FROM expr_list
{ $$ = $2; }
| /* EMPTY */
{ $$ = NIL; }
;
substr_for: FOR expr_list
{ $$ = $2; }
| /* EMPTY */
{ $$ = NIL; }
;
trim_list: a_expr FROM expr_list
{ $$ = lappend($3, $1); }
| FROM expr_list
{ $$ = $2; }
| expr_list
{ $$ = $1; }
;
in_expr_nodes: AexprConst
......@@ -2239,6 +2571,13 @@ attrs: attr_name
{ $$ = lappend($1, makeString("*")); }
;
datetime: YEARINTERVAL { $$ = "year"; }
| MONTHINTERVAL { $$ = "month"; }
| DAYINTERVAL { $$ = "day"; }
| HOURINTERVAL { $$ = "hour"; }
| MINUTEINTERVAL { $$ = "minute"; }
| SECONDINTERVAL { $$ = "second"; }
;
/*****************************************************************************
*
......@@ -2298,15 +2637,14 @@ res_target_el: Id opt_indirection '=' a_expr_or_null
** should get rid of the other but is still needed by the defunct retrieve into
** and update (uses a subset)
*/
res_target_list2:
res_target_list2 ',' res_target_el2
res_target_list2: res_target_list2 ',' res_target_el2
{ $$ = lappend($1, $3); }
| res_target_el2
{ $$ = lcons($1, NIL); }
;
/* AS is not optional because shift/red conflict with unary ops */
res_target_el2: a_expr AS Id
res_target_el2: a_expr AS ColId
{
$$ = makeNode(ResTarget);
$$->name = $3;
......@@ -2355,7 +2693,7 @@ relation_name: SpecialRuleRelation
$$ = $1;
strNcpy(saved_relname, $1, NAMEDATALEN-1);
}
| Id
| ColId
{
/* disallow refs to magic system tables */
if (strcmp(LogRelationName, $1) == 0
......@@ -2372,7 +2710,7 @@ relation_name: SpecialRuleRelation
database_name: Id { $$ = $1; };
access_method: Id { $$ = $1; };
attr_name: Id { $$ = $1; };
attr_name: ColId { $$ = $1; };
class: Id { $$ = $1; };
index_name: Id { $$ = $1; };
var_name: Id { $$ = $1; };
......@@ -2423,6 +2761,10 @@ Sconst: SCONST { $$ = $1; };
Id: IDENT { $$ = $1; };
ColId: Id { $$ = $1; }
| datetime { $$ = $1; }
;
SpecialRuleRelation: CURRENT
{
if (QueryIsRule)
......@@ -2466,6 +2808,8 @@ xlateSqlType(char *name)
else if (!strcasecmp(name, "float") ||
!strcasecmp(name, "real"))
return "float8";
else if (!strcasecmp(name, "interval"))
return "timespan";
else
return name;
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment