Commit 16669702 authored by Bruce Momjian's avatar Bruce Momjian

I've fixed up the way domain constraints (not null and type length)

are managed as per request.

Moved from merging with table attributes to applying themselves during
coerce_type() and coerce_type_typmod.

Regression tests altered to test the cast() scenarios.

Rod Taylor
parent 5af6e0a4
......@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/commands/tablecmds.c,v 1.18 2002/07/01 15:27:46 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/commands/tablecmds.c,v 1.19 2002/07/06 20:16:35 momjian Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -48,7 +48,6 @@
#include "utils/relcache.h"
static List *MergeDomainAttributes(List *schema);
static List *MergeAttributes(List *schema, List *supers, bool istemp,
List **supOids, List **supconstr, bool *supHasOids);
static bool change_varattnos_of_a_node(Node *node, const AttrNumber *newattno);
......@@ -122,13 +121,6 @@ DefineRelation(CreateStmt *stmt, char relkind)
aclcheck_error(aclresult, get_namespace_name(namespaceId));
}
/*
* Merge domain attributes into the known columns before processing table
* inheritance. Otherwise we risk adding double constraints to a
* domain-type column that's inherited.
*/
schema = MergeDomainAttributes(schema);
/*
* Look up inheritance ancestors and generate relation schema,
* including inherited attributes.
......@@ -328,49 +320,6 @@ TruncateRelation(const RangeVar *relation)
heap_truncate(relid);
}
/*
* MergeDomainAttributes
* Returns a new table schema with the constraints, types, and other
* attributes of domains resolved for fields using a domain as
* their type.
*/
static List *
MergeDomainAttributes(List *schema)
{
List *entry;
/*
* Loop through the table elements supplied. These should
* never include inherited domains else they'll be
* double (or more) processed.
*/
foreach(entry, schema)
{
ColumnDef *coldef = lfirst(entry);
HeapTuple tuple;
Form_pg_type typeTup;
tuple = typenameType(coldef->typename);
typeTup = (Form_pg_type) GETSTRUCT(tuple);
if (typeTup->typtype == 'd')
{
/* Force the column to have the correct typmod. */
coldef->typename->typmod = typeTup->typtypmod;
/* XXX more to do here? */
}
/* Enforce type NOT NULL || column definition NOT NULL -> NOT NULL */
/* Currently only used for domains, but could be valid for all */
coldef->is_not_null |= typeTup->typnotnull;
ReleaseSysCache(tuple);
}
return schema;
}
/*----------
* MergeAttributes
* Returns new schema given initial schema and superclasses.
......
......@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/executor/execQual.c,v 1.96 2002/07/04 16:44:08 momjian Exp $
* $Header: /cvsroot/pgsql/src/backend/executor/execQual.c,v 1.97 2002/07/06 20:16:35 momjian Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -66,6 +66,8 @@ static Datum ExecEvalNullTest(NullTest *ntest, ExprContext *econtext,
bool *isNull, ExprDoneCond *isDone);
static Datum ExecEvalBooleanTest(BooleanTest *btest, ExprContext *econtext,
bool *isNull, ExprDoneCond *isDone);
static Datum ExecEvalConstraint(Constraint *constraint, ExprContext *econtext,
bool *isNull, ExprDoneCond *isDone);
/*----------
......@@ -1226,6 +1228,43 @@ ExecEvalNullTest(NullTest *ntest,
}
}
/*
* ExecEvalConstraint
*
* Test the constraint against the data provided. If the data fits
* within the constraint specifications, pass it through (return the
* datum) otherwise throw an error.
*/
static Datum
ExecEvalConstraint(Constraint *constraint, ExprContext *econtext,
bool *isNull, ExprDoneCond *isDone)
{
Datum result;
result = ExecEvalExpr(constraint->raw_expr, econtext, isNull, isDone);
/* Test for the constraint type */
switch(constraint->contype)
{
case CONSTR_NOTNULL:
if (*isNull)
{
elog(ERROR, "Domain %s does not allow NULL values", constraint->name);
}
break;
case CONSTR_CHECK:
elog(ERROR, "ExecEvalConstraint: Domain CHECK Constraints not yet implemented");
break;
default:
elog(ERROR, "ExecEvalConstraint: Constraint type unknown");
break;
}
/* If all has gone well (constraint did not fail) return the datum */
return result;
}
/* ----------------------------------------------------------------
* ExecEvalBooleanTest
*
......@@ -1473,6 +1512,12 @@ ExecEvalExpr(Node *expression,
isNull,
isDone);
break;
case T_Constraint:
retDatum = ExecEvalConstraint((Constraint *) expression,
econtext,
isNull,
isDone);
break;
case T_CaseExpr:
retDatum = ExecEvalCase((CaseExpr *) expression,
econtext,
......
......@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/optimizer/util/clauses.c,v 1.102 2002/07/04 15:23:58 thomas Exp $
* $Header: /cvsroot/pgsql/src/backend/optimizer/util/clauses.c,v 1.103 2002/07/06 20:16:35 momjian Exp $
*
* HISTORY
* AUTHOR DATE MAJOR EVENT
......@@ -1882,6 +1882,8 @@ expression_tree_walker(Node *node,
return true;
}
break;
case T_Constraint:
return walker(((Constraint *) node)->raw_expr, context);
case T_NullTest:
return walker(((NullTest *) node)->arg, context);
case T_BooleanTest:
......@@ -2236,6 +2238,20 @@ expression_tree_mutator(Node *node,
return (Node *) newnode;
}
break;
case T_Constraint:
{
/*
* Used for confirming domains. Only needed fields
* within the executor are the name, raw expression
* and constraint type.
*/
Constraint *con = (Constraint *) node;
Constraint *newnode;
FLATCOPY(newnode, con, Constraint);
MUTATE(newnode->raw_expr, con->raw_expr, Node *);
return (Node *) newnode;
}
case T_NullTest:
{
NullTest *ntest = (NullTest *) node;
......
......@@ -11,7 +11,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/parser/gram.y,v 2.336 2002/07/04 15:23:59 thomas Exp $
* $Header: /cvsroot/pgsql/src/backend/parser/gram.y,v 2.337 2002/07/06 20:16:35 momjian Exp $
*
* HISTORY
* AUTHOR DATE MAJOR EVENT
......@@ -6968,24 +6968,16 @@ static Node *
makeTypeCast(Node *arg, TypeName *typename)
{
/*
* If arg is an A_Const, just stick the typename into the
* field reserved for it --- unless there's something there already!
* (We don't want to collapse x::type1::type2 into just x::type2.)
* Otherwise, generate a TypeCast node.
* Simply generate a TypeCast node.
*
* Earlier we would determine whether an A_Const would
* be acceptable, however Domains require coerce_type()
* to process them -- applying constraints as required.
*/
if (IsA(arg, A_Const) &&
((A_Const *) arg)->typename == NULL)
{
((A_Const *) arg)->typename = typename;
return arg;
}
else
{
TypeCast *n = makeNode(TypeCast);
n->arg = arg;
n->typename = typename;
return (Node *) n;
}
TypeCast *n = makeNode(TypeCast);
n->arg = arg;
n->typename = typename;
return (Node *) n;
}
static Node *
......
......@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/parser/parse_coerce.c,v 2.74 2002/06/20 20:29:32 momjian Exp $
* $Header: /cvsroot/pgsql/src/backend/parser/parse_coerce.c,v 2.75 2002/07/06 20:16:36 momjian Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -33,7 +33,7 @@ static Oid PreferredType(CATEGORY category, Oid type);
static Node *build_func_call(Oid funcid, Oid rettype, List *args);
static Oid find_coercion_function(Oid targetTypeId, Oid inputTypeId,
Oid secondArgType, bool isExplicit);
static Node *TypeConstraints(Node *arg, Oid typeId);
/* coerce_type()
* Convert a function argument to a different type.
......@@ -48,7 +48,7 @@ coerce_type(ParseState *pstate, Node *node, Oid inputTypeId,
targetTypeId == InvalidOid ||
node == NULL)
{
/* no conversion needed */
/* no conversion needed, but constraints may need to be applied */
result = node;
}
else if (inputTypeId == UNKNOWNOID && IsA(node, Const))
......@@ -72,6 +72,7 @@ coerce_type(ParseState *pstate, Node *node, Oid inputTypeId,
Const *con = (Const *) node;
Const *newcon = makeNode(Const);
Type targetType = typeidType(targetTypeId);
Oid baseTypeId = getBaseType(targetTypeId);
newcon->consttype = targetTypeId;
newcon->constlen = typeLen(targetType);
......@@ -83,14 +84,16 @@ coerce_type(ParseState *pstate, Node *node, Oid inputTypeId,
{
char *val = DatumGetCString(DirectFunctionCall1(unknownout,
con->constvalue));
newcon->constvalue = stringTypeDatum(targetType, val, atttypmod);
pfree(val);
}
ReleaseSysCache(targetType);
/* Test for domain, and apply appropriate constraints */
result = (Node *) newcon;
if (targetTypeId != baseTypeId)
result = (Node *) TypeConstraints(result, targetTypeId);
}
else if (IsBinaryCompatible(inputTypeId, targetTypeId))
{
......@@ -103,8 +106,17 @@ coerce_type(ParseState *pstate, Node *node, Oid inputTypeId,
* default -1 typmod, to save a possible length-coercion later?
* Would work if both types have same interpretation of typmod,
* which is likely but not certain.
*
* Domains may have value restrictions beyond the base type that
* must be accounted for.
*/
result = (Node *) makeRelabelType(node, targetTypeId, -1);
Oid baseTypeId = getBaseType(targetTypeId);
result = node;
if (targetTypeId != baseTypeId)
result = (Node *) TypeConstraints(result, targetTypeId);
result = (Node *) makeRelabelType(result, targetTypeId, -1);
}
else if (typeInheritsFrom(inputTypeId, targetTypeId))
{
......@@ -125,8 +137,8 @@ coerce_type(ParseState *pstate, Node *node, Oid inputTypeId,
*
* For domains, we use the coercion function for the base type.
*/
Oid baseTypeId = getBaseType(targetTypeId);
Oid funcId;
Oid baseTypeId = getBaseType(targetTypeId);
funcId = find_coercion_function(baseTypeId,
getBaseType(inputTypeId),
......@@ -138,9 +150,12 @@ coerce_type(ParseState *pstate, Node *node, Oid inputTypeId,
result = build_func_call(funcId, baseTypeId, makeList1(node));
/* if domain, relabel with domain type ID */
/*
* If domain, relabel with domain type ID and test against domain
* constraints
*/
if (targetTypeId != baseTypeId)
result = (Node *) makeRelabelType(result, targetTypeId, -1);
result = (Node *) TypeConstraints(result, targetTypeId);
/*
* If the input is a constant, apply the type conversion function
......@@ -275,6 +290,21 @@ coerce_type_typmod(ParseState *pstate, Node *node,
{
Oid baseTypeId;
Oid funcId;
int32 domainTypMod = NULL;
/* If given type is a domain, use base type instead */
baseTypeId = getBaseTypeTypeMod(targetTypeId, &domainTypMod);
/*
* Use the domain typmod rather than what was supplied if the
* domain was empty. atttypmod will always be -1 if domains are in use.
*/
if (baseTypeId != targetTypeId)
{
Assert(atttypmod < 0);
atttypmod = domainTypMod;
}
/*
* A negative typmod is assumed to mean that no coercion is wanted.
......@@ -282,12 +312,8 @@ coerce_type_typmod(ParseState *pstate, Node *node,
if (atttypmod < 0 || atttypmod == exprTypmod(node))
return node;
/* If given type is a domain, use base type instead */
baseTypeId = getBaseType(targetTypeId);
/* Note this is always implicit coercion */
funcId = find_coercion_function(baseTypeId, baseTypeId, INT4OID, false);
if (OidIsValid(funcId))
{
Const *cons;
......@@ -301,10 +327,6 @@ coerce_type_typmod(ParseState *pstate, Node *node,
false);
node = build_func_call(funcId, baseTypeId, makeList2(node, cons));
/* relabel if it's domain case */
if (targetTypeId != baseTypeId)
node = (Node *) makeRelabelType(node, targetTypeId, atttypmod);
}
return node;
......@@ -805,3 +827,58 @@ build_func_call(Oid funcid, Oid rettype, List *args)
return (Node *) expr;
}
/*
* Create an expression tree to enforce the constraints (if any)
* which should be applied by the type.
*/
static Node *
TypeConstraints(Node *arg, Oid typeId)
{
char *notNull = NULL;
for (;;)
{
HeapTuple tup;
Form_pg_type typTup;
tup = SearchSysCache(TYPEOID,
ObjectIdGetDatum(typeId),
0, 0, 0);
if (!HeapTupleIsValid(tup))
elog(ERROR, "getBaseType: failed to lookup type %u", typeId);
typTup = (Form_pg_type) GETSTRUCT(tup);
/* Test for NOT NULL Constraint */
if (typTup->typnotnull && notNull == NULL)
notNull = NameStr(typTup->typname);
/* TODO: Add CHECK Constraints to domains */
if (typTup->typtype != 'd')
{
/* Not a domain, so done */
ReleaseSysCache(tup);
break;
}
typeId = typTup->typbasetype;
ReleaseSysCache(tup);
}
/*
* Only need to add one NOT NULL check regardless of how many
* domains in the tree request it.
*/
if (notNull != NULL) {
Constraint *r = makeNode(Constraint);
r->raw_expr = arg;
r->contype = CONSTR_NOTNULL;
r->name = notNull;
arg = (Node *) r;
}
return arg;
}
......@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/parser/parse_expr.c,v 1.120 2002/07/04 15:24:01 thomas Exp $
* $Header: /cvsroot/pgsql/src/backend/parser/parse_expr.c,v 1.121 2002/07/06 20:16:36 momjian Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -864,7 +864,7 @@ exprType(Node *expr)
TargetEntry *tent;
if (!qtree || !IsA(qtree, Query))
elog(ERROR, "Cannot get type for untransformed sublink");
elog(ERROR, "exprType: Cannot get type for untransformed sublink");
tent = (TargetEntry *) lfirst(qtree->targetList);
type = tent->resdom->restype;
}
......@@ -881,6 +881,9 @@ exprType(Node *expr)
case T_CaseWhen:
type = exprType(((CaseWhen *) expr)->result);
break;
case T_Constraint:
type = exprType(((Constraint *) expr)->raw_expr);
break;
case T_NullTest:
type = BOOLOID;
break;
......@@ -888,7 +891,7 @@ exprType(Node *expr)
type = BOOLOID;
break;
default:
elog(ERROR, "Do not know how to get type for %d node",
elog(ERROR, "exprType: Do not know how to get type for %d node",
nodeTag(expr));
break;
}
......
......@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/parser/parse_type.c,v 1.43 2002/06/20 20:29:33 momjian Exp $
* $Header: /cvsroot/pgsql/src/backend/parser/parse_type.c,v 1.44 2002/07/06 20:16:36 momjian Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -450,7 +450,7 @@ parseTypeString(const char *str, Oid *type_id, int32 *typmod)
List *raw_parsetree_list;
SelectStmt *stmt;
ResTarget *restarget;
A_Const *aconst;
TypeCast *typecast;
TypeName *typename;
initStringInfo(&buf);
......@@ -463,7 +463,7 @@ parseTypeString(const char *str, Oid *type_id, int32 *typmod)
* paranoia is justified since the string might contain anything.
*/
if (length(raw_parsetree_list) != 1)
elog(ERROR, "Invalid type name '%s'", str);
elog(ERROR, "parseTypeString: Invalid type name '%s'", str);
stmt = (SelectStmt *) lfirst(raw_parsetree_list);
if (stmt == NULL ||
!IsA(stmt, SelectStmt) ||
......@@ -479,25 +479,23 @@ parseTypeString(const char *str, Oid *type_id, int32 *typmod)
stmt->limitCount != NULL ||
stmt->forUpdate != NIL ||
stmt->op != SETOP_NONE)
elog(ERROR, "Invalid type name '%s'", str);
elog(ERROR, "parseTypeString: Invalid type name '%s'", str);
if (length(stmt->targetList) != 1)
elog(ERROR, "Invalid type name '%s'", str);
elog(ERROR, "parseTypeString: Invalid type name '%s'", str);
restarget = (ResTarget *) lfirst(stmt->targetList);
if (restarget == NULL ||
!IsA(restarget, ResTarget) ||
restarget->name != NULL ||
restarget->indirection != NIL)
elog(ERROR, "Invalid type name '%s'", str);
aconst = (A_Const *) restarget->val;
if (aconst == NULL ||
!IsA(aconst, A_Const) ||
aconst->val.type != T_Null)
elog(ERROR, "Invalid type name '%s'", str);
typename = aconst->typename;
elog(ERROR, "parseTypeString: Invalid type name '%s'", str);
typecast = (TypeCast *) restarget->val;
if (typecast == NULL ||
!IsA(typecast->arg, A_Const))
elog(ERROR, "parseTypeString: Invalid type name '%s'", str);
typename = typecast->typename;
if (typename == NULL ||
!IsA(typename, TypeName))
elog(ERROR, "Invalid type name '%s'", str);
elog(ERROR, "parseTypeString: Invalid type name '%s'", str);
*type_id = typenameTypeId(typename);
*typmod = typename->typmod;
......
......@@ -7,7 +7,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/utils/cache/lsyscache.c,v 1.74 2002/06/20 20:29:39 momjian Exp $
* $Header: /cvsroot/pgsql/src/backend/utils/cache/lsyscache.c,v 1.75 2002/07/06 20:16:36 momjian Exp $
*
* NOTES
* Eventually, the index information should go through here, too.
......@@ -1057,6 +1057,43 @@ getBaseType(Oid typid)
return typid;
}
/*
* getBaseTypeTypeMod
* If the given type is a domain, return its base type;
* otherwise return the type's own OID.
*/
Oid
getBaseTypeTypeMod(Oid typid, int32 *typmod)
{
/*
* We loop to find the bottom base type in a stack of domains.
*/
for (;;)
{
HeapTuple tup;
Form_pg_type typTup;
tup = SearchSysCache(TYPEOID,
ObjectIdGetDatum(typid),
0, 0, 0);
if (!HeapTupleIsValid(tup))
elog(ERROR, "getBaseType: failed to lookup type %u", typid);
typTup = (Form_pg_type) GETSTRUCT(tup);
if (typTup->typtype != 'd')
{
/* Not a domain, so done */
ReleaseSysCache(tup);
break;
}
typid = typTup->typbasetype;
*typmod = typTup->typtypmod;
ReleaseSysCache(tup);
}
return typid;
}
/*
* get_typavgwidth
*
......
......@@ -6,7 +6,7 @@
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $Id: lsyscache.h,v 1.53 2002/06/20 20:29:53 momjian Exp $
* $Id: lsyscache.h,v 1.54 2002/07/06 20:16:36 momjian Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -52,6 +52,7 @@ extern void get_typlenbyval(Oid typid, int16 *typlen, bool *typbyval);
extern char get_typstorage(Oid typid);
extern Node *get_typdefault(Oid typid);
extern Oid getBaseType(Oid typid);
extern Oid getBaseTypeTypeMod(Oid typid, int32 *typmod);
extern int32 get_typavgwidth(Oid typid, int32 typmod);
extern int32 get_attavgwidth(Oid relid, AttrNumber attnum);
extern bool get_attstatsslot(HeapTuple statstuple,
......
......@@ -11,6 +11,15 @@ create domain domainvarchar varchar(5);
create domain domainnumeric numeric(8,2);
create domain domainint4 int4;
create domain domaintext text;
-- Test coercions
SELECT cast('123456' as domainvarchar); -- fail
ERROR: value too long for type character varying(5)
SELECT cast('12345' as domainvarchar); -- pass
domainvarchar
---------------
12345
(1 row)
-- Test tables using domains
create table basictest
( testint4 domainint4
......@@ -80,7 +89,7 @@ drop table domarrtest;
drop domain domainint4arr restrict;
drop domain domaintextarr restrict;
create domain dnotnull varchar(15) NOT NULL;
create domain dnull varchar(15) NULL;
create domain dnull varchar(15);
create table nulltest
( col1 dnotnull
, col2 dnotnull NULL -- NOT NULL in the domain cannot be overridden
......@@ -88,12 +97,12 @@ create table nulltest
, col4 dnull
);
INSERT INTO nulltest DEFAULT VALUES;
ERROR: ExecAppend: Fail to add null value in not null attribute col1
ERROR: ExecAppend: Fail to add null value in not null attribute col3
INSERT INTO nulltest values ('a', 'b', 'c', 'd'); -- Good
INSERT INTO nulltest values (NULL, 'b', 'c', 'd');
ERROR: ExecAppend: Fail to add null value in not null attribute col1
ERROR: Domain dnotnull does not allow NULL values
INSERT INTO nulltest values ('a', NULL, 'c', 'd');
ERROR: ExecAppend: Fail to add null value in not null attribute col2
ERROR: Domain dnotnull does not allow NULL values
INSERT INTO nulltest values ('a', 'b', NULL, 'd');
ERROR: ExecAppend: Fail to add null value in not null attribute col3
INSERT INTO nulltest values ('a', 'b', 'c', NULL); -- Good
......@@ -104,6 +113,20 @@ select * from nulltest;
a | b | c |
(2 rows)
-- Test out coerced (casted) constraints
SELECT cast('1' as dnotnull);
dnotnull
----------
1
(1 row)
SELECT cast(NULL as dnotnull); -- fail
ERROR: Domain dnotnull does not allow NULL values
SELECT cast(cast(NULL as dnull) as dnotnull); -- fail
ERROR: Domain dnotnull does not allow NULL values
SELECT cast(col4 as dnotnull) from nulltest; -- fail
ERROR: Domain dnotnull does not allow NULL values
-- cleanup
drop table nulltest;
drop domain dnotnull restrict;
drop domain dnull restrict;
......
......@@ -17,6 +17,9 @@ create domain domainnumeric numeric(8,2);
create domain domainint4 int4;
create domain domaintext text;
-- Test coercions
SELECT cast('123456' as domainvarchar); -- fail
SELECT cast('12345' as domainvarchar); -- pass
-- Test tables using domains
create table basictest
......@@ -65,7 +68,7 @@ drop domain domaintextarr restrict;
create domain dnotnull varchar(15) NOT NULL;
create domain dnull varchar(15) NULL;
create domain dnull varchar(15);
create table nulltest
( col1 dnotnull
......@@ -81,6 +84,13 @@ INSERT INTO nulltest values ('a', 'b', NULL, 'd');
INSERT INTO nulltest values ('a', 'b', 'c', NULL); -- Good
select * from nulltest;
-- Test out coerced (casted) constraints
SELECT cast('1' as dnotnull);
SELECT cast(NULL as dnotnull); -- fail
SELECT cast(cast(NULL as dnull) as dnotnull); -- fail
SELECT cast(col4 as dnotnull) from nulltest; -- fail
-- cleanup
drop table nulltest;
drop domain dnotnull restrict;
drop domain dnull restrict;
......
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