Commit 37a795a6 authored by Tom Lane's avatar Tom Lane

Support domains over composite types.

This is the last major omission in our domains feature: you can now
make a domain over anything that's not a pseudotype.

The major complication from an implementation standpoint is that places
that might be creating tuples of a domain type now need to be prepared
to apply domain_check().  It seems better that unprepared code fail
with an error like "<type> is not composite" than that it silently fail
to apply domain constraints.  Therefore, relevant infrastructure like
get_func_result_type() and lookup_rowtype_tupdesc() has been adjusted
to treat domain-over-composite as a distinct case that unprepared code
won't recognize, rather than just transparently treating it the same
as plain composite.  This isn't a 100% solution to the possibility of
overlooked domain checks, but it catches most places.

In passing, improve typcache.c's support for domains (it can now cache
the identity of a domain's base type), and rewrite the argument handling
logic in jsonfuncs.c's populate_record[set]_worker to reduce duplicative
per-call lookups.

I believe this is code-complete so far as the core and contrib code go.
The PLs need varying amounts of work, which will be tackled in followup
patches.

Discussion: https://postgr.es/m/4206.1499798337@sss.pgh.pa.us
parent 08f1e1f0
...@@ -752,6 +752,8 @@ typedef struct RecordIOData ...@@ -752,6 +752,8 @@ typedef struct RecordIOData
{ {
Oid record_type; Oid record_type;
int32 record_typmod; int32 record_typmod;
/* this field is used only if target type is domain over composite: */
void *domain_info; /* opaque cache for domain checks */
int ncolumns; int ncolumns;
ColumnIOData columns[FLEXIBLE_ARRAY_MEMBER]; ColumnIOData columns[FLEXIBLE_ARRAY_MEMBER];
} RecordIOData; } RecordIOData;
...@@ -780,9 +782,11 @@ hstore_from_record(PG_FUNCTION_ARGS) ...@@ -780,9 +782,11 @@ hstore_from_record(PG_FUNCTION_ARGS)
Oid argtype = get_fn_expr_argtype(fcinfo->flinfo, 0); Oid argtype = get_fn_expr_argtype(fcinfo->flinfo, 0);
/* /*
* have no tuple to look at, so the only source of type info is the * We have no tuple to look at, so the only source of type info is the
* argtype. The lookup_rowtype_tupdesc call below will error out if we * argtype --- which might be domain over composite, but we don't care
* don't have a known composite type oid here. * here, since we have no need to be concerned about domain
* constraints. The lookup_rowtype_tupdesc_domain call below will
* error out if we don't have a known composite type oid here.
*/ */
tupType = argtype; tupType = argtype;
tupTypmod = -1; tupTypmod = -1;
...@@ -793,12 +797,15 @@ hstore_from_record(PG_FUNCTION_ARGS) ...@@ -793,12 +797,15 @@ hstore_from_record(PG_FUNCTION_ARGS)
{ {
rec = PG_GETARG_HEAPTUPLEHEADER(0); rec = PG_GETARG_HEAPTUPLEHEADER(0);
/* Extract type info from the tuple itself */ /*
* Extract type info from the tuple itself -- this will work even for
* anonymous record types.
*/
tupType = HeapTupleHeaderGetTypeId(rec); tupType = HeapTupleHeaderGetTypeId(rec);
tupTypmod = HeapTupleHeaderGetTypMod(rec); tupTypmod = HeapTupleHeaderGetTypMod(rec);
} }
tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod); tupdesc = lookup_rowtype_tupdesc_domain(tupType, tupTypmod, false);
ncolumns = tupdesc->natts; ncolumns = tupdesc->natts;
/* /*
...@@ -943,9 +950,9 @@ hstore_populate_record(PG_FUNCTION_ARGS) ...@@ -943,9 +950,9 @@ hstore_populate_record(PG_FUNCTION_ARGS)
rec = NULL; rec = NULL;
/* /*
* have no tuple to look at, so the only source of type info is the * We have no tuple to look at, so the only source of type info is the
* argtype. The lookup_rowtype_tupdesc call below will error out if we * argtype. The lookup_rowtype_tupdesc_domain call below will error
* don't have a known composite type oid here. * out if we don't have a known composite type oid here.
*/ */
tupType = argtype; tupType = argtype;
tupTypmod = -1; tupTypmod = -1;
...@@ -957,7 +964,10 @@ hstore_populate_record(PG_FUNCTION_ARGS) ...@@ -957,7 +964,10 @@ hstore_populate_record(PG_FUNCTION_ARGS)
if (PG_ARGISNULL(1)) if (PG_ARGISNULL(1))
PG_RETURN_POINTER(rec); PG_RETURN_POINTER(rec);
/* Extract type info from the tuple itself */ /*
* Extract type info from the tuple itself -- this will work even for
* anonymous record types.
*/
tupType = HeapTupleHeaderGetTypeId(rec); tupType = HeapTupleHeaderGetTypeId(rec);
tupTypmod = HeapTupleHeaderGetTypMod(rec); tupTypmod = HeapTupleHeaderGetTypMod(rec);
} }
...@@ -975,7 +985,11 @@ hstore_populate_record(PG_FUNCTION_ARGS) ...@@ -975,7 +985,11 @@ hstore_populate_record(PG_FUNCTION_ARGS)
if (HS_COUNT(hs) == 0 && rec) if (HS_COUNT(hs) == 0 && rec)
PG_RETURN_POINTER(rec); PG_RETURN_POINTER(rec);
tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod); /*
* Lookup the input record's tupdesc. For the moment, we don't worry
* about whether it is a domain over composite.
*/
tupdesc = lookup_rowtype_tupdesc_domain(tupType, tupTypmod, false);
ncolumns = tupdesc->natts; ncolumns = tupdesc->natts;
if (rec) if (rec)
...@@ -1002,6 +1016,7 @@ hstore_populate_record(PG_FUNCTION_ARGS) ...@@ -1002,6 +1016,7 @@ hstore_populate_record(PG_FUNCTION_ARGS)
my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra; my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
my_extra->record_type = InvalidOid; my_extra->record_type = InvalidOid;
my_extra->record_typmod = 0; my_extra->record_typmod = 0;
my_extra->domain_info = NULL;
} }
if (my_extra->record_type != tupType || if (my_extra->record_type != tupType ||
...@@ -1103,6 +1118,17 @@ hstore_populate_record(PG_FUNCTION_ARGS) ...@@ -1103,6 +1118,17 @@ hstore_populate_record(PG_FUNCTION_ARGS)
rettuple = heap_form_tuple(tupdesc, values, nulls); rettuple = heap_form_tuple(tupdesc, values, nulls);
/*
* If the target type is domain over composite, all we know at this point
* is that we've made a valid value of the base composite type. Must
* check domain constraints before deciding we're done.
*/
if (argtype != tupdesc->tdtypeid)
domain_check(HeapTupleGetDatum(rettuple), false,
argtype,
&my_extra->domain_info,
fcinfo->flinfo->fn_mcxt);
ReleaseTupleDesc(tupdesc); ReleaseTupleDesc(tupdesc);
PG_RETURN_DATUM(HeapTupleGetDatum(rettuple)); PG_RETURN_DATUM(HeapTupleGetDatum(rettuple));
......
...@@ -4379,8 +4379,7 @@ SET xmloption TO { DOCUMENT | CONTENT }; ...@@ -4379,8 +4379,7 @@ SET xmloption TO { DOCUMENT | CONTENT };
underlying type &mdash; for example, any operator or function that underlying type &mdash; for example, any operator or function that
can be applied to the underlying type will work on the domain type. can be applied to the underlying type will work on the domain type.
The underlying type can be any built-in or user-defined base type, The underlying type can be any built-in or user-defined base type,
enum type, array or range type, or another domain. (Currently, domains enum type, array type, composite type, range type, or another domain.
over composite types are not implemented.)
</para> </para>
<para> <para>
......
...@@ -84,8 +84,9 @@ CREATE TABLE inventory_item ( ...@@ -84,8 +84,9 @@ CREATE TABLE inventory_item (
restriction of the current implementation: since no constraints are restriction of the current implementation: since no constraints are
associated with a composite type, the constraints shown in the table associated with a composite type, the constraints shown in the table
definition <emphasis>do not apply</emphasis> to values of the composite type definition <emphasis>do not apply</emphasis> to values of the composite type
outside the table. (A partial workaround is to use domain outside the table. (To work around this, create a domain over the composite
types as members of composite types.) type, and apply the desired constraints as <literal>CHECK</literal>
constraints of the domain.)
</para> </para>
</sect2> </sect2>
......
...@@ -351,6 +351,31 @@ CREATE FUNCTION tf1 (accountno integer, debit numeric) RETURNS numeric AS $$ ...@@ -351,6 +351,31 @@ CREATE FUNCTION tf1 (accountno integer, debit numeric) RETURNS numeric AS $$
WHERE accountno = tf1.accountno WHERE accountno = tf1.accountno
RETURNING balance; RETURNING balance;
$$ LANGUAGE SQL; $$ LANGUAGE SQL;
</programlisting>
</para>
<para>
A <acronym>SQL</acronym> function must return exactly its declared
result type. This may require inserting an explicit cast.
For example, suppose we wanted the
previous <function>add_em</function> function to return
type <type>float8</type> instead. This won't work:
<programlisting>
CREATE FUNCTION add_em(integer, integer) RETURNS float8 AS $$
SELECT $1 + $2;
$$ LANGUAGE SQL;
</programlisting>
even though in other contexts <productname>PostgreSQL</productname>
would be willing to insert an implicit cast to
convert <type>integer</type> to <type>float8</type>.
We need to write it as
<programlisting>
CREATE FUNCTION add_em(integer, integer) RETURNS float8 AS $$
SELECT ($1 + $2)::float8;
$$ LANGUAGE SQL;
</programlisting> </programlisting>
</para> </para>
</sect2> </sect2>
...@@ -452,13 +477,16 @@ $$ LANGUAGE SQL; ...@@ -452,13 +477,16 @@ $$ LANGUAGE SQL;
</listitem> </listitem>
<listitem> <listitem>
<para> <para>
You must typecast the expressions to match the We must ensure each expression's type matches the corresponding
definition of the composite type, or you will get errors like this: column of the composite type, inserting a cast if necessary.
Otherwise we'll get errors like this:
<screen> <screen>
<computeroutput> <computeroutput>
ERROR: function declared to return emp returns varchar instead of text at column 1 ERROR: function declared to return emp returns varchar instead of text at column 1
</computeroutput> </computeroutput>
</screen> </screen>
As with the base-type case, the function will not insert any casts
automatically.
</para> </para>
</listitem> </listitem>
</itemizedlist> </itemizedlist>
...@@ -478,6 +506,11 @@ $$ LANGUAGE SQL; ...@@ -478,6 +506,11 @@ $$ LANGUAGE SQL;
in this situation, but it is a handy alternative in some cases in this situation, but it is a handy alternative in some cases
&mdash; for example, if we need to compute the result by calling &mdash; for example, if we need to compute the result by calling
another function that returns the desired composite value. another function that returns the desired composite value.
Another example is that if we are trying to write a function that
returns a domain over composite, rather than a plain composite type,
it is always necessary to write it as returning a single column,
since there is no other way to produce a value that is exactly of
the domain type.
</para> </para>
<para> <para>
......
...@@ -301,6 +301,11 @@ has_superclass(Oid relationId) ...@@ -301,6 +301,11 @@ has_superclass(Oid relationId)
/* /*
* Given two type OIDs, determine whether the first is a complex type * Given two type OIDs, determine whether the first is a complex type
* (class type) that inherits from the second. * (class type) that inherits from the second.
*
* This essentially asks whether the first type is guaranteed to be coercible
* to the second. Therefore, we allow the first type to be a domain over a
* complex type that inherits from the second; that creates no difficulties.
* But the second type cannot be a domain.
*/ */
bool bool
typeInheritsFrom(Oid subclassTypeId, Oid superclassTypeId) typeInheritsFrom(Oid subclassTypeId, Oid superclassTypeId)
...@@ -314,9 +319,9 @@ typeInheritsFrom(Oid subclassTypeId, Oid superclassTypeId) ...@@ -314,9 +319,9 @@ typeInheritsFrom(Oid subclassTypeId, Oid superclassTypeId)
ListCell *queue_item; ListCell *queue_item;
/* We need to work with the associated relation OIDs */ /* We need to work with the associated relation OIDs */
subclassRelid = typeidTypeRelid(subclassTypeId); subclassRelid = typeOrDomainTypeRelid(subclassTypeId);
if (subclassRelid == InvalidOid) if (subclassRelid == InvalidOid)
return false; /* not a complex type */ return false; /* not a complex type or domain over one */
superclassRelid = typeidTypeRelid(superclassTypeId); superclassRelid = typeidTypeRelid(superclassTypeId);
if (superclassRelid == InvalidOid) if (superclassRelid == InvalidOid)
return false; /* not a complex type */ return false; /* not a complex type */
......
...@@ -262,7 +262,7 @@ ProcedureCreate(const char *procedureName, ...@@ -262,7 +262,7 @@ ProcedureCreate(const char *procedureName,
*/ */
if (parameterCount == 1 && if (parameterCount == 1 &&
OidIsValid(parameterTypes->values[0]) && OidIsValid(parameterTypes->values[0]) &&
(relid = typeidTypeRelid(parameterTypes->values[0])) != InvalidOid && (relid = typeOrDomainTypeRelid(parameterTypes->values[0])) != InvalidOid &&
get_attnum(relid, procedureName) != InvalidAttrNumber) get_attnum(relid, procedureName) != InvalidAttrNumber)
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_COLUMN), (errcode(ERRCODE_DUPLICATE_COLUMN),
......
...@@ -5091,6 +5091,8 @@ find_typed_table_dependencies(Oid typeOid, const char *typeName, DropBehavior be ...@@ -5091,6 +5091,8 @@ find_typed_table_dependencies(Oid typeOid, const char *typeName, DropBehavior be
* isn't suitable, throw an error. Currently, we require that the type * isn't suitable, throw an error. Currently, we require that the type
* originated with CREATE TYPE AS. We could support any row type, but doing so * originated with CREATE TYPE AS. We could support any row type, but doing so
* would require handling a number of extra corner cases in the DDL commands. * would require handling a number of extra corner cases in the DDL commands.
* (Also, allowing domain-over-composite would open up a can of worms about
* whether and how the domain's constraints should apply to derived tables.)
*/ */
void void
check_of_type(HeapTuple typetuple) check_of_type(HeapTuple typetuple)
...@@ -6190,8 +6192,8 @@ ATPrepSetStatistics(Relation rel, const char *colName, int16 colNum, Node *newVa ...@@ -6190,8 +6192,8 @@ ATPrepSetStatistics(Relation rel, const char *colName, int16 colNum, Node *newVa
RelationGetRelationName(rel)))); RelationGetRelationName(rel))));
/* /*
* We allow referencing columns by numbers only for indexes, since * We allow referencing columns by numbers only for indexes, since table
* table column numbers could contain gaps if columns are later dropped. * column numbers could contain gaps if columns are later dropped.
*/ */
if (rel->rd_rel->relkind != RELKIND_INDEX && !colName) if (rel->rd_rel->relkind != RELKIND_INDEX && !colName)
ereport(ERROR, ereport(ERROR,
......
...@@ -798,13 +798,16 @@ DefineDomain(CreateDomainStmt *stmt) ...@@ -798,13 +798,16 @@ DefineDomain(CreateDomainStmt *stmt)
basetypeoid = HeapTupleGetOid(typeTup); basetypeoid = HeapTupleGetOid(typeTup);
/* /*
* Base type must be a plain base type, another domain, an enum or a range * Base type must be a plain base type, a composite type, another domain,
* type. Domains over pseudotypes would create a security hole. Domains * an enum or a range type. Domains over pseudotypes would create a
* over composite types might be made to work in the future, but not * security hole. (It would be shorter to code this to just check for
* today. * pseudotypes; but it seems safer to call out the specific typtypes that
* are supported, rather than assume that all future typtypes would be
* automatically supported.)
*/ */
typtype = baseType->typtype; typtype = baseType->typtype;
if (typtype != TYPTYPE_BASE && if (typtype != TYPTYPE_BASE &&
typtype != TYPTYPE_COMPOSITE &&
typtype != TYPTYPE_DOMAIN && typtype != TYPTYPE_DOMAIN &&
typtype != TYPTYPE_ENUM && typtype != TYPTYPE_ENUM &&
typtype != TYPTYPE_RANGE) typtype != TYPTYPE_RANGE)
......
...@@ -3469,8 +3469,12 @@ ExecEvalWholeRowVar(ExprState *state, ExprEvalStep *op, ExprContext *econtext) ...@@ -3469,8 +3469,12 @@ ExecEvalWholeRowVar(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
* generates an INT4 NULL regardless of the dropped column type). * generates an INT4 NULL regardless of the dropped column type).
* If we find a dropped column and cannot verify that case (1) * If we find a dropped column and cannot verify that case (1)
* holds, we have to use the slow path to check (2) for each row. * holds, we have to use the slow path to check (2) for each row.
*
* If vartype is a domain over composite, just look through that
* to the base composite type.
*/ */
var_tupdesc = lookup_rowtype_tupdesc(variable->vartype, -1); var_tupdesc = lookup_rowtype_tupdesc_domain(variable->vartype,
-1, false);
slot_tupdesc = slot->tts_tupleDescriptor; slot_tupdesc = slot->tts_tupleDescriptor;
......
...@@ -734,7 +734,8 @@ init_sexpr(Oid foid, Oid input_collation, Expr *node, ...@@ -734,7 +734,8 @@ init_sexpr(Oid foid, Oid input_collation, Expr *node,
/* Must save tupdesc in sexpr's context */ /* Must save tupdesc in sexpr's context */
oldcontext = MemoryContextSwitchTo(sexprCxt); oldcontext = MemoryContextSwitchTo(sexprCxt);
if (functypclass == TYPEFUNC_COMPOSITE) if (functypclass == TYPEFUNC_COMPOSITE ||
functypclass == TYPEFUNC_COMPOSITE_DOMAIN)
{ {
/* Composite data type, e.g. a table's row type */ /* Composite data type, e.g. a table's row type */
Assert(tupdesc); Assert(tupdesc);
......
...@@ -1665,7 +1665,15 @@ check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList, ...@@ -1665,7 +1665,15 @@ check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList,
} }
else if (fn_typtype == TYPTYPE_COMPOSITE || rettype == RECORDOID) else if (fn_typtype == TYPTYPE_COMPOSITE || rettype == RECORDOID)
{ {
/* Returns a rowtype */ /*
* Returns a rowtype.
*
* Note that we will not consider a domain over composite to be a
* "rowtype" return type; it goes through the scalar case above. This
* is because SQL functions don't provide any implicit casting to the
* result type, so there is no way to produce a domain-over-composite
* result except by computing it as an explicit single-column result.
*/
TupleDesc tupdesc; TupleDesc tupdesc;
int tupnatts; /* physical number of columns in tuple */ int tupnatts; /* physical number of columns in tuple */
int tuplogcols; /* # of nondeleted columns in tuple */ int tuplogcols; /* # of nondeleted columns in tuple */
...@@ -1711,7 +1719,10 @@ check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList, ...@@ -1711,7 +1719,10 @@ check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList,
} }
} }
/* Is the rowtype fixed, or determined only at runtime? */ /*
* Is the rowtype fixed, or determined only at runtime? (Note we
* cannot see TYPEFUNC_COMPOSITE_DOMAIN here.)
*/
if (get_func_result_type(func_id, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) if (get_func_result_type(func_id, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
{ {
/* /*
......
...@@ -383,7 +383,8 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags) ...@@ -383,7 +383,8 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags)
&funcrettype, &funcrettype,
&tupdesc); &tupdesc);
if (functypclass == TYPEFUNC_COMPOSITE) if (functypclass == TYPEFUNC_COMPOSITE ||
functypclass == TYPEFUNC_COMPOSITE_DOMAIN)
{ {
/* Composite data type, e.g. a table's row type */ /* Composite data type, e.g. a table's row type */
Assert(tupdesc); Assert(tupdesc);
......
...@@ -120,8 +120,10 @@ makeVarFromTargetEntry(Index varno, ...@@ -120,8 +120,10 @@ makeVarFromTargetEntry(Index varno,
* table entry, and varattno == 0 to signal that it references the whole * table entry, and varattno == 0 to signal that it references the whole
* tuple. (Use of zero here is unclean, since it could easily be confused * tuple. (Use of zero here is unclean, since it could easily be confused
* with error cases, but it's not worth changing now.) The vartype indicates * with error cases, but it's not worth changing now.) The vartype indicates
* a rowtype; either a named composite type, or RECORD. This function * a rowtype; either a named composite type, or a domain over a named
* encapsulates the logic for determining the correct rowtype OID to use. * composite type (only possible if the RTE is a function returning that),
* or RECORD. This function encapsulates the logic for determining the
* correct rowtype OID to use.
* *
* If allowScalar is true, then for the case where the RTE is a single function * If allowScalar is true, then for the case where the RTE is a single function
* returning a non-composite result type, we produce a normal Var referencing * returning a non-composite result type, we produce a normal Var referencing
......
...@@ -2356,6 +2356,10 @@ CommuteRowCompareExpr(RowCompareExpr *clause) ...@@ -2356,6 +2356,10 @@ CommuteRowCompareExpr(RowCompareExpr *clause)
* is still what it was when the expression was parsed. This is needed to * is still what it was when the expression was parsed. This is needed to
* guard against improper simplification after ALTER COLUMN TYPE. (XXX we * guard against improper simplification after ALTER COLUMN TYPE. (XXX we
* may well need to make similar checks elsewhere?) * may well need to make similar checks elsewhere?)
*
* rowtypeid may come from a whole-row Var, and therefore it can be a domain
* over composite, but for this purpose we only care about checking the type
* of a contained field.
*/ */
static bool static bool
rowtype_field_matches(Oid rowtypeid, int fieldnum, rowtype_field_matches(Oid rowtypeid, int fieldnum,
...@@ -2368,7 +2372,7 @@ rowtype_field_matches(Oid rowtypeid, int fieldnum, ...@@ -2368,7 +2372,7 @@ rowtype_field_matches(Oid rowtypeid, int fieldnum,
/* No issue for RECORD, since there is no way to ALTER such a type */ /* No issue for RECORD, since there is no way to ALTER such a type */
if (rowtypeid == RECORDOID) if (rowtypeid == RECORDOID)
return true; return true;
tupdesc = lookup_rowtype_tupdesc(rowtypeid, -1); tupdesc = lookup_rowtype_tupdesc_domain(rowtypeid, -1, false);
if (fieldnum <= 0 || fieldnum > tupdesc->natts) if (fieldnum <= 0 || fieldnum > tupdesc->natts)
{ {
ReleaseTupleDesc(tupdesc); ReleaseTupleDesc(tupdesc);
...@@ -5005,7 +5009,9 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte) ...@@ -5005,7 +5009,9 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
* *
* If the function returns a composite type, don't inline unless the check * If the function returns a composite type, don't inline unless the check
* shows it's returning a whole tuple result; otherwise what it's * shows it's returning a whole tuple result; otherwise what it's
* returning is a single composite column which is not what we need. * returning is a single composite column which is not what we need. (Like
* check_sql_fn_retval, we deliberately exclude domains over composite
* here.)
*/ */
if (!check_sql_fn_retval(func_oid, fexpr->funcresulttype, if (!check_sql_fn_retval(func_oid, fexpr->funcresulttype,
querytree_list, querytree_list,
......
...@@ -499,9 +499,26 @@ coerce_type(ParseState *pstate, Node *node, ...@@ -499,9 +499,26 @@ coerce_type(ParseState *pstate, Node *node,
* Input class type is a subclass of target, so generate an * Input class type is a subclass of target, so generate an
* appropriate runtime conversion (removing unneeded columns and * appropriate runtime conversion (removing unneeded columns and
* possibly rearranging the ones that are wanted). * possibly rearranging the ones that are wanted).
*
* We will also get here when the input is a domain over a subclass of
* the target type. To keep life simple for the executor, we define
* ConvertRowtypeExpr as only working between regular composite types;
* therefore, in such cases insert a RelabelType to smash the input
* expression down to its base type.
*/ */
Oid baseTypeId = getBaseType(inputTypeId);
ConvertRowtypeExpr *r = makeNode(ConvertRowtypeExpr); ConvertRowtypeExpr *r = makeNode(ConvertRowtypeExpr);
if (baseTypeId != inputTypeId)
{
RelabelType *rt = makeRelabelType((Expr *) node,
baseTypeId, -1,
InvalidOid,
COERCE_IMPLICIT_CAST);
rt->location = location;
node = (Node *) rt;
}
r->arg = (Expr *) node; r->arg = (Expr *) node;
r->resulttype = targetTypeId; r->resulttype = targetTypeId;
r->convertformat = cformat; r->convertformat = cformat;
...@@ -966,6 +983,8 @@ coerce_record_to_complex(ParseState *pstate, Node *node, ...@@ -966,6 +983,8 @@ coerce_record_to_complex(ParseState *pstate, Node *node,
int location) int location)
{ {
RowExpr *rowexpr; RowExpr *rowexpr;
Oid baseTypeId;
int32 baseTypeMod = -1;
TupleDesc tupdesc; TupleDesc tupdesc;
List *args = NIL; List *args = NIL;
List *newargs; List *newargs;
...@@ -1001,7 +1020,14 @@ coerce_record_to_complex(ParseState *pstate, Node *node, ...@@ -1001,7 +1020,14 @@ coerce_record_to_complex(ParseState *pstate, Node *node,
format_type_be(targetTypeId)), format_type_be(targetTypeId)),
parser_coercion_errposition(pstate, location, node))); parser_coercion_errposition(pstate, location, node)));
tupdesc = lookup_rowtype_tupdesc(targetTypeId, -1); /*
* Look up the composite type, accounting for possibility that what we are
* given is a domain over composite.
*/
baseTypeId = getBaseTypeAndTypmod(targetTypeId, &baseTypeMod);
tupdesc = lookup_rowtype_tupdesc(baseTypeId, baseTypeMod);
/* Process the fields */
newargs = NIL; newargs = NIL;
ucolno = 1; ucolno = 1;
arg = list_head(args); arg = list_head(args);
...@@ -1070,10 +1096,22 @@ coerce_record_to_complex(ParseState *pstate, Node *node, ...@@ -1070,10 +1096,22 @@ coerce_record_to_complex(ParseState *pstate, Node *node,
rowexpr = makeNode(RowExpr); rowexpr = makeNode(RowExpr);
rowexpr->args = newargs; rowexpr->args = newargs;
rowexpr->row_typeid = targetTypeId; rowexpr->row_typeid = baseTypeId;
rowexpr->row_format = cformat; rowexpr->row_format = cformat;
rowexpr->colnames = NIL; /* not needed for named target type */ rowexpr->colnames = NIL; /* not needed for named target type */
rowexpr->location = location; rowexpr->location = location;
/* If target is a domain, apply constraints */
if (baseTypeId != targetTypeId)
{
rowexpr->row_format = COERCE_IMPLICIT_CAST;
return coerce_to_domain((Node *) rowexpr,
baseTypeId, baseTypeMod,
targetTypeId,
ccontext, cformat, location,
false);
}
return (Node *) rowexpr; return (Node *) rowexpr;
} }
...@@ -2401,13 +2439,13 @@ is_complex_array(Oid typid) ...@@ -2401,13 +2439,13 @@ is_complex_array(Oid typid)
/* /*
* Check whether reltypeId is the row type of a typed table of type * Check whether reltypeId is the row type of a typed table of type
* reloftypeId. (This is conceptually similar to the subtype * reloftypeId, or is a domain over such a row type. (This is conceptually
* relationship checked by typeInheritsFrom().) * similar to the subtype relationship checked by typeInheritsFrom().)
*/ */
static bool static bool
typeIsOfTypedTable(Oid reltypeId, Oid reloftypeId) typeIsOfTypedTable(Oid reltypeId, Oid reloftypeId)
{ {
Oid relid = typeidTypeRelid(reltypeId); Oid relid = typeOrDomainTypeRelid(reltypeId);
bool result = false; bool result = false;
if (relid) if (relid)
......
...@@ -1819,18 +1819,19 @@ ParseComplexProjection(ParseState *pstate, char *funcname, Node *first_arg, ...@@ -1819,18 +1819,19 @@ ParseComplexProjection(ParseState *pstate, char *funcname, Node *first_arg,
} }
/* /*
* Else do it the hard way with get_expr_result_type(). * Else do it the hard way with get_expr_result_tupdesc().
* *
* If it's a Var of type RECORD, we have to work even harder: we have to * If it's a Var of type RECORD, we have to work even harder: we have to
* find what the Var refers to, and pass that to get_expr_result_type. * find what the Var refers to, and pass that to get_expr_result_tupdesc.
* That task is handled by expandRecordVariable(). * That task is handled by expandRecordVariable().
*/ */
if (IsA(first_arg, Var) && if (IsA(first_arg, Var) &&
((Var *) first_arg)->vartype == RECORDOID) ((Var *) first_arg)->vartype == RECORDOID)
tupdesc = expandRecordVariable(pstate, (Var *) first_arg, 0); tupdesc = expandRecordVariable(pstate, (Var *) first_arg, 0);
else if (get_expr_result_type(first_arg, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) else
tupdesc = get_expr_result_tupdesc(first_arg, true);
if (!tupdesc)
return NULL; /* unresolvable RECORD type */ return NULL; /* unresolvable RECORD type */
Assert(tupdesc);
for (i = 0; i < tupdesc->natts; i++) for (i = 0; i < tupdesc->natts; i++)
{ {
......
...@@ -1496,7 +1496,8 @@ addRangeTableEntryForFunction(ParseState *pstate, ...@@ -1496,7 +1496,8 @@ addRangeTableEntryForFunction(ParseState *pstate,
parser_errposition(pstate, exprLocation(funcexpr)))); parser_errposition(pstate, exprLocation(funcexpr))));
} }
if (functypclass == TYPEFUNC_COMPOSITE) if (functypclass == TYPEFUNC_COMPOSITE ||
functypclass == TYPEFUNC_COMPOSITE_DOMAIN)
{ {
/* Composite data type, e.g. a table's row type */ /* Composite data type, e.g. a table's row type */
Assert(tupdesc); Assert(tupdesc);
...@@ -2245,7 +2246,8 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up, ...@@ -2245,7 +2246,8 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
functypclass = get_expr_result_type(rtfunc->funcexpr, functypclass = get_expr_result_type(rtfunc->funcexpr,
&funcrettype, &funcrettype,
&tupdesc); &tupdesc);
if (functypclass == TYPEFUNC_COMPOSITE) if (functypclass == TYPEFUNC_COMPOSITE ||
functypclass == TYPEFUNC_COMPOSITE_DOMAIN)
{ {
/* Composite data type, e.g. a table's row type */ /* Composite data type, e.g. a table's row type */
Assert(tupdesc); Assert(tupdesc);
...@@ -2765,7 +2767,8 @@ get_rte_attribute_type(RangeTblEntry *rte, AttrNumber attnum, ...@@ -2765,7 +2767,8 @@ get_rte_attribute_type(RangeTblEntry *rte, AttrNumber attnum,
&funcrettype, &funcrettype,
&tupdesc); &tupdesc);
if (functypclass == TYPEFUNC_COMPOSITE) if (functypclass == TYPEFUNC_COMPOSITE ||
functypclass == TYPEFUNC_COMPOSITE_DOMAIN)
{ {
/* Composite data type, e.g. a table's row type */ /* Composite data type, e.g. a table's row type */
Form_pg_attribute att_tup; Form_pg_attribute att_tup;
...@@ -2966,14 +2969,11 @@ get_rte_attribute_is_dropped(RangeTblEntry *rte, AttrNumber attnum) ...@@ -2966,14 +2969,11 @@ get_rte_attribute_is_dropped(RangeTblEntry *rte, AttrNumber attnum)
if (attnum > atts_done && if (attnum > atts_done &&
attnum <= atts_done + rtfunc->funccolcount) attnum <= atts_done + rtfunc->funccolcount)
{ {
TypeFuncClass functypclass;
Oid funcrettype;
TupleDesc tupdesc; TupleDesc tupdesc;
functypclass = get_expr_result_type(rtfunc->funcexpr, tupdesc = get_expr_result_tupdesc(rtfunc->funcexpr,
&funcrettype, true);
&tupdesc); if (tupdesc)
if (functypclass == TYPEFUNC_COMPOSITE)
{ {
/* Composite data type, e.g. a table's row type */ /* Composite data type, e.g. a table's row type */
Form_pg_attribute att_tup; Form_pg_attribute att_tup;
......
...@@ -725,6 +725,8 @@ transformAssignmentIndirection(ParseState *pstate, ...@@ -725,6 +725,8 @@ transformAssignmentIndirection(ParseState *pstate,
else else
{ {
FieldStore *fstore; FieldStore *fstore;
Oid baseTypeId;
int32 baseTypeMod;
Oid typrelid; Oid typrelid;
AttrNumber attnum; AttrNumber attnum;
Oid fieldTypeId; Oid fieldTypeId;
...@@ -752,7 +754,14 @@ transformAssignmentIndirection(ParseState *pstate, ...@@ -752,7 +754,14 @@ transformAssignmentIndirection(ParseState *pstate,
/* No subscripts, so can process field selection here */ /* No subscripts, so can process field selection here */
typrelid = typeidTypeRelid(targetTypeId); /*
* Look up the composite type, accounting for possibility that
* what we are given is a domain over composite.
*/
baseTypeMod = targetTypMod;
baseTypeId = getBaseTypeAndTypmod(targetTypeId, &baseTypeMod);
typrelid = typeidTypeRelid(baseTypeId);
if (!typrelid) if (!typrelid)
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH), (errcode(ERRCODE_DATATYPE_MISMATCH),
...@@ -796,7 +805,17 @@ transformAssignmentIndirection(ParseState *pstate, ...@@ -796,7 +805,17 @@ transformAssignmentIndirection(ParseState *pstate,
fstore->arg = (Expr *) basenode; fstore->arg = (Expr *) basenode;
fstore->newvals = list_make1(rhs); fstore->newvals = list_make1(rhs);
fstore->fieldnums = list_make1_int(attnum); fstore->fieldnums = list_make1_int(attnum);
fstore->resulttype = targetTypeId; fstore->resulttype = baseTypeId;
/* If target is a domain, apply constraints */
if (baseTypeId != targetTypeId)
return coerce_to_domain((Node *) fstore,
baseTypeId, baseTypeMod,
targetTypeId,
COERCION_IMPLICIT,
COERCE_IMPLICIT_CAST,
location,
false);
return (Node *) fstore; return (Node *) fstore;
} }
...@@ -1387,22 +1406,18 @@ ExpandRowReference(ParseState *pstate, Node *expr, ...@@ -1387,22 +1406,18 @@ ExpandRowReference(ParseState *pstate, Node *expr,
* (This can be pretty inefficient if the expression involves nontrivial * (This can be pretty inefficient if the expression involves nontrivial
* computation :-(.) * computation :-(.)
* *
* Verify it's a composite type, and get the tupdesc. We use * Verify it's a composite type, and get the tupdesc.
* get_expr_result_type() because that can handle references to functions * get_expr_result_tupdesc() handles this conveniently.
* returning anonymous record types. If that fails, use
* lookup_rowtype_tupdesc(), which will almost certainly fail as well, but
* it will give an appropriate error message.
* *
* If it's a Var of type RECORD, we have to work even harder: we have to * If it's a Var of type RECORD, we have to work even harder: we have to
* find what the Var refers to, and pass that to get_expr_result_type. * find what the Var refers to, and pass that to get_expr_result_tupdesc.
* That task is handled by expandRecordVariable(). * That task is handled by expandRecordVariable().
*/ */
if (IsA(expr, Var) && if (IsA(expr, Var) &&
((Var *) expr)->vartype == RECORDOID) ((Var *) expr)->vartype == RECORDOID)
tupleDesc = expandRecordVariable(pstate, (Var *) expr, 0); tupleDesc = expandRecordVariable(pstate, (Var *) expr, 0);
else if (get_expr_result_type(expr, NULL, &tupleDesc) != TYPEFUNC_COMPOSITE) else
tupleDesc = lookup_rowtype_tupdesc_copy(exprType(expr), tupleDesc = get_expr_result_tupdesc(expr, false);
exprTypmod(expr));
Assert(tupleDesc); Assert(tupleDesc);
/* Generate a list of references to the individual fields */ /* Generate a list of references to the individual fields */
...@@ -1610,15 +1625,9 @@ expandRecordVariable(ParseState *pstate, Var *var, int levelsup) ...@@ -1610,15 +1625,9 @@ expandRecordVariable(ParseState *pstate, Var *var, int levelsup)
/* /*
* We now have an expression we can't expand any more, so see if * We now have an expression we can't expand any more, so see if
* get_expr_result_type() can do anything with it. If not, pass to * get_expr_result_tupdesc() can do anything with it.
* lookup_rowtype_tupdesc() which will probably fail, but will give an
* appropriate error message while failing.
*/ */
if (get_expr_result_type(expr, NULL, &tupleDesc) != TYPEFUNC_COMPOSITE) return get_expr_result_tupdesc(expr, false);
tupleDesc = lookup_rowtype_tupdesc_copy(exprType(expr),
exprTypmod(expr));
return tupleDesc;
} }
......
...@@ -641,7 +641,10 @@ stringTypeDatum(Type tp, char *string, int32 atttypmod) ...@@ -641,7 +641,10 @@ stringTypeDatum(Type tp, char *string, int32 atttypmod)
return OidInputFunctionCall(typinput, string, typioparam, atttypmod); return OidInputFunctionCall(typinput, string, typioparam, atttypmod);
} }
/* given a typeid, return the type's typrelid (associated relation, if any) */ /*
* Given a typeid, return the type's typrelid (associated relation), if any.
* Returns InvalidOid if type is not a composite type.
*/
Oid Oid
typeidTypeRelid(Oid type_id) typeidTypeRelid(Oid type_id)
{ {
...@@ -652,8 +655,39 @@ typeidTypeRelid(Oid type_id) ...@@ -652,8 +655,39 @@ typeidTypeRelid(Oid type_id)
typeTuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(type_id)); typeTuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(type_id));
if (!HeapTupleIsValid(typeTuple)) if (!HeapTupleIsValid(typeTuple))
elog(ERROR, "cache lookup failed for type %u", type_id); elog(ERROR, "cache lookup failed for type %u", type_id);
type = (Form_pg_type) GETSTRUCT(typeTuple);
result = type->typrelid;
ReleaseSysCache(typeTuple);
return result;
}
/*
* Given a typeid, return the type's typrelid (associated relation), if any.
* Returns InvalidOid if type is not a composite type or a domain over one.
* This is the same as typeidTypeRelid(getBaseType(type_id)), but faster.
*/
Oid
typeOrDomainTypeRelid(Oid type_id)
{
HeapTuple typeTuple;
Form_pg_type type;
Oid result;
for (;;)
{
typeTuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(type_id));
if (!HeapTupleIsValid(typeTuple))
elog(ERROR, "cache lookup failed for type %u", type_id);
type = (Form_pg_type) GETSTRUCT(typeTuple); type = (Form_pg_type) GETSTRUCT(typeTuple);
if (type->typtype != TYPTYPE_DOMAIN)
{
/* Not a domain, so done looking through domains */
break;
}
/* It is a domain, so examine the base type instead */
type_id = type->typbasetype;
ReleaseSysCache(typeTuple);
}
result = type->typrelid; result = type->typrelid;
ReleaseSysCache(typeTuple); ReleaseSysCache(typeTuple);
return result; return result;
......
...@@ -82,9 +82,10 @@ domain_state_setup(Oid domainType, bool binary, MemoryContext mcxt) ...@@ -82,9 +82,10 @@ domain_state_setup(Oid domainType, bool binary, MemoryContext mcxt)
* Verify that domainType represents a valid domain type. We need to be * Verify that domainType represents a valid domain type. We need to be
* careful here because domain_in and domain_recv can be called from SQL, * careful here because domain_in and domain_recv can be called from SQL,
* possibly with incorrect arguments. We use lookup_type_cache mainly * possibly with incorrect arguments. We use lookup_type_cache mainly
* because it will throw a clean user-facing error for a bad OID. * because it will throw a clean user-facing error for a bad OID; but also
* it can cache the underlying base type info.
*/ */
typentry = lookup_type_cache(domainType, 0); typentry = lookup_type_cache(domainType, TYPECACHE_DOMAIN_BASE_INFO);
if (typentry->typtype != TYPTYPE_DOMAIN) if (typentry->typtype != TYPTYPE_DOMAIN)
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH), (errcode(ERRCODE_DATATYPE_MISMATCH),
...@@ -92,8 +93,8 @@ domain_state_setup(Oid domainType, bool binary, MemoryContext mcxt) ...@@ -92,8 +93,8 @@ domain_state_setup(Oid domainType, bool binary, MemoryContext mcxt)
format_type_be(domainType)))); format_type_be(domainType))));
/* Find out the base type */ /* Find out the base type */
my_extra->typtypmod = -1; baseType = typentry->domainBaseType;
baseType = getBaseTypeAndTypmod(domainType, &my_extra->typtypmod); my_extra->typtypmod = typentry->domainBaseTypmod;
/* Look up underlying I/O function */ /* Look up underlying I/O function */
if (binary) if (binary)
......
This diff is collapsed.
...@@ -6731,17 +6731,12 @@ get_name_for_var_field(Var *var, int fieldno, ...@@ -6731,17 +6731,12 @@ get_name_for_var_field(Var *var, int fieldno,
/* /*
* If it's a Var of type RECORD, we have to find what the Var refers to; * If it's a Var of type RECORD, we have to find what the Var refers to;
* if not, we can use get_expr_result_type. If that fails, we try * if not, we can use get_expr_result_tupdesc().
* lookup_rowtype_tupdesc, which will probably fail too, but will ereport
* an acceptable message.
*/ */
if (!IsA(var, Var) || if (!IsA(var, Var) ||
var->vartype != RECORDOID) var->vartype != RECORDOID)
{ {
if (get_expr_result_type((Node *) var, NULL, &tupleDesc) != TYPEFUNC_COMPOSITE) tupleDesc = get_expr_result_tupdesc((Node *) var, false);
tupleDesc = lookup_rowtype_tupdesc_copy(exprType((Node *) var),
exprTypmod((Node *) var));
Assert(tupleDesc);
/* Got the tupdesc, so we can extract the field name */ /* Got the tupdesc, so we can extract the field name */
Assert(fieldno >= 1 && fieldno <= tupleDesc->natts); Assert(fieldno >= 1 && fieldno <= tupleDesc->natts);
return NameStr(TupleDescAttr(tupleDesc, fieldno - 1)->attname); return NameStr(TupleDescAttr(tupleDesc, fieldno - 1)->attname);
...@@ -7044,14 +7039,9 @@ get_name_for_var_field(Var *var, int fieldno, ...@@ -7044,14 +7039,9 @@ get_name_for_var_field(Var *var, int fieldno,
/* /*
* We now have an expression we can't expand any more, so see if * We now have an expression we can't expand any more, so see if
* get_expr_result_type() can do anything with it. If not, pass to * get_expr_result_tupdesc() can do anything with it.
* lookup_rowtype_tupdesc() which will probably fail, but will give an */
* appropriate error message while failing. tupleDesc = get_expr_result_tupdesc(expr, false);
*/
if (get_expr_result_type(expr, NULL, &tupleDesc) != TYPEFUNC_COMPOSITE)
tupleDesc = lookup_rowtype_tupdesc_copy(exprType(expr),
exprTypmod(expr));
Assert(tupleDesc);
/* Got the tupdesc, so we can extract the field name */ /* Got the tupdesc, so we can extract the field name */
Assert(fieldno >= 1 && fieldno <= tupleDesc->natts); Assert(fieldno >= 1 && fieldno <= tupleDesc->natts);
return NameStr(TupleDescAttr(tupleDesc, fieldno - 1)->attname); return NameStr(TupleDescAttr(tupleDesc, fieldno - 1)->attname);
......
...@@ -2398,12 +2398,26 @@ get_typtype(Oid typid) ...@@ -2398,12 +2398,26 @@ get_typtype(Oid typid)
* type_is_rowtype * type_is_rowtype
* *
* Convenience function to determine whether a type OID represents * Convenience function to determine whether a type OID represents
* a "rowtype" type --- either RECORD or a named composite type. * a "rowtype" type --- either RECORD or a named composite type
* (including a domain over a named composite type).
*/ */
bool bool
type_is_rowtype(Oid typid) type_is_rowtype(Oid typid)
{ {
return (typid == RECORDOID || get_typtype(typid) == TYPTYPE_COMPOSITE); if (typid == RECORDOID)
return true; /* easy case */
switch (get_typtype(typid))
{
case TYPTYPE_COMPOSITE:
return true;
case TYPTYPE_DOMAIN:
if (get_typtype(getBaseType(typid)) == TYPTYPE_COMPOSITE)
return true;
break;
default:
break;
}
return false;
} }
/* /*
......
...@@ -96,6 +96,7 @@ static TypeCacheEntry *firstDomainTypeEntry = NULL; ...@@ -96,6 +96,7 @@ static TypeCacheEntry *firstDomainTypeEntry = NULL;
#define TCFLAGS_HAVE_FIELD_EQUALITY 0x004000 #define TCFLAGS_HAVE_FIELD_EQUALITY 0x004000
#define TCFLAGS_HAVE_FIELD_COMPARE 0x008000 #define TCFLAGS_HAVE_FIELD_COMPARE 0x008000
#define TCFLAGS_CHECKED_DOMAIN_CONSTRAINTS 0x010000 #define TCFLAGS_CHECKED_DOMAIN_CONSTRAINTS 0x010000
#define TCFLAGS_DOMAIN_BASE_IS_COMPOSITE 0x020000
/* /*
* Data stored about a domain type's constraints. Note that we do not create * Data stored about a domain type's constraints. Note that we do not create
...@@ -747,7 +748,15 @@ lookup_type_cache(Oid type_id, int flags) ...@@ -747,7 +748,15 @@ lookup_type_cache(Oid type_id, int flags)
/* /*
* If requested, get information about a domain type * If requested, get information about a domain type
*/ */
if ((flags & TYPECACHE_DOMAIN_INFO) && if ((flags & TYPECACHE_DOMAIN_BASE_INFO) &&
typentry->domainBaseType == InvalidOid &&
typentry->typtype == TYPTYPE_DOMAIN)
{
typentry->domainBaseTypmod = -1;
typentry->domainBaseType =
getBaseTypeAndTypmod(type_id, &typentry->domainBaseTypmod);
}
if ((flags & TYPECACHE_DOMAIN_CONSTR_INFO) &&
(typentry->flags & TCFLAGS_CHECKED_DOMAIN_CONSTRAINTS) == 0 && (typentry->flags & TCFLAGS_CHECKED_DOMAIN_CONSTRAINTS) == 0 &&
typentry->typtype == TYPTYPE_DOMAIN) typentry->typtype == TYPTYPE_DOMAIN)
{ {
...@@ -1166,7 +1175,7 @@ InitDomainConstraintRef(Oid type_id, DomainConstraintRef *ref, ...@@ -1166,7 +1175,7 @@ InitDomainConstraintRef(Oid type_id, DomainConstraintRef *ref,
MemoryContext refctx, bool need_exprstate) MemoryContext refctx, bool need_exprstate)
{ {
/* Look up the typcache entry --- we assume it survives indefinitely */ /* Look up the typcache entry --- we assume it survives indefinitely */
ref->tcache = lookup_type_cache(type_id, TYPECACHE_DOMAIN_INFO); ref->tcache = lookup_type_cache(type_id, TYPECACHE_DOMAIN_CONSTR_INFO);
ref->need_exprstate = need_exprstate; ref->need_exprstate = need_exprstate;
/* For safety, establish the callback before acquiring a refcount */ /* For safety, establish the callback before acquiring a refcount */
ref->refctx = refctx; ref->refctx = refctx;
...@@ -1257,7 +1266,7 @@ DomainHasConstraints(Oid type_id) ...@@ -1257,7 +1266,7 @@ DomainHasConstraints(Oid type_id)
* Note: a side effect is to cause the typcache's domain data to become * Note: a side effect is to cause the typcache's domain data to become
* valid. This is fine since we'll likely need it soon if there is any. * valid. This is fine since we'll likely need it soon if there is any.
*/ */
typentry = lookup_type_cache(type_id, TYPECACHE_DOMAIN_INFO); typentry = lookup_type_cache(type_id, TYPECACHE_DOMAIN_CONSTR_INFO);
return (typentry->domainData != NULL); return (typentry->domainData != NULL);
} }
...@@ -1405,6 +1414,29 @@ cache_record_field_properties(TypeCacheEntry *typentry) ...@@ -1405,6 +1414,29 @@ cache_record_field_properties(TypeCacheEntry *typentry)
DecrTupleDescRefCount(tupdesc); DecrTupleDescRefCount(tupdesc);
} }
else if (typentry->typtype == TYPTYPE_DOMAIN)
{
/* If it's domain over composite, copy base type's properties */
TypeCacheEntry *baseentry;
/* load up basetype info if we didn't already */
if (typentry->domainBaseType == InvalidOid)
{
typentry->domainBaseTypmod = -1;
typentry->domainBaseType =
getBaseTypeAndTypmod(typentry->type_id,
&typentry->domainBaseTypmod);
}
baseentry = lookup_type_cache(typentry->domainBaseType,
TYPECACHE_EQ_OPR |
TYPECACHE_CMP_PROC);
if (baseentry->typtype == TYPTYPE_COMPOSITE)
{
typentry->flags |= TCFLAGS_DOMAIN_BASE_IS_COMPOSITE;
typentry->flags |= baseentry->flags & (TCFLAGS_HAVE_FIELD_EQUALITY |
TCFLAGS_HAVE_FIELD_COMPARE);
}
}
typentry->flags |= TCFLAGS_CHECKED_FIELD_PROPERTIES; typentry->flags |= TCFLAGS_CHECKED_FIELD_PROPERTIES;
} }
...@@ -1618,6 +1650,53 @@ lookup_rowtype_tupdesc_copy(Oid type_id, int32 typmod) ...@@ -1618,6 +1650,53 @@ lookup_rowtype_tupdesc_copy(Oid type_id, int32 typmod)
return CreateTupleDescCopyConstr(tmp); return CreateTupleDescCopyConstr(tmp);
} }
/*
* lookup_rowtype_tupdesc_domain
*
* Same as lookup_rowtype_tupdesc_noerror(), except that the type can also be
* a domain over a named composite type; so this is effectively equivalent to
* lookup_rowtype_tupdesc_noerror(getBaseType(type_id), typmod, noError)
* except for being a tad faster.
*
* Note: the reason we don't fold the look-through-domain behavior into plain
* lookup_rowtype_tupdesc() is that we want callers to know they might be
* dealing with a domain. Otherwise they might construct a tuple that should
* be of the domain type, but not apply domain constraints.
*/
TupleDesc
lookup_rowtype_tupdesc_domain(Oid type_id, int32 typmod, bool noError)
{
TupleDesc tupDesc;
if (type_id != RECORDOID)
{
/*
* Check for domain or named composite type. We might as well load
* whichever data is needed.
*/
TypeCacheEntry *typentry;
typentry = lookup_type_cache(type_id,
TYPECACHE_TUPDESC |
TYPECACHE_DOMAIN_BASE_INFO);
if (typentry->typtype == TYPTYPE_DOMAIN)
return lookup_rowtype_tupdesc_noerror(typentry->domainBaseType,
typentry->domainBaseTypmod,
noError);
if (typentry->tupDesc == NULL && !noError)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("type %s is not composite",
format_type_be(type_id))));
tupDesc = typentry->tupDesc;
}
else
tupDesc = lookup_rowtype_tupdesc_internal(type_id, typmod, noError);
if (tupDesc != NULL)
PinTupleDesc(tupDesc);
return tupDesc;
}
/* /*
* Hash function for the hash table of RecordCacheEntry. * Hash function for the hash table of RecordCacheEntry.
*/ */
...@@ -1929,9 +2008,8 @@ TypeCacheRelCallback(Datum arg, Oid relid) ...@@ -1929,9 +2008,8 @@ TypeCacheRelCallback(Datum arg, Oid relid)
hash_seq_init(&status, TypeCacheHash); hash_seq_init(&status, TypeCacheHash);
while ((typentry = (TypeCacheEntry *) hash_seq_search(&status)) != NULL) while ((typentry = (TypeCacheEntry *) hash_seq_search(&status)) != NULL)
{ {
if (typentry->typtype != TYPTYPE_COMPOSITE) if (typentry->typtype == TYPTYPE_COMPOSITE)
continue; /* skip non-composites */ {
/* Skip if no match, unless we're zapping all composite types */ /* Skip if no match, unless we're zapping all composite types */
if (relid != typentry->typrelid && relid != InvalidOid) if (relid != typentry->typrelid && relid != InvalidOid)
continue; continue;
...@@ -1941,8 +2019,8 @@ TypeCacheRelCallback(Datum arg, Oid relid) ...@@ -1941,8 +2019,8 @@ TypeCacheRelCallback(Datum arg, Oid relid)
{ {
/* /*
* Release our refcount, and free the tupdesc if none remain. * Release our refcount, and free the tupdesc if none remain.
* (Can't use DecrTupleDescRefCount because this reference is not * (Can't use DecrTupleDescRefCount because this reference is
* logged in current resource owner.) * not logged in current resource owner.)
*/ */
Assert(typentry->tupDesc->tdrefcount > 0); Assert(typentry->tupDesc->tdrefcount > 0);
if (--typentry->tupDesc->tdrefcount == 0) if (--typentry->tupDesc->tdrefcount == 0)
...@@ -1953,6 +2031,18 @@ TypeCacheRelCallback(Datum arg, Oid relid) ...@@ -1953,6 +2031,18 @@ TypeCacheRelCallback(Datum arg, Oid relid)
/* Reset equality/comparison/hashing validity information */ /* Reset equality/comparison/hashing validity information */
typentry->flags = 0; typentry->flags = 0;
} }
else if (typentry->typtype == TYPTYPE_DOMAIN)
{
/*
* If it's domain over composite, reset flags. (We don't bother
* trying to determine whether the specific base type needs a
* reset.) Note that if we haven't determined whether the base
* type is composite, we don't need to reset anything.
*/
if (typentry->flags & TCFLAGS_DOMAIN_BASE_IS_COMPOSITE)
typentry->flags = 0;
}
}
} }
/* /*
......
...@@ -39,7 +39,7 @@ static TypeFuncClass internal_get_result_type(Oid funcid, ...@@ -39,7 +39,7 @@ static TypeFuncClass internal_get_result_type(Oid funcid,
static bool resolve_polymorphic_tupdesc(TupleDesc tupdesc, static bool resolve_polymorphic_tupdesc(TupleDesc tupdesc,
oidvector *declared_args, oidvector *declared_args,
Node *call_expr); Node *call_expr);
static TypeFuncClass get_type_func_class(Oid typid); static TypeFuncClass get_type_func_class(Oid typid, Oid *base_typeid);
/* /*
...@@ -246,14 +246,17 @@ get_expr_result_type(Node *expr, ...@@ -246,14 +246,17 @@ get_expr_result_type(Node *expr,
{ {
/* handle as a generic expression; no chance to resolve RECORD */ /* handle as a generic expression; no chance to resolve RECORD */
Oid typid = exprType(expr); Oid typid = exprType(expr);
Oid base_typid;
if (resultTypeId) if (resultTypeId)
*resultTypeId = typid; *resultTypeId = typid;
if (resultTupleDesc) if (resultTupleDesc)
*resultTupleDesc = NULL; *resultTupleDesc = NULL;
result = get_type_func_class(typid); result = get_type_func_class(typid, &base_typid);
if (result == TYPEFUNC_COMPOSITE && resultTupleDesc) if ((result == TYPEFUNC_COMPOSITE ||
*resultTupleDesc = lookup_rowtype_tupdesc_copy(typid, -1); result == TYPEFUNC_COMPOSITE_DOMAIN) &&
resultTupleDesc)
*resultTupleDesc = lookup_rowtype_tupdesc_copy(base_typid, -1);
} }
return result; return result;
...@@ -296,6 +299,7 @@ internal_get_result_type(Oid funcid, ...@@ -296,6 +299,7 @@ internal_get_result_type(Oid funcid,
HeapTuple tp; HeapTuple tp;
Form_pg_proc procform; Form_pg_proc procform;
Oid rettype; Oid rettype;
Oid base_rettype;
TupleDesc tupdesc; TupleDesc tupdesc;
/* First fetch the function's pg_proc row to inspect its rettype */ /* First fetch the function's pg_proc row to inspect its rettype */
...@@ -363,12 +367,13 @@ internal_get_result_type(Oid funcid, ...@@ -363,12 +367,13 @@ internal_get_result_type(Oid funcid,
*resultTupleDesc = NULL; /* default result */ *resultTupleDesc = NULL; /* default result */
/* Classify the result type */ /* Classify the result type */
result = get_type_func_class(rettype); result = get_type_func_class(rettype, &base_rettype);
switch (result) switch (result)
{ {
case TYPEFUNC_COMPOSITE: case TYPEFUNC_COMPOSITE:
case TYPEFUNC_COMPOSITE_DOMAIN:
if (resultTupleDesc) if (resultTupleDesc)
*resultTupleDesc = lookup_rowtype_tupdesc_copy(rettype, -1); *resultTupleDesc = lookup_rowtype_tupdesc_copy(base_rettype, -1);
/* Named composite types can't have any polymorphic columns */ /* Named composite types can't have any polymorphic columns */
break; break;
case TYPEFUNC_SCALAR: case TYPEFUNC_SCALAR:
...@@ -393,6 +398,46 @@ internal_get_result_type(Oid funcid, ...@@ -393,6 +398,46 @@ internal_get_result_type(Oid funcid,
return result; return result;
} }
/*
* get_expr_result_tupdesc
* Get a tupdesc describing the result of a composite-valued expression
*
* If expression is not composite or rowtype can't be determined, returns NULL
* if noError is true, else throws error.
*
* This is a simpler version of get_expr_result_type() for use when the caller
* is only interested in determinate rowtype results.
*/
TupleDesc
get_expr_result_tupdesc(Node *expr, bool noError)
{
TupleDesc tupleDesc;
TypeFuncClass functypclass;
functypclass = get_expr_result_type(expr, NULL, &tupleDesc);
if (functypclass == TYPEFUNC_COMPOSITE ||
functypclass == TYPEFUNC_COMPOSITE_DOMAIN)
return tupleDesc;
if (!noError)
{
Oid exprTypeId = exprType(expr);
if (exprTypeId != RECORDOID)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("type %s is not composite",
format_type_be(exprTypeId))));
else
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("record type has not been registered")));
}
return NULL;
}
/* /*
* Given the result tuple descriptor for a function with OUT parameters, * Given the result tuple descriptor for a function with OUT parameters,
* replace any polymorphic columns (ANYELEMENT etc) with correct data types * replace any polymorphic columns (ANYELEMENT etc) with correct data types
...@@ -741,23 +786,31 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes, ...@@ -741,23 +786,31 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
/* /*
* get_type_func_class * get_type_func_class
* Given the type OID, obtain its TYPEFUNC classification. * Given the type OID, obtain its TYPEFUNC classification.
* Also, if it's a domain, return the base type OID.
* *
* This is intended to centralize a bunch of formerly ad-hoc code for * This is intended to centralize a bunch of formerly ad-hoc code for
* classifying types. The categories used here are useful for deciding * classifying types. The categories used here are useful for deciding
* how to handle functions returning the datatype. * how to handle functions returning the datatype.
*/ */
static TypeFuncClass static TypeFuncClass
get_type_func_class(Oid typid) get_type_func_class(Oid typid, Oid *base_typeid)
{ {
*base_typeid = typid;
switch (get_typtype(typid)) switch (get_typtype(typid))
{ {
case TYPTYPE_COMPOSITE: case TYPTYPE_COMPOSITE:
return TYPEFUNC_COMPOSITE; return TYPEFUNC_COMPOSITE;
case TYPTYPE_BASE: case TYPTYPE_BASE:
case TYPTYPE_DOMAIN:
case TYPTYPE_ENUM: case TYPTYPE_ENUM:
case TYPTYPE_RANGE: case TYPTYPE_RANGE:
return TYPEFUNC_SCALAR; return TYPEFUNC_SCALAR;
case TYPTYPE_DOMAIN:
*base_typeid = typid = getBaseType(typid);
if (get_typtype(typid) == TYPTYPE_COMPOSITE)
return TYPEFUNC_COMPOSITE_DOMAIN;
else /* domain base type can't be a pseudotype */
return TYPEFUNC_SCALAR;
case TYPTYPE_PSEUDO: case TYPTYPE_PSEUDO:
if (typid == RECORDOID) if (typid == RECORDOID)
return TYPEFUNC_RECORD; return TYPEFUNC_RECORD;
...@@ -1320,16 +1373,20 @@ RelationNameGetTupleDesc(const char *relname) ...@@ -1320,16 +1373,20 @@ RelationNameGetTupleDesc(const char *relname)
TupleDesc TupleDesc
TypeGetTupleDesc(Oid typeoid, List *colaliases) TypeGetTupleDesc(Oid typeoid, List *colaliases)
{ {
TypeFuncClass functypclass = get_type_func_class(typeoid); Oid base_typeoid;
TypeFuncClass functypclass = get_type_func_class(typeoid, &base_typeoid);
TupleDesc tupdesc = NULL; TupleDesc tupdesc = NULL;
/* /*
* Build a suitable tupledesc representing the output rows * Build a suitable tupledesc representing the output rows. We
* intentionally do not support TYPEFUNC_COMPOSITE_DOMAIN here, as it's
* unlikely that legacy callers of this obsolete function would be
* prepared to apply domain constraints.
*/ */
if (functypclass == TYPEFUNC_COMPOSITE) if (functypclass == TYPEFUNC_COMPOSITE)
{ {
/* Composite data type, e.g. a table's row type */ /* Composite data type, e.g. a table's row type */
tupdesc = lookup_rowtype_tupdesc_copy(typeoid, -1); tupdesc = lookup_rowtype_tupdesc_copy(base_typeoid, -1);
if (colaliases != NIL) if (colaliases != NIL)
{ {
...@@ -1424,7 +1481,8 @@ extract_variadic_args(FunctionCallInfo fcinfo, int variadic_start, ...@@ -1424,7 +1481,8 @@ extract_variadic_args(FunctionCallInfo fcinfo, int variadic_start,
Datum *args_res; Datum *args_res;
bool *nulls_res; bool *nulls_res;
Oid *types_res; Oid *types_res;
int nargs, i; int nargs,
i;
*args = NULL; *args = NULL;
*types = NULL; *types = NULL;
...@@ -1460,7 +1518,7 @@ extract_variadic_args(FunctionCallInfo fcinfo, int variadic_start, ...@@ -1460,7 +1518,7 @@ extract_variadic_args(FunctionCallInfo fcinfo, int variadic_start,
else else
{ {
nargs = PG_NARGS() - variadic_start; nargs = PG_NARGS() - variadic_start;
Assert (nargs > 0); Assert(nargs > 0);
nulls_res = (bool *) palloc0(nargs * sizeof(bool)); nulls_res = (bool *) palloc0(nargs * sizeof(bool));
args_res = (Datum *) palloc0(nargs * sizeof(Datum)); args_res = (Datum *) palloc0(nargs * sizeof(Datum));
types_res = (Oid *) palloc0(nargs * sizeof(Oid)); types_res = (Oid *) palloc0(nargs * sizeof(Oid));
...@@ -1473,11 +1531,10 @@ extract_variadic_args(FunctionCallInfo fcinfo, int variadic_start, ...@@ -1473,11 +1531,10 @@ extract_variadic_args(FunctionCallInfo fcinfo, int variadic_start,
/* /*
* Turn a constant (more or less literal) value that's of unknown * Turn a constant (more or less literal) value that's of unknown
* type into text if required . Unknowns come in as a cstring * type into text if required. Unknowns come in as a cstring
* pointer. * pointer. Note: for functions declared as taking type "any", the
* Note: for functions declared as taking type "any", the parser * parser will not do any type conversion on unknown-type literals
* will not do any type conversion on unknown-type literals (that * (that is, undecorated strings or NULLs).
* is, undecorated strings or NULLs).
*/ */
if (convert_unknown && if (convert_unknown &&
types_res[i] == UNKNOWNOID && types_res[i] == UNKNOWNOID &&
......
...@@ -134,6 +134,11 @@ typedef struct DatumTupleFields ...@@ -134,6 +134,11 @@ typedef struct DatumTupleFields
Oid datum_typeid; /* composite type OID, or RECORDOID */ Oid datum_typeid; /* composite type OID, or RECORDOID */
/* /*
* datum_typeid cannot be a domain over composite, only plain composite,
* even if the datum is meant as a value of a domain-over-composite type.
* This is in line with the general principle that CoerceToDomain does not
* change the physical representation of the base type value.
*
* Note: field ordering is chosen with thought that Oid might someday * Note: field ordering is chosen with thought that Oid might someday
* widen to 64 bits. * widen to 64 bits.
*/ */
......
...@@ -60,6 +60,12 @@ typedef struct tupleConstr ...@@ -60,6 +60,12 @@ typedef struct tupleConstr
* row type, or a value >= 0 to allow the rowtype to be looked up in the * row type, or a value >= 0 to allow the rowtype to be looked up in the
* typcache.c type cache. * typcache.c type cache.
* *
* Note that tdtypeid is never the OID of a domain over composite, even if
* we are dealing with values that are known (at some higher level) to be of
* a domain-over-composite type. This is because tdtypeid/tdtypmod need to
* match up with the type labeling of composite Datums, and those are never
* explicitly marked as being of a domain type, either.
*
* Tuple descriptors that live in caches (relcache or typcache, at present) * Tuple descriptors that live in caches (relcache or typcache, at present)
* are reference-counted: they can be deleted when their reference count goes * are reference-counted: they can be deleted when their reference count goes
* to zero. Tuple descriptors created by the executor need no reference * to zero. Tuple descriptors created by the executor need no reference
......
...@@ -144,6 +144,10 @@ typedef struct FuncCallContext ...@@ -144,6 +144,10 @@ typedef struct FuncCallContext
* get_call_result_type. Note: the cases in which rowtypes cannot be * get_call_result_type. Note: the cases in which rowtypes cannot be
* determined are different from the cases for get_call_result_type. * determined are different from the cases for get_call_result_type.
* Do *not* use this if you can use one of the others. * Do *not* use this if you can use one of the others.
*
* See also get_expr_result_tupdesc(), which is a convenient wrapper around
* get_expr_result_type() for use when the caller only cares about
* determinable-rowtype cases.
*---------- *----------
*/ */
...@@ -152,6 +156,7 @@ typedef enum TypeFuncClass ...@@ -152,6 +156,7 @@ typedef enum TypeFuncClass
{ {
TYPEFUNC_SCALAR, /* scalar result type */ TYPEFUNC_SCALAR, /* scalar result type */
TYPEFUNC_COMPOSITE, /* determinable rowtype result */ TYPEFUNC_COMPOSITE, /* determinable rowtype result */
TYPEFUNC_COMPOSITE_DOMAIN, /* domain over determinable rowtype result */
TYPEFUNC_RECORD, /* indeterminate rowtype result */ TYPEFUNC_RECORD, /* indeterminate rowtype result */
TYPEFUNC_OTHER /* bogus type, eg pseudotype */ TYPEFUNC_OTHER /* bogus type, eg pseudotype */
} TypeFuncClass; } TypeFuncClass;
...@@ -166,6 +171,8 @@ extern TypeFuncClass get_func_result_type(Oid functionId, ...@@ -166,6 +171,8 @@ extern TypeFuncClass get_func_result_type(Oid functionId,
Oid *resultTypeId, Oid *resultTypeId,
TupleDesc *resultTupleDesc); TupleDesc *resultTupleDesc);
extern TupleDesc get_expr_result_tupdesc(Node *expr, bool noError);
extern bool resolve_polymorphic_argtypes(int numargs, Oid *argtypes, extern bool resolve_polymorphic_argtypes(int numargs, Oid *argtypes,
char *argmodes, char *argmodes,
Node *call_expr); Node *call_expr);
......
...@@ -166,7 +166,7 @@ typedef struct Var ...@@ -166,7 +166,7 @@ typedef struct Var
Index varno; /* index of this var's relation in the range Index varno; /* index of this var's relation in the range
* table, or INNER_VAR/OUTER_VAR/INDEX_VAR */ * table, or INNER_VAR/OUTER_VAR/INDEX_VAR */
AttrNumber varattno; /* attribute number of this var, or zero for AttrNumber varattno; /* attribute number of this var, or zero for
* all */ * all attrs ("whole-row Var") */
Oid vartype; /* pg_type OID for the type of this var */ Oid vartype; /* pg_type OID for the type of this var */
int32 vartypmod; /* pg_attribute typmod value */ int32 vartypmod; /* pg_attribute typmod value */
Oid varcollid; /* OID of collation, or InvalidOid if none */ Oid varcollid; /* OID of collation, or InvalidOid if none */
...@@ -755,6 +755,9 @@ typedef struct FieldSelect ...@@ -755,6 +755,9 @@ typedef struct FieldSelect
* the assign case of ArrayRef, this is used to implement UPDATE of a * the assign case of ArrayRef, this is used to implement UPDATE of a
* portion of a column. * portion of a column.
* *
* resulttype is always a named composite type (not a domain). To update
* a composite domain value, apply CoerceToDomain to the FieldStore.
*
* A single FieldStore can actually represent updates of several different * A single FieldStore can actually represent updates of several different
* fields. The parser only generates FieldStores with single-element lists, * fields. The parser only generates FieldStores with single-element lists,
* but the planner will collapse multiple updates of the same base column * but the planner will collapse multiple updates of the same base column
...@@ -849,7 +852,8 @@ typedef struct ArrayCoerceExpr ...@@ -849,7 +852,8 @@ typedef struct ArrayCoerceExpr
* needed for the destination type plus possibly others; the columns need not * needed for the destination type plus possibly others; the columns need not
* be in the same positions, but are matched up by name. This is primarily * be in the same positions, but are matched up by name. This is primarily
* used to convert a whole-row value of an inheritance child table into a * used to convert a whole-row value of an inheritance child table into a
* valid whole-row value of its parent table's rowtype. * valid whole-row value of its parent table's rowtype. Both resulttype
* and the exposed type of "arg" must be named composite types (not domains).
* ---------------- * ----------------
*/ */
...@@ -987,6 +991,9 @@ typedef struct RowExpr ...@@ -987,6 +991,9 @@ typedef struct RowExpr
Oid row_typeid; /* RECORDOID or a composite type's ID */ Oid row_typeid; /* RECORDOID or a composite type's ID */
/* /*
* row_typeid cannot be a domain over composite, only plain composite. To
* create a composite domain value, apply CoerceToDomain to the RowExpr.
*
* Note: we deliberately do NOT store a typmod. Although a typmod will be * Note: we deliberately do NOT store a typmod. Although a typmod will be
* associated with specific RECORD types at runtime, it will differ for * associated with specific RECORD types at runtime, it will differ for
* different backends, and so cannot safely be stored in stored * different backends, and so cannot safely be stored in stored
......
...@@ -46,10 +46,12 @@ extern Oid typeTypeCollation(Type typ); ...@@ -46,10 +46,12 @@ extern Oid typeTypeCollation(Type typ);
extern Datum stringTypeDatum(Type tp, char *string, int32 atttypmod); extern Datum stringTypeDatum(Type tp, char *string, int32 atttypmod);
extern Oid typeidTypeRelid(Oid type_id); extern Oid typeidTypeRelid(Oid type_id);
extern Oid typeOrDomainTypeRelid(Oid type_id);
extern TypeName *typeStringToTypeName(const char *str); extern TypeName *typeStringToTypeName(const char *str);
extern void parseTypeString(const char *str, Oid *typeid_p, int32 *typmod_p, bool missing_ok); extern void parseTypeString(const char *str, Oid *typeid_p, int32 *typmod_p, bool missing_ok);
#define ISCOMPLEX(typeid) (typeidTypeRelid(typeid) != InvalidOid) /* true if typeid is composite, or domain over composite, but not RECORD */
#define ISCOMPLEX(typeid) (typeOrDomainTypeRelid(typeid) != InvalidOid)
#endif /* PARSE_TYPE_H */ #endif /* PARSE_TYPE_H */
...@@ -91,6 +91,13 @@ typedef struct TypeCacheEntry ...@@ -91,6 +91,13 @@ typedef struct TypeCacheEntry
FmgrInfo rng_canonical_finfo; /* canonicalization function, if any */ FmgrInfo rng_canonical_finfo; /* canonicalization function, if any */
FmgrInfo rng_subdiff_finfo; /* difference function, if any */ FmgrInfo rng_subdiff_finfo; /* difference function, if any */
/*
* Domain's base type and typmod if it's a domain type. Zeroes if not
* domain, or if information hasn't been requested.
*/
Oid domainBaseType;
int32 domainBaseTypmod;
/* /*
* Domain constraint data if it's a domain type. NULL if not domain, or * Domain constraint data if it's a domain type. NULL if not domain, or
* if domain has no constraints, or if information hasn't been requested. * if domain has no constraints, or if information hasn't been requested.
...@@ -123,9 +130,10 @@ typedef struct TypeCacheEntry ...@@ -123,9 +130,10 @@ typedef struct TypeCacheEntry
#define TYPECACHE_BTREE_OPFAMILY 0x0200 #define TYPECACHE_BTREE_OPFAMILY 0x0200
#define TYPECACHE_HASH_OPFAMILY 0x0400 #define TYPECACHE_HASH_OPFAMILY 0x0400
#define TYPECACHE_RANGE_INFO 0x0800 #define TYPECACHE_RANGE_INFO 0x0800
#define TYPECACHE_DOMAIN_INFO 0x1000 #define TYPECACHE_DOMAIN_BASE_INFO 0x1000
#define TYPECACHE_HASH_EXTENDED_PROC 0x2000 #define TYPECACHE_DOMAIN_CONSTR_INFO 0x2000
#define TYPECACHE_HASH_EXTENDED_PROC_FINFO 0x4000 #define TYPECACHE_HASH_EXTENDED_PROC 0x4000
#define TYPECACHE_HASH_EXTENDED_PROC_FINFO 0x8000
/* /*
* Callers wishing to maintain a long-lived reference to a domain's constraint * Callers wishing to maintain a long-lived reference to a domain's constraint
...@@ -163,6 +171,9 @@ extern TupleDesc lookup_rowtype_tupdesc_noerror(Oid type_id, int32 typmod, ...@@ -163,6 +171,9 @@ extern TupleDesc lookup_rowtype_tupdesc_noerror(Oid type_id, int32 typmod,
extern TupleDesc lookup_rowtype_tupdesc_copy(Oid type_id, int32 typmod); extern TupleDesc lookup_rowtype_tupdesc_copy(Oid type_id, int32 typmod);
extern TupleDesc lookup_rowtype_tupdesc_domain(Oid type_id, int32 typmod,
bool noError);
extern void assign_record_type_typmod(TupleDesc tupDesc); extern void assign_record_type_typmod(TupleDesc tupDesc);
extern int compare_values_of_enum(TypeCacheEntry *tcache, Oid arg1, Oid arg2); extern int compare_values_of_enum(TypeCacheEntry *tcache, Oid arg1, Oid arg2);
......
...@@ -198,6 +198,94 @@ select pg_typeof('{1,2,3}'::dia || 42); -- should be int[] not dia ...@@ -198,6 +198,94 @@ select pg_typeof('{1,2,3}'::dia || 42); -- should be int[] not dia
(1 row) (1 row)
drop domain dia; drop domain dia;
-- Test domains over composites
create type comptype as (r float8, i float8);
create domain dcomptype as comptype;
create table dcomptable (d1 dcomptype unique);
insert into dcomptable values (row(1,2)::dcomptype);
insert into dcomptable values (row(3,4)::comptype);
insert into dcomptable values (row(1,2)::dcomptype); -- fail on uniqueness
ERROR: duplicate key value violates unique constraint "dcomptable_d1_key"
DETAIL: Key (d1)=((1,2)) already exists.
insert into dcomptable (d1.r) values(11);
select * from dcomptable;
d1
-------
(1,2)
(3,4)
(11,)
(3 rows)
select (d1).r, (d1).i, (d1).* from dcomptable;
r | i | r | i
----+---+----+---
1 | 2 | 1 | 2
3 | 4 | 3 | 4
11 | | 11 |
(3 rows)
update dcomptable set d1.r = (d1).r + 1 where (d1).i > 0;
select * from dcomptable;
d1
-------
(11,)
(2,2)
(4,4)
(3 rows)
alter domain dcomptype add constraint c1 check ((value).r <= (value).i);
alter domain dcomptype add constraint c2 check ((value).r > (value).i); -- fail
ERROR: column "d1" of table "dcomptable" contains values that violate the new constraint
select row(2,1)::dcomptype; -- fail
ERROR: value for domain dcomptype violates check constraint "c1"
insert into dcomptable values (row(1,2)::comptype);
insert into dcomptable values (row(2,1)::comptype); -- fail
ERROR: value for domain dcomptype violates check constraint "c1"
insert into dcomptable (d1.r) values(99);
insert into dcomptable (d1.r, d1.i) values(99, 100);
insert into dcomptable (d1.r, d1.i) values(100, 99); -- fail
ERROR: value for domain dcomptype violates check constraint "c1"
update dcomptable set d1.r = (d1).r + 1 where (d1).i > 0; -- fail
ERROR: value for domain dcomptype violates check constraint "c1"
update dcomptable set d1.r = (d1).r - 1, d1.i = (d1).i + 1 where (d1).i > 0;
select * from dcomptable;
d1
----------
(11,)
(99,)
(1,3)
(3,5)
(0,3)
(98,101)
(6 rows)
explain (verbose, costs off)
update dcomptable set d1.r = (d1).r - 1, d1.i = (d1).i + 1 where (d1).i > 0;
QUERY PLAN
-----------------------------------------------------------------------------------------------
Update on public.dcomptable
-> Seq Scan on public.dcomptable
Output: ROW(((d1).r - '1'::double precision), ((d1).i + '1'::double precision)), ctid
Filter: ((dcomptable.d1).i > '0'::double precision)
(4 rows)
create rule silly as on delete to dcomptable do instead
update dcomptable set d1.r = (d1).r - 1, d1.i = (d1).i + 1 where (d1).i > 0;
\d+ dcomptable
Table "public.dcomptable"
Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
--------+-----------+-----------+----------+---------+----------+--------------+-------------
d1 | dcomptype | | | | extended | |
Indexes:
"dcomptable_d1_key" UNIQUE CONSTRAINT, btree (d1)
Rules:
silly AS
ON DELETE TO dcomptable DO INSTEAD UPDATE dcomptable SET d1.r = (dcomptable.d1).r - 1::double precision, d1.i = (dcomptable.d1).i + 1::double precision
WHERE (dcomptable.d1).i > 0::double precision
drop table dcomptable;
drop type comptype cascade;
NOTICE: drop cascades to type dcomptype
-- Test domains over arrays of composite -- Test domains over arrays of composite
create type comptype as (r float8, i float8); create type comptype as (r float8, i float8);
create domain dcomptypea as comptype[]; create domain dcomptypea as comptype[];
...@@ -762,6 +850,14 @@ insert into ddtest2 values('{(-1)}'); ...@@ -762,6 +850,14 @@ insert into ddtest2 values('{(-1)}');
alter domain posint add constraint c1 check(value >= 0); alter domain posint add constraint c1 check(value >= 0);
ERROR: cannot alter type "posint" because column "ddtest2.f1" uses it ERROR: cannot alter type "posint" because column "ddtest2.f1" uses it
drop table ddtest2; drop table ddtest2;
-- Likewise for domains within domains over composite
create domain ddtest1d as ddtest1;
create table ddtest2(f1 ddtest1d);
insert into ddtest2 values('(-1)');
alter domain posint add constraint c1 check(value >= 0);
ERROR: cannot alter type "posint" because column "ddtest2.f1" uses it
drop table ddtest2;
drop domain ddtest1d;
-- Likewise for domains within domains over array of composite -- Likewise for domains within domains over array of composite
create domain ddtest1d as ddtest1[]; create domain ddtest1d as ddtest1[];
create table ddtest2(f1 ddtest1d); create table ddtest2(f1 ddtest1d);
......
...@@ -1316,6 +1316,8 @@ create type jpop as (a text, b int, c timestamp); ...@@ -1316,6 +1316,8 @@ create type jpop as (a text, b int, c timestamp);
CREATE DOMAIN js_int_not_null AS int NOT NULL; CREATE DOMAIN js_int_not_null AS int NOT NULL;
CREATE DOMAIN js_int_array_1d AS int[] CHECK(array_length(VALUE, 1) = 3); CREATE DOMAIN js_int_array_1d AS int[] CHECK(array_length(VALUE, 1) = 3);
CREATE DOMAIN js_int_array_2d AS int[][] CHECK(array_length(VALUE, 2) = 3); CREATE DOMAIN js_int_array_2d AS int[][] CHECK(array_length(VALUE, 2) = 3);
create type j_unordered_pair as (x int, y int);
create domain j_ordered_pair as j_unordered_pair check((value).x <= (value).y);
CREATE TYPE jsrec AS ( CREATE TYPE jsrec AS (
i int, i int,
ia _int4, ia _int4,
...@@ -1740,6 +1742,30 @@ SELECT rec FROM json_populate_record( ...@@ -1740,6 +1742,30 @@ SELECT rec FROM json_populate_record(
(abc,3,"Thu Jan 02 00:00:00 2003") (abc,3,"Thu Jan 02 00:00:00 2003")
(1 row) (1 row)
-- anonymous record type
SELECT json_populate_record(null::record, '{"x": 0, "y": 1}');
ERROR: record type has not been registered
SELECT json_populate_record(row(1,2), '{"f1": 0, "f2": 1}');
json_populate_record
----------------------
(0,1)
(1 row)
-- composite domain
SELECT json_populate_record(null::j_ordered_pair, '{"x": 0, "y": 1}');
json_populate_record
----------------------
(0,1)
(1 row)
SELECT json_populate_record(row(1,2)::j_ordered_pair, '{"x": 0}');
json_populate_record
----------------------
(0,2)
(1 row)
SELECT json_populate_record(row(1,2)::j_ordered_pair, '{"x": 1, "y": 0}');
ERROR: value for domain j_ordered_pair violates check constraint "j_ordered_pair_check"
-- populate_recordset -- populate_recordset
select * from json_populate_recordset(null::jpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]') q; select * from json_populate_recordset(null::jpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]') q;
a | b | c a | b | c
...@@ -1806,6 +1832,31 @@ select * from json_populate_recordset(row('def',99,null)::jpop,'[{"a":[100,200,3 ...@@ -1806,6 +1832,31 @@ select * from json_populate_recordset(row('def',99,null)::jpop,'[{"a":[100,200,3
{"z":true} | 3 | Fri Jan 20 10:42:53 2012 {"z":true} | 3 | Fri Jan 20 10:42:53 2012
(2 rows) (2 rows)
-- anonymous record type
SELECT json_populate_recordset(null::record, '[{"x": 0, "y": 1}]');
ERROR: record type has not been registered
SELECT json_populate_recordset(row(1,2), '[{"f1": 0, "f2": 1}]');
json_populate_recordset
-------------------------
(0,1)
(1 row)
-- composite domain
SELECT json_populate_recordset(null::j_ordered_pair, '[{"x": 0, "y": 1}]');
json_populate_recordset
-------------------------
(0,1)
(1 row)
SELECT json_populate_recordset(row(1,2)::j_ordered_pair, '[{"x": 0}, {"y": 3}]');
json_populate_recordset
-------------------------
(0,2)
(1,3)
(2 rows)
SELECT json_populate_recordset(row(1,2)::j_ordered_pair, '[{"x": 1, "y": 0}]');
ERROR: value for domain j_ordered_pair violates check constraint "j_ordered_pair_check"
-- test type info caching in json_populate_record() -- test type info caching in json_populate_record()
CREATE TEMP TABLE jspoptest (js json); CREATE TEMP TABLE jspoptest (js json);
INSERT INTO jspoptest INSERT INTO jspoptest
...@@ -1828,6 +1879,8 @@ DROP TYPE jsrec_i_not_null; ...@@ -1828,6 +1879,8 @@ DROP TYPE jsrec_i_not_null;
DROP DOMAIN js_int_not_null; DROP DOMAIN js_int_not_null;
DROP DOMAIN js_int_array_1d; DROP DOMAIN js_int_array_1d;
DROP DOMAIN js_int_array_2d; DROP DOMAIN js_int_array_2d;
DROP DOMAIN j_ordered_pair;
DROP TYPE j_unordered_pair;
--json_typeof() function --json_typeof() function
select value, json_typeof(value) select value, json_typeof(value)
from (values (json '123.4'), from (values (json '123.4'),
......
...@@ -2005,6 +2005,8 @@ CREATE TYPE jbpop AS (a text, b int, c timestamp); ...@@ -2005,6 +2005,8 @@ CREATE TYPE jbpop AS (a text, b int, c timestamp);
CREATE DOMAIN jsb_int_not_null AS int NOT NULL; CREATE DOMAIN jsb_int_not_null AS int NOT NULL;
CREATE DOMAIN jsb_int_array_1d AS int[] CHECK(array_length(VALUE, 1) = 3); CREATE DOMAIN jsb_int_array_1d AS int[] CHECK(array_length(VALUE, 1) = 3);
CREATE DOMAIN jsb_int_array_2d AS int[][] CHECK(array_length(VALUE, 2) = 3); CREATE DOMAIN jsb_int_array_2d AS int[][] CHECK(array_length(VALUE, 2) = 3);
create type jb_unordered_pair as (x int, y int);
create domain jb_ordered_pair as jb_unordered_pair check((value).x <= (value).y);
CREATE TYPE jsbrec AS ( CREATE TYPE jsbrec AS (
i int, i int,
ia _int4, ia _int4,
...@@ -2429,6 +2431,30 @@ SELECT rec FROM jsonb_populate_record( ...@@ -2429,6 +2431,30 @@ SELECT rec FROM jsonb_populate_record(
(abc,3,"Thu Jan 02 00:00:00 2003") (abc,3,"Thu Jan 02 00:00:00 2003")
(1 row) (1 row)
-- anonymous record type
SELECT jsonb_populate_record(null::record, '{"x": 0, "y": 1}');
ERROR: record type has not been registered
SELECT jsonb_populate_record(row(1,2), '{"f1": 0, "f2": 1}');
jsonb_populate_record
-----------------------
(0,1)
(1 row)
-- composite domain
SELECT jsonb_populate_record(null::jb_ordered_pair, '{"x": 0, "y": 1}');
jsonb_populate_record
-----------------------
(0,1)
(1 row)
SELECT jsonb_populate_record(row(1,2)::jb_ordered_pair, '{"x": 0}');
jsonb_populate_record
-----------------------
(0,2)
(1 row)
SELECT jsonb_populate_record(row(1,2)::jb_ordered_pair, '{"x": 1, "y": 0}');
ERROR: value for domain jb_ordered_pair violates check constraint "jb_ordered_pair_check"
-- populate_recordset -- populate_recordset
SELECT * FROM jsonb_populate_recordset(NULL::jbpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]') q; SELECT * FROM jsonb_populate_recordset(NULL::jbpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]') q;
a | b | c a | b | c
...@@ -2488,6 +2514,31 @@ SELECT * FROM jsonb_populate_recordset(row('def',99,NULL)::jbpop,'[{"a":[100,200 ...@@ -2488,6 +2514,31 @@ SELECT * FROM jsonb_populate_recordset(row('def',99,NULL)::jbpop,'[{"a":[100,200
{"z": true} | 3 | Fri Jan 20 10:42:53 2012 {"z": true} | 3 | Fri Jan 20 10:42:53 2012
(2 rows) (2 rows)
-- anonymous record type
SELECT jsonb_populate_recordset(null::record, '[{"x": 0, "y": 1}]');
ERROR: record type has not been registered
SELECT jsonb_populate_recordset(row(1,2), '[{"f1": 0, "f2": 1}]');
jsonb_populate_recordset
--------------------------
(0,1)
(1 row)
-- composite domain
SELECT jsonb_populate_recordset(null::jb_ordered_pair, '[{"x": 0, "y": 1}]');
jsonb_populate_recordset
--------------------------
(0,1)
(1 row)
SELECT jsonb_populate_recordset(row(1,2)::jb_ordered_pair, '[{"x": 0}, {"y": 3}]');
jsonb_populate_recordset
--------------------------
(0,2)
(1,3)
(2 rows)
SELECT jsonb_populate_recordset(row(1,2)::jb_ordered_pair, '[{"x": 1, "y": 0}]');
ERROR: value for domain jb_ordered_pair violates check constraint "jb_ordered_pair_check"
-- jsonb_to_record and jsonb_to_recordset -- jsonb_to_record and jsonb_to_recordset
select * from jsonb_to_record('{"a":1,"b":"foo","c":"bar"}') select * from jsonb_to_record('{"a":1,"b":"foo","c":"bar"}')
as x(a int, b text, d text); as x(a int, b text, d text);
...@@ -2587,6 +2638,8 @@ DROP TYPE jsbrec_i_not_null; ...@@ -2587,6 +2638,8 @@ DROP TYPE jsbrec_i_not_null;
DROP DOMAIN jsb_int_not_null; DROP DOMAIN jsb_int_not_null;
DROP DOMAIN jsb_int_array_1d; DROP DOMAIN jsb_int_array_1d;
DROP DOMAIN jsb_int_array_2d; DROP DOMAIN jsb_int_array_2d;
DROP DOMAIN jb_ordered_pair;
DROP TYPE jb_unordered_pair;
-- indexing -- indexing
SELECT count(*) FROM testjsonb WHERE j @> '{"wait":null}'; SELECT count(*) FROM testjsonb WHERE j @> '{"wait":null}';
count count
......
...@@ -120,6 +120,45 @@ select pg_typeof('{1,2,3}'::dia || 42); -- should be int[] not dia ...@@ -120,6 +120,45 @@ select pg_typeof('{1,2,3}'::dia || 42); -- should be int[] not dia
drop domain dia; drop domain dia;
-- Test domains over composites
create type comptype as (r float8, i float8);
create domain dcomptype as comptype;
create table dcomptable (d1 dcomptype unique);
insert into dcomptable values (row(1,2)::dcomptype);
insert into dcomptable values (row(3,4)::comptype);
insert into dcomptable values (row(1,2)::dcomptype); -- fail on uniqueness
insert into dcomptable (d1.r) values(11);
select * from dcomptable;
select (d1).r, (d1).i, (d1).* from dcomptable;
update dcomptable set d1.r = (d1).r + 1 where (d1).i > 0;
select * from dcomptable;
alter domain dcomptype add constraint c1 check ((value).r <= (value).i);
alter domain dcomptype add constraint c2 check ((value).r > (value).i); -- fail
select row(2,1)::dcomptype; -- fail
insert into dcomptable values (row(1,2)::comptype);
insert into dcomptable values (row(2,1)::comptype); -- fail
insert into dcomptable (d1.r) values(99);
insert into dcomptable (d1.r, d1.i) values(99, 100);
insert into dcomptable (d1.r, d1.i) values(100, 99); -- fail
update dcomptable set d1.r = (d1).r + 1 where (d1).i > 0; -- fail
update dcomptable set d1.r = (d1).r - 1, d1.i = (d1).i + 1 where (d1).i > 0;
select * from dcomptable;
explain (verbose, costs off)
update dcomptable set d1.r = (d1).r - 1, d1.i = (d1).i + 1 where (d1).i > 0;
create rule silly as on delete to dcomptable do instead
update dcomptable set d1.r = (d1).r - 1, d1.i = (d1).i + 1 where (d1).i > 0;
\d+ dcomptable
drop table dcomptable;
drop type comptype cascade;
-- Test domains over arrays of composite -- Test domains over arrays of composite
create type comptype as (r float8, i float8); create type comptype as (r float8, i float8);
...@@ -500,6 +539,14 @@ insert into ddtest2 values('{(-1)}'); ...@@ -500,6 +539,14 @@ insert into ddtest2 values('{(-1)}');
alter domain posint add constraint c1 check(value >= 0); alter domain posint add constraint c1 check(value >= 0);
drop table ddtest2; drop table ddtest2;
-- Likewise for domains within domains over composite
create domain ddtest1d as ddtest1;
create table ddtest2(f1 ddtest1d);
insert into ddtest2 values('(-1)');
alter domain posint add constraint c1 check(value >= 0);
drop table ddtest2;
drop domain ddtest1d;
-- Likewise for domains within domains over array of composite -- Likewise for domains within domains over array of composite
create domain ddtest1d as ddtest1[]; create domain ddtest1d as ddtest1[];
create table ddtest2(f1 ddtest1d); create table ddtest2(f1 ddtest1d);
......
...@@ -388,6 +388,9 @@ CREATE DOMAIN js_int_not_null AS int NOT NULL; ...@@ -388,6 +388,9 @@ CREATE DOMAIN js_int_not_null AS int NOT NULL;
CREATE DOMAIN js_int_array_1d AS int[] CHECK(array_length(VALUE, 1) = 3); CREATE DOMAIN js_int_array_1d AS int[] CHECK(array_length(VALUE, 1) = 3);
CREATE DOMAIN js_int_array_2d AS int[][] CHECK(array_length(VALUE, 2) = 3); CREATE DOMAIN js_int_array_2d AS int[][] CHECK(array_length(VALUE, 2) = 3);
create type j_unordered_pair as (x int, y int);
create domain j_ordered_pair as j_unordered_pair check((value).x <= (value).y);
CREATE TYPE jsrec AS ( CREATE TYPE jsrec AS (
i int, i int,
ia _int4, ia _int4,
...@@ -516,6 +519,15 @@ SELECT rec FROM json_populate_record( ...@@ -516,6 +519,15 @@ SELECT rec FROM json_populate_record(
'{"rec": {"a": "abc", "c": "01.02.2003", "x": 43.2}}' '{"rec": {"a": "abc", "c": "01.02.2003", "x": 43.2}}'
) q; ) q;
-- anonymous record type
SELECT json_populate_record(null::record, '{"x": 0, "y": 1}');
SELECT json_populate_record(row(1,2), '{"f1": 0, "f2": 1}');
-- composite domain
SELECT json_populate_record(null::j_ordered_pair, '{"x": 0, "y": 1}');
SELECT json_populate_record(row(1,2)::j_ordered_pair, '{"x": 0}');
SELECT json_populate_record(row(1,2)::j_ordered_pair, '{"x": 1, "y": 0}');
-- populate_recordset -- populate_recordset
select * from json_populate_recordset(null::jpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]') q; select * from json_populate_recordset(null::jpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]') q;
...@@ -532,6 +544,15 @@ select * from json_populate_recordset(null::jpop,'[{"a":"blurfl","x":43.2},{"b": ...@@ -532,6 +544,15 @@ select * from json_populate_recordset(null::jpop,'[{"a":"blurfl","x":43.2},{"b":
select * from json_populate_recordset(row('def',99,null)::jpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]') q; select * from json_populate_recordset(row('def',99,null)::jpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]') q;
select * from json_populate_recordset(row('def',99,null)::jpop,'[{"a":[100,200,300],"x":43.2},{"a":{"z":true},"b":3,"c":"2012-01-20 10:42:53"}]') q; select * from json_populate_recordset(row('def',99,null)::jpop,'[{"a":[100,200,300],"x":43.2},{"a":{"z":true},"b":3,"c":"2012-01-20 10:42:53"}]') q;
-- anonymous record type
SELECT json_populate_recordset(null::record, '[{"x": 0, "y": 1}]');
SELECT json_populate_recordset(row(1,2), '[{"f1": 0, "f2": 1}]');
-- composite domain
SELECT json_populate_recordset(null::j_ordered_pair, '[{"x": 0, "y": 1}]');
SELECT json_populate_recordset(row(1,2)::j_ordered_pair, '[{"x": 0}, {"y": 3}]');
SELECT json_populate_recordset(row(1,2)::j_ordered_pair, '[{"x": 1, "y": 0}]');
-- test type info caching in json_populate_record() -- test type info caching in json_populate_record()
CREATE TEMP TABLE jspoptest (js json); CREATE TEMP TABLE jspoptest (js json);
...@@ -550,6 +571,8 @@ DROP TYPE jsrec_i_not_null; ...@@ -550,6 +571,8 @@ DROP TYPE jsrec_i_not_null;
DROP DOMAIN js_int_not_null; DROP DOMAIN js_int_not_null;
DROP DOMAIN js_int_array_1d; DROP DOMAIN js_int_array_1d;
DROP DOMAIN js_int_array_2d; DROP DOMAIN js_int_array_2d;
DROP DOMAIN j_ordered_pair;
DROP TYPE j_unordered_pair;
--json_typeof() function --json_typeof() function
select value, json_typeof(value) select value, json_typeof(value)
......
...@@ -508,6 +508,9 @@ CREATE DOMAIN jsb_int_not_null AS int NOT NULL; ...@@ -508,6 +508,9 @@ CREATE DOMAIN jsb_int_not_null AS int NOT NULL;
CREATE DOMAIN jsb_int_array_1d AS int[] CHECK(array_length(VALUE, 1) = 3); CREATE DOMAIN jsb_int_array_1d AS int[] CHECK(array_length(VALUE, 1) = 3);
CREATE DOMAIN jsb_int_array_2d AS int[][] CHECK(array_length(VALUE, 2) = 3); CREATE DOMAIN jsb_int_array_2d AS int[][] CHECK(array_length(VALUE, 2) = 3);
create type jb_unordered_pair as (x int, y int);
create domain jb_ordered_pair as jb_unordered_pair check((value).x <= (value).y);
CREATE TYPE jsbrec AS ( CREATE TYPE jsbrec AS (
i int, i int,
ia _int4, ia _int4,
...@@ -636,6 +639,15 @@ SELECT rec FROM jsonb_populate_record( ...@@ -636,6 +639,15 @@ SELECT rec FROM jsonb_populate_record(
'{"rec": {"a": "abc", "c": "01.02.2003", "x": 43.2}}' '{"rec": {"a": "abc", "c": "01.02.2003", "x": 43.2}}'
) q; ) q;
-- anonymous record type
SELECT jsonb_populate_record(null::record, '{"x": 0, "y": 1}');
SELECT jsonb_populate_record(row(1,2), '{"f1": 0, "f2": 1}');
-- composite domain
SELECT jsonb_populate_record(null::jb_ordered_pair, '{"x": 0, "y": 1}');
SELECT jsonb_populate_record(row(1,2)::jb_ordered_pair, '{"x": 0}');
SELECT jsonb_populate_record(row(1,2)::jb_ordered_pair, '{"x": 1, "y": 0}');
-- populate_recordset -- populate_recordset
SELECT * FROM jsonb_populate_recordset(NULL::jbpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]') q; SELECT * FROM jsonb_populate_recordset(NULL::jbpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]') q;
SELECT * FROM jsonb_populate_recordset(row('def',99,NULL)::jbpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]') q; SELECT * FROM jsonb_populate_recordset(row('def',99,NULL)::jbpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]') q;
...@@ -648,6 +660,15 @@ SELECT * FROM jsonb_populate_recordset(NULL::jbpop,'[{"a":"blurfl","x":43.2},{"b ...@@ -648,6 +660,15 @@ SELECT * FROM jsonb_populate_recordset(NULL::jbpop,'[{"a":"blurfl","x":43.2},{"b
SELECT * FROM jsonb_populate_recordset(row('def',99,NULL)::jbpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]') q; SELECT * FROM jsonb_populate_recordset(row('def',99,NULL)::jbpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]') q;
SELECT * FROM jsonb_populate_recordset(row('def',99,NULL)::jbpop,'[{"a":[100,200,300],"x":43.2},{"a":{"z":true},"b":3,"c":"2012-01-20 10:42:53"}]') q; SELECT * FROM jsonb_populate_recordset(row('def',99,NULL)::jbpop,'[{"a":[100,200,300],"x":43.2},{"a":{"z":true},"b":3,"c":"2012-01-20 10:42:53"}]') q;
-- anonymous record type
SELECT jsonb_populate_recordset(null::record, '[{"x": 0, "y": 1}]');
SELECT jsonb_populate_recordset(row(1,2), '[{"f1": 0, "f2": 1}]');
-- composite domain
SELECT jsonb_populate_recordset(null::jb_ordered_pair, '[{"x": 0, "y": 1}]');
SELECT jsonb_populate_recordset(row(1,2)::jb_ordered_pair, '[{"x": 0}, {"y": 3}]');
SELECT jsonb_populate_recordset(row(1,2)::jb_ordered_pair, '[{"x": 1, "y": 0}]');
-- jsonb_to_record and jsonb_to_recordset -- jsonb_to_record and jsonb_to_recordset
select * from jsonb_to_record('{"a":1,"b":"foo","c":"bar"}') select * from jsonb_to_record('{"a":1,"b":"foo","c":"bar"}')
...@@ -693,6 +714,8 @@ DROP TYPE jsbrec_i_not_null; ...@@ -693,6 +714,8 @@ DROP TYPE jsbrec_i_not_null;
DROP DOMAIN jsb_int_not_null; DROP DOMAIN jsb_int_not_null;
DROP DOMAIN jsb_int_array_1d; DROP DOMAIN jsb_int_array_1d;
DROP DOMAIN jsb_int_array_2d; DROP DOMAIN jsb_int_array_2d;
DROP DOMAIN jb_ordered_pair;
DROP TYPE jb_unordered_pair;
-- indexing -- indexing
SELECT count(*) FROM testjsonb WHERE j @> '{"wait":null}'; SELECT count(*) FROM testjsonb WHERE j @> '{"wait":null}';
......
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