Commit 68d9fbeb authored by Thomas G. Lockhart's avatar Thomas G. Lockhart

Implement the IS DISTINCT FROM operator per SQL99.

Reused the Expr node to hold DISTINCT which strongly resembles
 the existing OP info. Define DISTINCT_EXPR which strongly resembles
 the existing OPER_EXPR opType, but with handling for NULLs required
 by SQL99.
We have explicit support for single-element DISTINCT comparisons
 all the way through to the executor. But, multi-element DISTINCTs
 are handled by expanding into a comparison tree in gram.y as is done for
 other row comparisons. Per discussions, it might be desirable to move
 this into one or more purpose-built nodes to be handled in the backend.
Define the optional ROW keyword and token per SQL99.
 This allows single-element row constructs, which were formerly disallowed
 due to shift/reduce conflicts with parenthesized a_expr clauses.
Define the SQL99 TREAT() function. Currently, use as a synonym for CAST().
parent c7eea66c
......@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/executor/execQual.c,v 1.94 2002/06/20 20:29:27 momjian Exp $
* $Header: /cvsroot/pgsql/src/backend/executor/execQual.c,v 1.95 2002/07/04 15:23:29 thomas Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -51,6 +51,8 @@ static Datum ExecEvalArrayRef(ArrayRef *arrayRef, ExprContext *econtext,
static Datum ExecEvalVar(Var *variable, ExprContext *econtext, bool *isNull);
static Datum ExecEvalOper(Expr *opClause, ExprContext *econtext,
bool *isNull, ExprDoneCond *isDone);
static Datum ExecEvalDistinct(Expr *opClause, ExprContext *econtext,
bool *isNull, ExprDoneCond *isDone);
static Datum ExecEvalFunc(Expr *funcClause, ExprContext *econtext,
bool *isNull, ExprDoneCond *isDone);
static ExprDoneCond ExecEvalFuncArgs(FunctionCallInfo fcinfo,
......@@ -832,6 +834,7 @@ ExecMakeFunctionResult(FunctionCachePtr fcache,
/* ----------------------------------------------------------------
* ExecEvalOper
* ExecEvalDistinct
* ExecEvalFunc
*
* Evaluate the functional result of a list of arguments by calling the
......@@ -878,6 +881,80 @@ ExecEvalOper(Expr *opClause,
isNull, isDone);
}
/* ----------------------------------------------------------------
* ExecEvalDistinct
*
* IS DISTINCT FROM must evaluate arguments to determine whether
* they are NULL; if either is NULL then the result is already
* known. If neither is NULL, then proceed to evaluate the
* function. Note that this is *always* derived from the equals
* operator, but since we've already evaluated the arguments
* we can not simply reuse ExecEvalOper() or ExecEvalFunc().
* ----------------------------------------------------------------
*/
static Datum
ExecEvalDistinct(Expr *opClause,
ExprContext *econtext,
bool *isNull,
ExprDoneCond *isDone)
{
bool result;
FunctionCachePtr fcache;
FunctionCallInfoData fcinfo;
ExprDoneCond argDone;
Oper *op;
List *argList;
/*
* we extract the oid of the function associated with the op and then
* pass the work onto ExecMakeFunctionResult which evaluates the
* arguments and returns the result of calling the function on the
* evaluated arguments.
*/
op = (Oper *) opClause->oper;
argList = opClause->args;
/*
* get the fcache from the Oper node. If it is NULL, then initialize
* it
*/
fcache = op->op_fcache;
if (fcache == NULL)
{
fcache = init_fcache(op->opid, length(argList),
econtext->ecxt_per_query_memory);
op->op_fcache = fcache;
}
Assert(fcache->func.fn_retset == FALSE);
/* Need to prep callinfo structure */
MemSet(&fcinfo, 0, sizeof(fcinfo));
fcinfo.flinfo = &(fcache->func);
argDone = ExecEvalFuncArgs(&fcinfo, argList, econtext);
Assert(fcinfo->nargs == 2);
if (fcinfo.argnull[0] && fcinfo.argnull[1])
{
/* Both NULL? Then is not distinct... */
result = FALSE;
}
else if (fcinfo.argnull[0] || fcinfo.argnull[1])
{
/* One is NULL? Then is distinct... */
result = TRUE;
}
else
{
fcinfo.isnull = false;
result = FunctionCallInvoke(&fcinfo);
*isNull = fcinfo.isnull;
result = (!DatumGetBool(result));
}
return BoolGetDatum(result);
}
/* ----------------------------------------------------------------
* ExecEvalFunc
* ----------------------------------------------------------------
......@@ -1367,6 +1444,10 @@ ExecEvalExpr(Node *expression,
case NOT_EXPR:
retDatum = ExecEvalNot(expr, econtext, isNull);
break;
case DISTINCT_EXPR:
retDatum = ExecEvalDistinct(expr, econtext,
isNull, isDone);
break;
case SUBPLAN_EXPR:
retDatum = ExecSubPlan((SubPlan *) expr->oper,
expr->args, econtext,
......
......@@ -5,7 +5,7 @@
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $Header: /cvsroot/pgsql/src/backend/nodes/outfuncs.c,v 1.160 2002/06/20 20:29:29 momjian Exp $
* $Header: /cvsroot/pgsql/src/backend/nodes/outfuncs.c,v 1.161 2002/07/04 15:23:53 thomas Exp $
*
* NOTES
* Every (plan) node in POSTGRES has an associated "out" routine which
......@@ -719,6 +719,9 @@ _outExpr(StringInfo str, Expr *node)
case OP_EXPR:
opstr = "op";
break;
case DISTINCT_EXPR:
opstr = "distinct";
break;
case FUNC_EXPR:
opstr = "func";
break;
......
......@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/nodes/readfuncs.c,v 1.123 2002/06/20 20:29:29 momjian Exp $
* $Header: /cvsroot/pgsql/src/backend/nodes/readfuncs.c,v 1.124 2002/07/04 15:23:54 thomas Exp $
*
* NOTES
* Most of the read functions for plan nodes are tested. (In fact, they
......@@ -804,6 +804,8 @@ _readExpr(void)
token = pg_strtok(&length); /* get opType */
if (strncmp(token, "op", 2) == 0)
local_node->opType = OP_EXPR;
else if (strncmp(token, "distinct", 8) == 0)
local_node->opType = DISTINCT_EXPR;
else if (strncmp(token, "func", 4) == 0)
local_node->opType = FUNC_EXPR;
else if (strncmp(token, "or", 2) == 0)
......
......@@ -42,7 +42,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/optimizer/path/costsize.c,v 1.88 2002/06/26 21:58:56 momjian Exp $
* $Header: /cvsroot/pgsql/src/backend/optimizer/path/costsize.c,v 1.89 2002/07/04 15:23:56 thomas Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -1092,6 +1092,7 @@ cost_qual_eval_walker(Node *node, Cost *total)
switch (expr->opType)
{
case OP_EXPR:
case DISTINCT_EXPR:
case FUNC_EXPR:
*total += cpu_operator_cost;
break;
......
......@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/optimizer/util/clauses.c,v 1.101 2002/06/20 20:29:31 momjian Exp $
* $Header: /cvsroot/pgsql/src/backend/optimizer/util/clauses.c,v 1.102 2002/07/04 15:23:58 thomas Exp $
*
* HISTORY
* AUTHOR DATE MAJOR EVENT
......@@ -71,6 +71,7 @@ make_clause(int type, Node *oper, List *args)
expr->typeOid = BOOLOID;
break;
case OP_EXPR:
case DISTINCT_EXPR:
expr->typeOid = ((Oper *) oper)->opresulttype;
break;
case FUNC_EXPR:
......@@ -107,7 +108,8 @@ is_opclause(Node *clause)
{
return (clause != NULL &&
IsA(clause, Expr) &&
((Expr *) clause)->opType == OP_EXPR);
((((Expr *) clause)->opType == OP_EXPR) ||
((Expr *) clause)->opType == DISTINCT_EXPR));
}
/*
......@@ -458,7 +460,7 @@ pull_agg_clause_walker(Node *node, List **listptr)
/*
* expression_returns_set
* Test whethe an expression returns a set result.
* Test whether an expression returns a set result.
*
* Because we use expression_tree_walker(), this can also be applied to
* whole targetlists; it'll produce TRUE if any one of the tlist items
......@@ -482,6 +484,7 @@ expression_returns_set_walker(Node *node, void *context)
switch (expr->opType)
{
case OP_EXPR:
case DISTINCT_EXPR:
if (((Oper *) expr->oper)->opretset)
return true;
/* else fall through to check args */
......@@ -757,6 +760,7 @@ contain_mutable_functions_walker(Node *node, void *context)
switch (expr->opType)
{
case OP_EXPR:
case DISTINCT_EXPR:
if (op_volatile(((Oper *) expr->oper)->opno) != PROVOLATILE_IMMUTABLE)
return true;
break;
......@@ -806,6 +810,7 @@ contain_volatile_functions_walker(Node *node, void *context)
switch (expr->opType)
{
case OP_EXPR:
case DISTINCT_EXPR:
if (op_volatile(((Oper *) expr->oper)->opno) == PROVOLATILE_VOLATILE)
return true;
break;
......@@ -1138,7 +1143,7 @@ eval_const_expressions_mutator(Node *node, void *context)
* expression_tree_mutator directly rather than recursing to self.
*/
args = (List *) expression_tree_mutator((Node *) expr->args,
eval_const_expressions_mutator,
eval_const_expressions_mutator,
(void *) context);
switch (expr->opType)
......@@ -1159,6 +1164,97 @@ eval_const_expressions_mutator(Node *node, void *context)
* args
*/
break;
case DISTINCT_EXPR:
{
List *arg;
bool has_null_input = false;
bool all_null_input = true;
bool has_nonconst_input = false;
/*
* Check for constant inputs and especially constant-NULL inputs.
*/
Assert(length(args) == 2);
foreach(arg, args)
{
if (IsA(lfirst(arg), Const))
{
has_null_input |= ((Const *) lfirst(arg))->constisnull;
all_null_input &= ((Const *) lfirst(arg))->constisnull;
}
else
{
has_nonconst_input = true;
}
}
/* all nulls? then not distinct */
if (all_null_input)
return MAKEBOOLCONST(false, false);
/* one null? then distinct */
if (has_null_input)
return MAKEBOOLCONST(true, false);
/* all constants? then optimize this out */
if (!has_nonconst_input)
{
Oid result_typeid;
int16 resultTypLen;
bool resultTypByVal;
ExprContext *econtext;
Datum const_val;
bool const_is_null;
Oper *oper = (Oper *) expr->oper;
replace_opid(oper); /* OK to scribble on input to this extent */
result_typeid = oper->opresulttype;
/*
* OK, looks like we can simplify this operator/function.
*
* We use the executor's routine ExecEvalExpr() to avoid duplication of
* code and ensure we get the same result as the executor would get.
*
* Build a new Expr node containing the already-simplified arguments. The
* only other setup needed here is the replace_opid() that we already
* did for the OP_EXPR case.
*/
newexpr = makeNode(Expr);
newexpr->typeOid = expr->typeOid;
newexpr->opType = expr->opType;
newexpr->oper = expr->oper;
newexpr->args = args;
/* Get info needed about result datatype */
get_typlenbyval(result_typeid, &resultTypLen, &resultTypByVal);
/*
* It is OK to pass a dummy econtext because none of the
* ExecEvalExpr() code used in this situation will use econtext. That
* might seem fortuitous, but it's not so unreasonable --- a constant
* expression does not depend on context, by definition, n'est ce pas?
*/
econtext = MakeExprContext(NULL, CurrentMemoryContext);
const_val = ExecEvalExprSwitchContext((Node *) newexpr, econtext,
&const_is_null, NULL);
/* Must copy result out of sub-context used by expression eval */
if (!const_is_null)
const_val = datumCopy(const_val, resultTypByVal, resultTypLen);
FreeExprContext(econtext);
pfree(newexpr);
/*
* Make the constant result node.
*/
return (Node *) makeConst(result_typeid, resultTypLen,
const_val, const_is_null,
resultTypByVal, false, false);
}
break;
}
case OR_EXPR:
{
......
This diff is collapsed.
......@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/parser/keywords.c,v 1.117 2002/06/22 02:04:45 thomas Exp $
* $Header: /cvsroot/pgsql/src/backend/parser/keywords.c,v 1.118 2002/07/04 15:24:01 thomas Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -283,6 +283,7 @@ static const ScanKeyword ScanKeywords[] = {
{"toast", TOAST},
{"trailing", TRAILING},
{"transaction", TRANSACTION},
{"treat", TREAT},
{"trigger", TRIGGER},
{"trim", TRIM},
{"true", TRUE_P},
......
......@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/parser/parse_expr.c,v 1.119 2002/06/20 20:29:32 momjian Exp $
* $Header: /cvsroot/pgsql/src/backend/parser/parse_expr.c,v 1.120 2002/07/04 15:24:01 thomas Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -271,6 +271,17 @@ transformExpr(ParseState *pstate, Node *expr)
result = (Node *) expr;
}
break;
case DISTINCT:
{
Node *lexpr = transformExpr(pstate,
a->lexpr);
Node *rexpr = transformExpr(pstate,
a->rexpr);
result = (Node *) make_op(a->name,
lexpr,
rexpr);
((Expr *)result)->opType = DISTINCT_EXPR;
}
}
break;
}
......
......@@ -3,7 +3,7 @@
* back to source text
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/utils/adt/ruleutils.c,v 1.108 2002/06/13 03:40:49 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/utils/adt/ruleutils.c,v 1.109 2002/07/04 15:24:07 thomas Exp $
*
* This software is copyrighted by Jan Wieck - Hamburg.
*
......@@ -1626,6 +1626,21 @@ get_rule_expr(Node *node, deparse_context *context)
get_oper_expr(expr, context);
break;
case DISTINCT_EXPR:
appendStringInfoChar(buf, '(');
Assert(length(args) == 2);
{
/* binary operator */
Node *arg1 = (Node *) lfirst(args);
Node *arg2 = (Node *) lsecond(args);
get_rule_expr(arg1, context);
appendStringInfo(buf, " IS DISTINCT FROM ");
get_rule_expr(arg2, context);
}
appendStringInfoChar(buf, ')');
break;
case FUNC_EXPR:
get_func_expr(expr, context);
break;
......
......@@ -10,7 +10,7 @@
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $Id: primnodes.h,v 1.64 2002/06/20 20:29:51 momjian Exp $
* $Id: primnodes.h,v 1.65 2002/07/04 15:24:11 thomas Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -144,7 +144,8 @@ typedef struct RangeVar
*/
typedef enum OpType
{
OP_EXPR, FUNC_EXPR, OR_EXPR, AND_EXPR, NOT_EXPR, SUBPLAN_EXPR
OP_EXPR, DISTINCT_EXPR, FUNC_EXPR,
OR_EXPR, AND_EXPR, NOT_EXPR, SUBPLAN_EXPR
} OpType;
typedef struct Expr
......
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