domains-over-composites-3.patch
text/x-diff
Filename: domains-over-composites-3.patch
Type: text/x-diff
Part: 0
Patch
Format: unified
| File | + | − |
|---|---|---|
| contrib/hstore/hstore_io.c | 0 | 0 |
| doc/src/sgml/datatype.sgml | 0 | 0 |
| doc/src/sgml/rowtypes.sgml | 0 | 0 |
| doc/src/sgml/xfunc.sgml | 0 | 0 |
| src/backend/catalog/pg_inherits.c | 0 | 0 |
| src/backend/catalog/pg_proc.c | 0 | 0 |
| src/backend/commands/tablecmds.c | 0 | 0 |
| src/backend/commands/typecmds.c | 0 | 0 |
| src/backend/executor/execExprInterp.c | 0 | 0 |
| src/backend/executor/execSRF.c | 0 | 0 |
| src/backend/executor/functions.c | 0 | 0 |
| src/backend/executor/nodeFunctionscan.c | 0 | 0 |
| src/backend/nodes/makefuncs.c | 0 | 0 |
| src/backend/optimizer/util/clauses.c | 0 | 0 |
| src/backend/parser/parse_coerce.c | 0 | 0 |
| src/backend/parser/parse_func.c | 0 | 0 |
| src/backend/parser/parse_relation.c | 0 | 0 |
| src/backend/parser/parse_target.c | 0 | 0 |
| src/backend/parser/parse_type.c | 0 | 0 |
| src/backend/utils/adt/domains.c | 0 | 0 |
| src/backend/utils/adt/jsonfuncs.c | 0 | 0 |
| src/backend/utils/adt/ruleutils.c | 0 | 0 |
| src/backend/utils/cache/lsyscache.c | 0 | 0 |
| src/backend/utils/cache/typcache.c | 0 | 0 |
| src/backend/utils/fmgr/funcapi.c | 0 | 0 |
| src/include/access/htup_details.h | 0 | 0 |
| src/include/access/tupdesc.h | 0 | 0 |
| src/include/funcapi.h | 0 | 0 |
| src/include/nodes/primnodes.h | 0 | 0 |
| src/include/parser/parse_type.h | 0 | 0 |
| src/include/utils/typcache.h | 0 | 0 |
| src/test/regress/expected/domain.out | 0 | 0 |
| src/test/regress/expected/jsonb.out | 0 | 0 |
| src/test/regress/expected/json.out | 0 | 0 |
| src/test/regress/sql/domain.sql | 0 | 0 |
| src/test/regress/sql/jsonb.sql | 0 | 0 |
| src/test/regress/sql/json.sql | 0 | 0 |
diff --git a/contrib/hstore/hstore_io.c b/contrib/hstore/hstore_io.c
index d828401..e999a8e 100644
*** a/contrib/hstore/hstore_io.c
--- b/contrib/hstore/hstore_io.c
*************** typedef struct RecordIOData
*** 752,757 ****
--- 752,759 ----
{
Oid record_type;
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;
ColumnIOData columns[FLEXIBLE_ARRAY_MEMBER];
} RecordIOData;
*************** hstore_from_record(PG_FUNCTION_ARGS)
*** 780,788 ****
Oid argtype = get_fn_expr_argtype(fcinfo->flinfo, 0);
/*
! * 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
! * don't have a known composite type oid here.
*/
tupType = argtype;
tupTypmod = -1;
--- 782,792 ----
Oid argtype = get_fn_expr_argtype(fcinfo->flinfo, 0);
/*
! * We have no tuple to look at, so the only source of type info is the
! * argtype --- which might be domain over composite, but we don't care
! * 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;
tupTypmod = -1;
*************** hstore_from_record(PG_FUNCTION_ARGS)
*** 793,804 ****
{
rec = PG_GETARG_HEAPTUPLEHEADER(0);
! /* Extract type info from the tuple itself */
tupType = HeapTupleHeaderGetTypeId(rec);
tupTypmod = HeapTupleHeaderGetTypMod(rec);
}
! tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
ncolumns = tupdesc->natts;
/*
--- 797,811 ----
{
rec = PG_GETARG_HEAPTUPLEHEADER(0);
! /*
! * Extract type info from the tuple itself -- this will work even for
! * anonymous record types.
! */
tupType = HeapTupleHeaderGetTypeId(rec);
tupTypmod = HeapTupleHeaderGetTypMod(rec);
}
! tupdesc = lookup_rowtype_tupdesc_domain(tupType, tupTypmod, false);
ncolumns = tupdesc->natts;
/*
*************** hstore_populate_record(PG_FUNCTION_ARGS)
*** 943,951 ****
rec = NULL;
/*
! * 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
! * don't have a known composite type oid here.
*/
tupType = argtype;
tupTypmod = -1;
--- 950,958 ----
rec = NULL;
/*
! * We have no tuple to look at, so the only source of type info is the
! * argtype. The lookup_rowtype_tupdesc_domain call below will error
! * out if we don't have a known composite type oid here.
*/
tupType = argtype;
tupTypmod = -1;
*************** hstore_populate_record(PG_FUNCTION_ARGS)
*** 957,963 ****
if (PG_ARGISNULL(1))
PG_RETURN_POINTER(rec);
! /* Extract type info from the tuple itself */
tupType = HeapTupleHeaderGetTypeId(rec);
tupTypmod = HeapTupleHeaderGetTypMod(rec);
}
--- 964,973 ----
if (PG_ARGISNULL(1))
PG_RETURN_POINTER(rec);
! /*
! * Extract type info from the tuple itself -- this will work even for
! * anonymous record types.
! */
tupType = HeapTupleHeaderGetTypeId(rec);
tupTypmod = HeapTupleHeaderGetTypMod(rec);
}
*************** hstore_populate_record(PG_FUNCTION_ARGS)
*** 975,981 ****
if (HS_COUNT(hs) == 0 && rec)
PG_RETURN_POINTER(rec);
! tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
ncolumns = tupdesc->natts;
if (rec)
--- 985,995 ----
if (HS_COUNT(hs) == 0 && rec)
PG_RETURN_POINTER(rec);
! /*
! * 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;
if (rec)
*************** hstore_populate_record(PG_FUNCTION_ARGS)
*** 1002,1007 ****
--- 1016,1022 ----
my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
my_extra->record_type = InvalidOid;
my_extra->record_typmod = 0;
+ my_extra->domain_info = NULL;
}
if (my_extra->record_type != tupType ||
*************** hstore_populate_record(PG_FUNCTION_ARGS)
*** 1103,1108 ****
--- 1118,1134 ----
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);
PG_RETURN_DATUM(HeapTupleGetDatum(rettuple));
diff --git a/doc/src/sgml/datatype.sgml b/doc/src/sgml/datatype.sgml
index b397e18..3d46098 100644
*** a/doc/src/sgml/datatype.sgml
--- b/doc/src/sgml/datatype.sgml
*************** SET xmloption TO { DOCUMENT | CONTENT };
*** 4379,4386 ****
underlying type — for example, any operator or function that
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,
! enum type, array or range type, or another domain. (Currently, domains
! over composite types are not implemented.)
</para>
<para>
--- 4379,4385 ----
underlying type — for example, any operator or function that
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,
! enum type, array type, composite type, range type, or another domain.
</para>
<para>
diff --git a/doc/src/sgml/rowtypes.sgml b/doc/src/sgml/rowtypes.sgml
index 7e436a4..f80d44b 100644
*** a/doc/src/sgml/rowtypes.sgml
--- b/doc/src/sgml/rowtypes.sgml
*************** CREATE TABLE inventory_item (
*** 84,91 ****
restriction of the current implementation: since no constraints are
associated with a composite type, the constraints shown in the table
definition <emphasis>do not apply</emphasis> to values of the composite type
! outside the table. (A partial workaround is to use domain
! types as members of composite types.)
</para>
</sect2>
--- 84,92 ----
restriction of the current implementation: since no constraints are
associated with a composite type, the constraints shown in the table
definition <emphasis>do not apply</emphasis> to values of the composite type
! outside the table. (To work around this, create a domain over the composite
! type, and apply the desired constraints as <literal>CHECK</literal>
! constraints of the domain.)
</para>
</sect2>
diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml
index d9fccaa..9bdb72c 100644
*** a/doc/src/sgml/xfunc.sgml
--- b/doc/src/sgml/xfunc.sgml
*************** CREATE FUNCTION tf1 (accountno integer,
*** 353,358 ****
--- 353,383 ----
$$ 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>
+ </para>
</sect2>
<sect2 id="xfunc-sql-composite-functions">
*************** $$ LANGUAGE SQL;
*** 452,464 ****
</listitem>
<listitem>
<para>
! You must typecast the expressions to match the
! definition of the composite type, or you will get errors like this:
<screen>
<computeroutput>
ERROR: function declared to return emp returns varchar instead of text at column 1
</computeroutput>
</screen>
</para>
</listitem>
</itemizedlist>
--- 477,492 ----
</listitem>
<listitem>
<para>
! We must ensure each expression's type matches the corresponding
! column of the composite type, inserting a cast if necessary.
! Otherwise we'll get errors like this:
<screen>
<computeroutput>
ERROR: function declared to return emp returns varchar instead of text at column 1
</computeroutput>
</screen>
+ As with the base-type case, the function will not insert any casts
+ automatically.
</para>
</listitem>
</itemizedlist>
*************** $$ LANGUAGE SQL;
*** 478,483 ****
--- 506,516 ----
in this situation, but it is a handy alternative in some cases
— for example, if we need to compute the result by calling
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>
diff --git a/src/backend/catalog/pg_inherits.c b/src/backend/catalog/pg_inherits.c
index 245a374..1bd8a58 100644
*** a/src/backend/catalog/pg_inherits.c
--- b/src/backend/catalog/pg_inherits.c
*************** has_superclass(Oid relationId)
*** 301,306 ****
--- 301,311 ----
/*
* Given two type OIDs, determine whether the first is a complex type
* (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
typeInheritsFrom(Oid subclassTypeId, Oid superclassTypeId)
*************** typeInheritsFrom(Oid subclassTypeId, Oid
*** 314,322 ****
ListCell *queue_item;
/* We need to work with the associated relation OIDs */
! subclassRelid = typeidTypeRelid(subclassTypeId);
if (subclassRelid == InvalidOid)
! return false; /* not a complex type */
superclassRelid = typeidTypeRelid(superclassTypeId);
if (superclassRelid == InvalidOid)
return false; /* not a complex type */
--- 319,327 ----
ListCell *queue_item;
/* We need to work with the associated relation OIDs */
! subclassRelid = typeOrDomainTypeRelid(subclassTypeId);
if (subclassRelid == InvalidOid)
! return false; /* not a complex type or domain over one */
superclassRelid = typeidTypeRelid(superclassTypeId);
if (superclassRelid == InvalidOid)
return false; /* not a complex type */
diff --git a/src/backend/catalog/pg_proc.c b/src/backend/catalog/pg_proc.c
index 571856e..47916cf 100644
*** a/src/backend/catalog/pg_proc.c
--- b/src/backend/catalog/pg_proc.c
*************** ProcedureCreate(const char *procedureNam
*** 262,268 ****
*/
if (parameterCount == 1 &&
OidIsValid(parameterTypes->values[0]) &&
! (relid = typeidTypeRelid(parameterTypes->values[0])) != InvalidOid &&
get_attnum(relid, procedureName) != InvalidAttrNumber)
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_COLUMN),
--- 262,268 ----
*/
if (parameterCount == 1 &&
OidIsValid(parameterTypes->values[0]) &&
! (relid = typeOrDomainTypeRelid(parameterTypes->values[0])) != InvalidOid &&
get_attnum(relid, procedureName) != InvalidAttrNumber)
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_COLUMN),
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 2d4dcd7..09a7001 100644
*** a/src/backend/commands/tablecmds.c
--- b/src/backend/commands/tablecmds.c
*************** find_typed_table_dependencies(Oid typeOi
*** 5091,5096 ****
--- 5091,5098 ----
* 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
* 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
check_of_type(HeapTuple typetuple)
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index c1b87e0..7df942b 100644
*** a/src/backend/commands/typecmds.c
--- b/src/backend/commands/typecmds.c
*************** DefineDomain(CreateDomainStmt *stmt)
*** 798,810 ****
basetypeoid = HeapTupleGetOid(typeTup);
/*
! * Base type must be a plain base type, another domain, an enum or a range
! * type. Domains over pseudotypes would create a security hole. Domains
! * over composite types might be made to work in the future, but not
! * today.
*/
typtype = baseType->typtype;
if (typtype != TYPTYPE_BASE &&
typtype != TYPTYPE_DOMAIN &&
typtype != TYPTYPE_ENUM &&
typtype != TYPTYPE_RANGE)
--- 798,813 ----
basetypeoid = HeapTupleGetOid(typeTup);
/*
! * Base type must be a plain base type, a composite type, another domain,
! * an enum or a range type. Domains over pseudotypes would create a
! * security hole. (It would be shorter to code this to just check for
! * 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;
if (typtype != TYPTYPE_BASE &&
+ typtype != TYPTYPE_COMPOSITE &&
typtype != TYPTYPE_DOMAIN &&
typtype != TYPTYPE_ENUM &&
typtype != TYPTYPE_RANGE)
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index c5e97ef..a0f537b 100644
*** a/src/backend/executor/execExprInterp.c
--- b/src/backend/executor/execExprInterp.c
*************** ExecEvalWholeRowVar(ExprState *state, Ex
*** 3469,3476 ****
* generates an INT4 NULL regardless of the dropped column type).
* 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.
*/
! var_tupdesc = lookup_rowtype_tupdesc(variable->vartype, -1);
slot_tupdesc = slot->tts_tupleDescriptor;
--- 3469,3480 ----
* generates an INT4 NULL regardless of the dropped column type).
* 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.
+ *
+ * If vartype is a domain over composite, just look through that
+ * to the base composite type.
*/
! var_tupdesc = lookup_rowtype_tupdesc_domain(variable->vartype,
! -1, false);
slot_tupdesc = slot->tts_tupleDescriptor;
diff --git a/src/backend/executor/execSRF.c b/src/backend/executor/execSRF.c
index cce771d..aabe26c 100644
*** a/src/backend/executor/execSRF.c
--- b/src/backend/executor/execSRF.c
*************** init_sexpr(Oid foid, Oid input_collation
*** 734,740 ****
/* Must save tupdesc in sexpr's context */
oldcontext = MemoryContextSwitchTo(sexprCxt);
! if (functypclass == TYPEFUNC_COMPOSITE)
{
/* Composite data type, e.g. a table's row type */
Assert(tupdesc);
--- 734,741 ----
/* Must save tupdesc in sexpr's context */
oldcontext = MemoryContextSwitchTo(sexprCxt);
! if (functypclass == TYPEFUNC_COMPOSITE ||
! functypclass == TYPEFUNC_COMPOSITE_DOMAIN)
{
/* Composite data type, e.g. a table's row type */
Assert(tupdesc);
diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c
index 42a4ca9..98eb777 100644
*** a/src/backend/executor/functions.c
--- b/src/backend/executor/functions.c
*************** check_sql_fn_retval(Oid func_id, Oid ret
*** 1665,1671 ****
}
else if (fn_typtype == TYPTYPE_COMPOSITE || rettype == RECORDOID)
{
! /* Returns a rowtype */
TupleDesc tupdesc;
int tupnatts; /* physical number of columns in tuple */
int tuplogcols; /* # of nondeleted columns in tuple */
--- 1665,1679 ----
}
else if (fn_typtype == TYPTYPE_COMPOSITE || rettype == RECORDOID)
{
! /*
! * 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;
int tupnatts; /* physical number of columns in tuple */
int tuplogcols; /* # of nondeleted columns in tuple */
*************** check_sql_fn_retval(Oid func_id, Oid ret
*** 1711,1717 ****
}
}
! /* Is the rowtype fixed, or determined only at runtime? */
if (get_func_result_type(func_id, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
{
/*
--- 1719,1728 ----
}
}
! /*
! * 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)
{
/*
diff --git a/src/backend/executor/nodeFunctionscan.c b/src/backend/executor/nodeFunctionscan.c
index 9f87a7e..de476ac 100644
*** a/src/backend/executor/nodeFunctionscan.c
--- b/src/backend/executor/nodeFunctionscan.c
*************** ExecInitFunctionScan(FunctionScan *node,
*** 383,389 ****
&funcrettype,
&tupdesc);
! if (functypclass == TYPEFUNC_COMPOSITE)
{
/* Composite data type, e.g. a table's row type */
Assert(tupdesc);
--- 383,390 ----
&funcrettype,
&tupdesc);
! if (functypclass == TYPEFUNC_COMPOSITE ||
! functypclass == TYPEFUNC_COMPOSITE_DOMAIN)
{
/* Composite data type, e.g. a table's row type */
Assert(tupdesc);
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index b58eb0f..7a67653 100644
*** a/src/backend/nodes/makefuncs.c
--- b/src/backend/nodes/makefuncs.c
*************** makeVarFromTargetEntry(Index varno,
*** 120,127 ****
* 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
* with error cases, but it's not worth changing now.) The vartype indicates
! * a rowtype; either a named composite type, 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
* returning a non-composite result type, we produce a normal Var referencing
--- 120,129 ----
* 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
* with error cases, but it's not worth changing now.) The vartype indicates
! * a rowtype; either a named composite type, or a domain over a named
! * 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
* returning a non-composite result type, we produce a normal Var referencing
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 7961362..5344f61 100644
*** a/src/backend/optimizer/util/clauses.c
--- b/src/backend/optimizer/util/clauses.c
*************** CommuteRowCompareExpr(RowCompareExpr *cl
*** 2356,2361 ****
--- 2356,2365 ----
* is still what it was when the expression was parsed. This is needed to
* guard against improper simplification after ALTER COLUMN TYPE. (XXX we
* 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
rowtype_field_matches(Oid rowtypeid, int fieldnum,
*************** rowtype_field_matches(Oid rowtypeid, int
*** 2368,2374 ****
/* No issue for RECORD, since there is no way to ALTER such a type */
if (rowtypeid == RECORDOID)
return true;
! tupdesc = lookup_rowtype_tupdesc(rowtypeid, -1);
if (fieldnum <= 0 || fieldnum > tupdesc->natts)
{
ReleaseTupleDesc(tupdesc);
--- 2372,2378 ----
/* No issue for RECORD, since there is no way to ALTER such a type */
if (rowtypeid == RECORDOID)
return true;
! tupdesc = lookup_rowtype_tupdesc_domain(rowtypeid, -1, false);
if (fieldnum <= 0 || fieldnum > tupdesc->natts)
{
ReleaseTupleDesc(tupdesc);
*************** inline_set_returning_function(PlannerInf
*** 5005,5011 ****
*
* 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
! * returning is a single composite column which is not what we need.
*/
if (!check_sql_fn_retval(func_oid, fexpr->funcresulttype,
querytree_list,
--- 5009,5017 ----
*
* 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
! * 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,
querytree_list,
diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c
index 53457dc..def41b3 100644
*** a/src/backend/parser/parse_coerce.c
--- b/src/backend/parser/parse_coerce.c
*************** coerce_type(ParseState *pstate, Node *no
*** 499,507 ****
--- 499,524 ----
* Input class type is a subclass of target, so generate an
* appropriate runtime conversion (removing unneeded columns and
* 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);
+ 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->resulttype = targetTypeId;
r->convertformat = cformat;
*************** coerce_record_to_complex(ParseState *pst
*** 966,971 ****
--- 983,990 ----
int location)
{
RowExpr *rowexpr;
+ Oid baseTypeId;
+ int32 baseTypeMod = -1;
TupleDesc tupdesc;
List *args = NIL;
List *newargs;
*************** coerce_record_to_complex(ParseState *pst
*** 1001,1007 ****
format_type_be(targetTypeId)),
parser_coercion_errposition(pstate, location, node)));
! tupdesc = lookup_rowtype_tupdesc(targetTypeId, -1);
newargs = NIL;
ucolno = 1;
arg = list_head(args);
--- 1020,1033 ----
format_type_be(targetTypeId)),
parser_coercion_errposition(pstate, location, node)));
! /*
! * 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;
ucolno = 1;
arg = list_head(args);
*************** coerce_record_to_complex(ParseState *pst
*** 1070,1079 ****
rowexpr = makeNode(RowExpr);
rowexpr->args = newargs;
! rowexpr->row_typeid = targetTypeId;
rowexpr->row_format = cformat;
rowexpr->colnames = NIL; /* not needed for named target type */
rowexpr->location = location;
return (Node *) rowexpr;
}
--- 1096,1117 ----
rowexpr = makeNode(RowExpr);
rowexpr->args = newargs;
! rowexpr->row_typeid = baseTypeId;
rowexpr->row_format = cformat;
rowexpr->colnames = NIL; /* not needed for named target type */
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;
}
*************** is_complex_array(Oid typid)
*** 2401,2413 ****
/*
* Check whether reltypeId is the row type of a typed table of type
! * reloftypeId. (This is conceptually similar to the subtype
! * relationship checked by typeInheritsFrom().)
*/
static bool
typeIsOfTypedTable(Oid reltypeId, Oid reloftypeId)
{
! Oid relid = typeidTypeRelid(reltypeId);
bool result = false;
if (relid)
--- 2439,2451 ----
/*
* Check whether reltypeId is the row type of a typed table of type
! * reloftypeId, or is a domain over such a row type. (This is conceptually
! * similar to the subtype relationship checked by typeInheritsFrom().)
*/
static bool
typeIsOfTypedTable(Oid reltypeId, Oid reloftypeId)
{
! Oid relid = typeOrDomainTypeRelid(reltypeId);
bool result = false;
if (relid)
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index 2f2f2c7..fc0d6bc 100644
*** a/src/backend/parser/parse_func.c
--- b/src/backend/parser/parse_func.c
*************** ParseComplexProjection(ParseState *pstat
*** 1819,1836 ****
}
/*
! * Else do it the hard way with get_expr_result_type().
*
* 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.
* That task is handled by expandRecordVariable().
*/
if (IsA(first_arg, Var) &&
((Var *) first_arg)->vartype == RECORDOID)
tupdesc = expandRecordVariable(pstate, (Var *) first_arg, 0);
! else if (get_expr_result_type(first_arg, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
return NULL; /* unresolvable RECORD type */
- Assert(tupdesc);
for (i = 0; i < tupdesc->natts; i++)
{
--- 1819,1837 ----
}
/*
! * 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
! * find what the Var refers to, and pass that to get_expr_result_tupdesc.
* That task is handled by expandRecordVariable().
*/
if (IsA(first_arg, Var) &&
((Var *) first_arg)->vartype == RECORDOID)
tupdesc = expandRecordVariable(pstate, (Var *) first_arg, 0);
! else
! tupdesc = get_expr_result_tupdesc(first_arg, true);
! if (!tupdesc)
return NULL; /* unresolvable RECORD type */
for (i = 0; i < tupdesc->natts; i++)
{
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index a9273af..ca32a37 100644
*** a/src/backend/parser/parse_relation.c
--- b/src/backend/parser/parse_relation.c
*************** addRangeTableEntryForFunction(ParseState
*** 1496,1502 ****
parser_errposition(pstate, exprLocation(funcexpr))));
}
! if (functypclass == TYPEFUNC_COMPOSITE)
{
/* Composite data type, e.g. a table's row type */
Assert(tupdesc);
--- 1496,1503 ----
parser_errposition(pstate, exprLocation(funcexpr))));
}
! if (functypclass == TYPEFUNC_COMPOSITE ||
! functypclass == TYPEFUNC_COMPOSITE_DOMAIN)
{
/* Composite data type, e.g. a table's row type */
Assert(tupdesc);
*************** expandRTE(RangeTblEntry *rte, int rtinde
*** 2245,2251 ****
functypclass = get_expr_result_type(rtfunc->funcexpr,
&funcrettype,
&tupdesc);
! if (functypclass == TYPEFUNC_COMPOSITE)
{
/* Composite data type, e.g. a table's row type */
Assert(tupdesc);
--- 2246,2253 ----
functypclass = get_expr_result_type(rtfunc->funcexpr,
&funcrettype,
&tupdesc);
! if (functypclass == TYPEFUNC_COMPOSITE ||
! functypclass == TYPEFUNC_COMPOSITE_DOMAIN)
{
/* Composite data type, e.g. a table's row type */
Assert(tupdesc);
*************** get_rte_attribute_type(RangeTblEntry *rt
*** 2765,2771 ****
&funcrettype,
&tupdesc);
! if (functypclass == TYPEFUNC_COMPOSITE)
{
/* Composite data type, e.g. a table's row type */
Form_pg_attribute att_tup;
--- 2767,2774 ----
&funcrettype,
&tupdesc);
! if (functypclass == TYPEFUNC_COMPOSITE ||
! functypclass == TYPEFUNC_COMPOSITE_DOMAIN)
{
/* Composite data type, e.g. a table's row type */
Form_pg_attribute att_tup;
*************** get_rte_attribute_is_dropped(RangeTblEnt
*** 2966,2979 ****
if (attnum > atts_done &&
attnum <= atts_done + rtfunc->funccolcount)
{
- TypeFuncClass functypclass;
- Oid funcrettype;
TupleDesc tupdesc;
! functypclass = get_expr_result_type(rtfunc->funcexpr,
! &funcrettype,
! &tupdesc);
! if (functypclass == TYPEFUNC_COMPOSITE)
{
/* Composite data type, e.g. a table's row type */
Form_pg_attribute att_tup;
--- 2969,2979 ----
if (attnum > atts_done &&
attnum <= atts_done + rtfunc->funccolcount)
{
TupleDesc tupdesc;
! tupdesc = get_expr_result_tupdesc(rtfunc->funcexpr,
! true);
! if (tupdesc)
{
/* Composite data type, e.g. a table's row type */
Form_pg_attribute att_tup;
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 2547524..16d3dba 100644
*** a/src/backend/parser/parse_target.c
--- b/src/backend/parser/parse_target.c
*************** transformAssignmentIndirection(ParseStat
*** 725,730 ****
--- 725,732 ----
else
{
FieldStore *fstore;
+ Oid baseTypeId;
+ int32 baseTypeMod;
Oid typrelid;
AttrNumber attnum;
Oid fieldTypeId;
*************** transformAssignmentIndirection(ParseStat
*** 752,758 ****
/* No subscripts, so can process field selection here */
! typrelid = typeidTypeRelid(targetTypeId);
if (!typrelid)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
--- 754,767 ----
/* No subscripts, so can process field selection here */
! /*
! * 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)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
*************** transformAssignmentIndirection(ParseStat
*** 796,802 ****
fstore->arg = (Expr *) basenode;
fstore->newvals = list_make1(rhs);
fstore->fieldnums = list_make1_int(attnum);
! fstore->resulttype = targetTypeId;
return (Node *) fstore;
}
--- 805,821 ----
fstore->arg = (Expr *) basenode;
fstore->newvals = list_make1(rhs);
fstore->fieldnums = list_make1_int(attnum);
! 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;
}
*************** ExpandRowReference(ParseState *pstate, N
*** 1387,1408 ****
* (This can be pretty inefficient if the expression involves nontrivial
* computation :-(.)
*
! * Verify it's a composite type, and get the tupdesc. We use
! * get_expr_result_type() because that can handle references to functions
! * 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
! * find what the Var refers to, and pass that to get_expr_result_type.
* That task is handled by expandRecordVariable().
*/
if (IsA(expr, Var) &&
((Var *) expr)->vartype == RECORDOID)
tupleDesc = expandRecordVariable(pstate, (Var *) expr, 0);
! else if (get_expr_result_type(expr, NULL, &tupleDesc) != TYPEFUNC_COMPOSITE)
! tupleDesc = lookup_rowtype_tupdesc_copy(exprType(expr),
! exprTypmod(expr));
Assert(tupleDesc);
/* Generate a list of references to the individual fields */
--- 1406,1423 ----
* (This can be pretty inefficient if the expression involves nontrivial
* computation :-(.)
*
! * Verify it's a composite type, and get the tupdesc.
! * get_expr_result_tupdesc() handles this conveniently.
*
* 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_tupdesc.
* That task is handled by expandRecordVariable().
*/
if (IsA(expr, Var) &&
((Var *) expr)->vartype == RECORDOID)
tupleDesc = expandRecordVariable(pstate, (Var *) expr, 0);
! else
! tupleDesc = get_expr_result_tupdesc(expr, false);
Assert(tupleDesc);
/* Generate a list of references to the individual fields */
*************** expandRecordVariable(ParseState *pstate,
*** 1610,1624 ****
/*
* 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
! * 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)
! tupleDesc = lookup_rowtype_tupdesc_copy(exprType(expr),
! exprTypmod(expr));
!
! return tupleDesc;
}
--- 1625,1633 ----
/*
* We now have an expression we can't expand any more, so see if
! * get_expr_result_tupdesc() can do anything with it.
*/
! return get_expr_result_tupdesc(expr, false);
}
diff --git a/src/backend/parser/parse_type.c b/src/backend/parser/parse_type.c
index d0b3fbe..b032651 100644
*** a/src/backend/parser/parse_type.c
--- b/src/backend/parser/parse_type.c
*************** stringTypeDatum(Type tp, char *string, i
*** 641,647 ****
return OidInputFunctionCall(typinput, string, typioparam, atttypmod);
}
! /* given a typeid, return the type's typrelid (associated relation, if any) */
Oid
typeidTypeRelid(Oid type_id)
{
--- 641,650 ----
return OidInputFunctionCall(typinput, string, typioparam, atttypmod);
}
! /*
! * Given a typeid, return the type's typrelid (associated relation), if any.
! * Returns InvalidOid if type is not a composite type.
! */
Oid
typeidTypeRelid(Oid type_id)
{
*************** typeidTypeRelid(Oid type_id)
*** 652,658 ****
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);
result = type->typrelid;
ReleaseSysCache(typeTuple);
--- 655,660 ----
*************** typeidTypeRelid(Oid type_id)
*** 660,665 ****
--- 662,699 ----
}
/*
+ * 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);
+ 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;
+ ReleaseSysCache(typeTuple);
+ return result;
+ }
+
+ /*
* error context callback for parse failure during parseTypeString()
*/
static void
diff --git a/src/backend/utils/adt/domains.c b/src/backend/utils/adt/domains.c
index e61d91b..86f916f 100644
*** a/src/backend/utils/adt/domains.c
--- b/src/backend/utils/adt/domains.c
*************** domain_state_setup(Oid domainType, bool
*** 82,90 ****
* 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,
* possibly with incorrect arguments. We use lookup_type_cache mainly
! * because it will throw a clean user-facing error for a bad OID.
*/
! typentry = lookup_type_cache(domainType, 0);
if (typentry->typtype != TYPTYPE_DOMAIN)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
--- 82,91 ----
* 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,
* possibly with incorrect arguments. We use lookup_type_cache mainly
! * 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, TYPECACHE_DOMAIN_BASE_INFO);
if (typentry->typtype != TYPTYPE_DOMAIN)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
*************** domain_state_setup(Oid domainType, bool
*** 92,99 ****
format_type_be(domainType))));
/* Find out the base type */
! my_extra->typtypmod = -1;
! baseType = getBaseTypeAndTypmod(domainType, &my_extra->typtypmod);
/* Look up underlying I/O function */
if (binary)
--- 93,100 ----
format_type_be(domainType))));
/* Find out the base type */
! baseType = typentry->domainBaseType;
! my_extra->typtypmod = typentry->domainBaseTypmod;
/* Look up underlying I/O function */
if (binary)
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index d36fd9e..76ef01c 100644
*** a/src/backend/utils/adt/jsonfuncs.c
--- b/src/backend/utils/adt/jsonfuncs.c
*************** typedef struct CompositeIOData
*** 169,174 ****
--- 169,179 ----
*/
RecordIOData *record_io; /* metadata cache for populate_record() */
TupleDesc tupdesc; /* cached tuple descriptor */
+ /* these fields differ from target type only if domain over composite: */
+ Oid base_typid; /* base type id */
+ int32 base_typmod; /* base type modifier */
+ /* this field is used only if target type is domain over composite: */
+ void *domain_info; /* opaque cache for domain checks */
} CompositeIOData;
/* structure to cache metadata needed for populate_domain() */
*************** typedef enum TypeCat
*** 186,191 ****
--- 191,197 ----
TYPECAT_SCALAR = 's',
TYPECAT_ARRAY = 'a',
TYPECAT_COMPOSITE = 'c',
+ TYPECAT_COMPOSITE_DOMAIN = 'C',
TYPECAT_DOMAIN = 'd'
} TypeCat;
*************** struct RecordIOData
*** 217,223 ****
ColumnIOData columns[FLEXIBLE_ARRAY_MEMBER];
};
! /* state for populate_recordset */
typedef struct PopulateRecordsetState
{
JsonLexContext *lex;
--- 223,237 ----
ColumnIOData columns[FLEXIBLE_ARRAY_MEMBER];
};
! /* per-query cache for populate_recordset */
! typedef struct PopulateRecordsetCache
! {
! Oid argtype; /* declared type of the record argument */
! ColumnIOData c; /* metadata cache for populate_composite() */
! MemoryContext fn_mcxt; /* where this is stored */
! } PopulateRecordsetCache;
!
! /* per-call state for populate_recordset */
typedef struct PopulateRecordsetState
{
JsonLexContext *lex;
*************** typedef struct PopulateRecordsetState
*** 227,243 ****
char *save_json_start;
JsonTokenType saved_token_type;
Tuplestorestate *tuple_store;
- TupleDesc ret_tdesc;
HeapTupleHeader rec;
! RecordIOData **my_extra;
! MemoryContext fn_mcxt; /* used to stash IO funcs */
} PopulateRecordsetState;
/* structure to cache metadata needed for populate_record_worker() */
typedef struct PopulateRecordCache
{
! Oid argtype; /* verified row type of the first argument */
! CompositeIOData io; /* metadata cache for populate_composite() */
} PopulateRecordCache;
/* common data for populate_array_json() and populate_array_dim_jsonb() */
--- 241,255 ----
char *save_json_start;
JsonTokenType saved_token_type;
Tuplestorestate *tuple_store;
HeapTupleHeader rec;
! PopulateRecordsetCache *cache;
} PopulateRecordsetState;
/* structure to cache metadata needed for populate_record_worker() */
typedef struct PopulateRecordCache
{
! Oid argtype; /* declared type of the record argument */
! ColumnIOData c; /* metadata cache for populate_composite() */
} PopulateRecordCache;
/* common data for populate_array_json() and populate_array_dim_jsonb() */
*************** static Datum populate_record_worker(Func
*** 415,430 ****
static HeapTupleHeader populate_record(TupleDesc tupdesc, RecordIOData **record_p,
HeapTupleHeader defaultval, MemoryContext mcxt,
JsObject *obj);
- static Datum populate_record_field(ColumnIOData *col, Oid typid, int32 typmod,
- const char *colname, MemoryContext mcxt,
- Datum defaultval, JsValue *jsv, bool *isnull);
static void JsValueToJsObject(JsValue *jsv, JsObject *jso);
! static Datum populate_composite(CompositeIOData *io, Oid typid, int32 typmod,
const char *colname, MemoryContext mcxt,
! HeapTupleHeader defaultval, JsValue *jsv);
static Datum populate_scalar(ScalarIOData *io, Oid typid, int32 typmod, JsValue *jsv);
static void prepare_column_cache(ColumnIOData *column, Oid typid, int32 typmod,
! MemoryContext mcxt, bool json);
static Datum populate_record_field(ColumnIOData *col, Oid typid, int32 typmod,
const char *colname, MemoryContext mcxt, Datum defaultval,
JsValue *jsv, bool *isnull);
--- 427,439 ----
static HeapTupleHeader populate_record(TupleDesc tupdesc, RecordIOData **record_p,
HeapTupleHeader defaultval, MemoryContext mcxt,
JsObject *obj);
static void JsValueToJsObject(JsValue *jsv, JsObject *jso);
! static Datum populate_composite(CompositeIOData *io, Oid typid,
const char *colname, MemoryContext mcxt,
! HeapTupleHeader defaultval, JsValue *jsv, bool isnull);
static Datum populate_scalar(ScalarIOData *io, Oid typid, int32 typmod, JsValue *jsv);
static void prepare_column_cache(ColumnIOData *column, Oid typid, int32 typmod,
! MemoryContext mcxt, bool need_scalar);
static Datum populate_record_field(ColumnIOData *col, Oid typid, int32 typmod,
const char *colname, MemoryContext mcxt, Datum defaultval,
JsValue *jsv, bool *isnull);
*************** JsValueToJsObject(JsValue *jsv, JsObject
*** 2704,2728 ****
}
}
! /* recursively populate a composite (row type) value from json/jsonb */
! static Datum
! populate_composite(CompositeIOData *io,
! Oid typid,
! int32 typmod,
! const char *colname,
! MemoryContext mcxt,
! HeapTupleHeader defaultval,
! JsValue *jsv)
{
- HeapTupleHeader tuple;
- JsObject jso;
-
- /* acquire cached tuple descriptor */
if (!io->tupdesc ||
! io->tupdesc->tdtypeid != typid ||
! io->tupdesc->tdtypmod != typmod)
{
! TupleDesc tupdesc = lookup_rowtype_tupdesc(typid, typmod);
MemoryContext oldcxt;
if (io->tupdesc)
--- 2713,2728 ----
}
}
! /* acquire or update cached tuple descriptor for a composite type */
! static void
! update_cached_tupdesc(CompositeIOData *io, MemoryContext mcxt)
{
if (!io->tupdesc ||
! io->tupdesc->tdtypeid != io->base_typid ||
! io->tupdesc->tdtypmod != io->base_typmod)
{
! TupleDesc tupdesc = lookup_rowtype_tupdesc(io->base_typid,
! io->base_typmod);
MemoryContext oldcxt;
if (io->tupdesc)
*************** populate_composite(CompositeIOData *io,
*** 2735,2751 ****
ReleaseTupleDesc(tupdesc);
}
! /* prepare input value */
! JsValueToJsObject(jsv, &jso);
! /* populate resulting record tuple */
! tuple = populate_record(io->tupdesc, &io->record_io,
! defaultval, mcxt, &jso);
! JsObjectFree(&jso);
! return HeapTupleHeaderGetDatum(tuple);
}
/* populate non-null scalar value from json/jsonb value */
--- 2735,2780 ----
ReleaseTupleDesc(tupdesc);
}
+ }
! /* recursively populate a composite (row type) value from json/jsonb */
! static Datum
! populate_composite(CompositeIOData *io,
! Oid typid,
! const char *colname,
! MemoryContext mcxt,
! HeapTupleHeader defaultval,
! JsValue *jsv,
! bool isnull)
! {
! Datum result;
! /* acquire/update cached tuple descriptor */
! update_cached_tupdesc(io, mcxt);
! if (isnull)
! result = (Datum) 0;
! else
! {
! HeapTupleHeader tuple;
! JsObject jso;
! /* prepare input value */
! JsValueToJsObject(jsv, &jso);
!
! /* populate resulting record tuple */
! tuple = populate_record(io->tupdesc, &io->record_io,
! defaultval, mcxt, &jso);
! result = HeapTupleHeaderGetDatum(tuple);
!
! JsObjectFree(&jso);
! }
!
! /* if it's domain over composite, check domain constraints */
! if (typid != io->base_typid)
! domain_check(result, isnull, typid, &io->domain_info, mcxt);
!
! return result;
}
/* populate non-null scalar value from json/jsonb value */
*************** prepare_column_cache(ColumnIOData *colum
*** 2867,2873 ****
Oid typid,
int32 typmod,
MemoryContext mcxt,
! bool json)
{
HeapTuple tup;
Form_pg_type type;
--- 2896,2902 ----
Oid typid,
int32 typmod,
MemoryContext mcxt,
! bool need_scalar)
{
HeapTuple tup;
Form_pg_type type;
*************** prepare_column_cache(ColumnIOData *colum
*** 2883,2900 ****
if (type->typtype == TYPTYPE_DOMAIN)
{
! column->typcat = TYPECAT_DOMAIN;
! column->io.domain.base_typid = type->typbasetype;
! column->io.domain.base_typmod = type->typtypmod;
! column->io.domain.base_io = MemoryContextAllocZero(mcxt,
! sizeof(ColumnIOData));
! column->io.domain.domain_info = NULL;
}
else if (type->typtype == TYPTYPE_COMPOSITE || typid == RECORDOID)
{
column->typcat = TYPECAT_COMPOSITE;
column->io.composite.record_io = NULL;
column->io.composite.tupdesc = NULL;
}
else if (type->typlen == -1 && OidIsValid(type->typelem))
{
--- 2912,2954 ----
if (type->typtype == TYPTYPE_DOMAIN)
{
! /*
! * We can move directly to the bottom base type; domain_check() will
! * take care of checking all constraints for a stack of domains.
! */
! Oid base_typid;
! int32 base_typmod = typmod;
!
! base_typid = getBaseTypeAndTypmod(typid, &base_typmod);
! if (get_typtype(base_typid) == TYPTYPE_COMPOSITE)
! {
! /* domain over composite has its own code path */
! column->typcat = TYPECAT_COMPOSITE_DOMAIN;
! column->io.composite.record_io = NULL;
! column->io.composite.tupdesc = NULL;
! column->io.composite.base_typid = base_typid;
! column->io.composite.base_typmod = base_typmod;
! column->io.composite.domain_info = NULL;
! }
! else
! {
! /* domain over anything else */
! column->typcat = TYPECAT_DOMAIN;
! column->io.domain.base_typid = base_typid;
! column->io.domain.base_typmod = base_typmod;
! column->io.domain.base_io =
! MemoryContextAllocZero(mcxt, sizeof(ColumnIOData));
! column->io.domain.domain_info = NULL;
! }
}
else if (type->typtype == TYPTYPE_COMPOSITE || typid == RECORDOID)
{
column->typcat = TYPECAT_COMPOSITE;
column->io.composite.record_io = NULL;
column->io.composite.tupdesc = NULL;
+ column->io.composite.base_typid = typid;
+ column->io.composite.base_typmod = typmod;
+ column->io.composite.domain_info = NULL;
}
else if (type->typlen == -1 && OidIsValid(type->typelem))
{
*************** prepare_column_cache(ColumnIOData *colum
*** 2906,2915 ****
column->io.array.element_typmod = typmod;
}
else
column->typcat = TYPECAT_SCALAR;
! /* don't need input function when converting from jsonb to jsonb */
! if (json || typid != JSONBOID)
{
Oid typioproc;
--- 2960,2972 ----
column->io.array.element_typmod = typmod;
}
else
+ {
column->typcat = TYPECAT_SCALAR;
+ need_scalar = true;
+ }
! /* caller can force us to look up scalar_io info even for non-scalars */
! if (need_scalar)
{
Oid typioproc;
*************** populate_record_field(ColumnIOData *col,
*** 2935,2943 ****
check_stack_depth();
! /* prepare column metadata cache for the given type */
if (col->typid != typid || col->typmod != typmod)
! prepare_column_cache(col, typid, typmod, mcxt, jsv->is_json);
*isnull = JsValueIsNull(jsv);
--- 2992,3003 ----
check_stack_depth();
! /*
! * Prepare column metadata cache for the given type. Force lookup of the
! * scalar_io data so that the json string hack below will work.
! */
if (col->typid != typid || col->typmod != typmod)
! prepare_column_cache(col, typid, typmod, mcxt, true);
*isnull = JsValueIsNull(jsv);
*************** populate_record_field(ColumnIOData *col,
*** 2945,2955 ****
/* try to convert json string to a non-scalar type through input function */
if (JsValueIsString(jsv) &&
! (typcat == TYPECAT_ARRAY || typcat == TYPECAT_COMPOSITE))
typcat = TYPECAT_SCALAR;
! /* we must perform domain checks for NULLs */
! if (*isnull && typcat != TYPECAT_DOMAIN)
return (Datum) 0;
switch (typcat)
--- 3005,3019 ----
/* try to convert json string to a non-scalar type through input function */
if (JsValueIsString(jsv) &&
! (typcat == TYPECAT_ARRAY ||
! typcat == TYPECAT_COMPOSITE ||
! typcat == TYPECAT_COMPOSITE_DOMAIN))
typcat = TYPECAT_SCALAR;
! /* we must perform domain checks for NULLs, otherwise exit immediately */
! if (*isnull &&
! typcat != TYPECAT_DOMAIN &&
! typcat != TYPECAT_COMPOSITE_DOMAIN)
return (Datum) 0;
switch (typcat)
*************** populate_record_field(ColumnIOData *col,
*** 2961,2972 ****
return populate_array(&col->io.array, colname, mcxt, jsv);
case TYPECAT_COMPOSITE:
! return populate_composite(&col->io.composite, typid, typmod,
colname, mcxt,
DatumGetPointer(defaultval)
? DatumGetHeapTupleHeader(defaultval)
: NULL,
! jsv);
case TYPECAT_DOMAIN:
return populate_domain(&col->io.domain, typid, colname, mcxt,
--- 3025,3037 ----
return populate_array(&col->io.array, colname, mcxt, jsv);
case TYPECAT_COMPOSITE:
! case TYPECAT_COMPOSITE_DOMAIN:
! return populate_composite(&col->io.composite, typid,
colname, mcxt,
DatumGetPointer(defaultval)
? DatumGetHeapTupleHeader(defaultval)
: NULL,
! jsv, *isnull);
case TYPECAT_DOMAIN:
return populate_domain(&col->io.domain, typid, colname, mcxt,
*************** populate_record_worker(FunctionCallInfo
*** 3137,3146 ****
int json_arg_num = have_record_arg ? 1 : 0;
Oid jtype = get_fn_expr_argtype(fcinfo->flinfo, json_arg_num);
JsValue jsv = {0};
! HeapTupleHeader rec = NULL;
! Oid tupType;
! int32 tupTypmod;
! TupleDesc tupdesc = NULL;
Datum rettuple;
JsonbValue jbv;
MemoryContext fnmcxt = fcinfo->flinfo->fn_mcxt;
--- 3202,3208 ----
int json_arg_num = have_record_arg ? 1 : 0;
Oid jtype = get_fn_expr_argtype(fcinfo->flinfo, json_arg_num);
JsValue jsv = {0};
! HeapTupleHeader rec;
Datum rettuple;
JsonbValue jbv;
MemoryContext fnmcxt = fcinfo->flinfo->fn_mcxt;
*************** populate_record_worker(FunctionCallInfo
*** 3149,3225 ****
Assert(jtype == JSONOID || jtype == JSONBOID);
/*
! * We arrange to look up the needed I/O info just once per series of
! * calls, assuming the record type doesn't change underneath us.
*/
if (!cache)
fcinfo->flinfo->fn_extra = cache =
MemoryContextAllocZero(fnmcxt, sizeof(*cache));
! if (have_record_arg)
! {
! Oid argtype = get_fn_expr_argtype(fcinfo->flinfo, 0);
!
! if (cache->argtype != argtype)
{
! if (!type_is_rowtype(argtype))
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("first argument of %s must be a row type",
funcname)));
-
- cache->argtype = argtype;
}
!
! if (PG_ARGISNULL(0))
{
- if (PG_ARGISNULL(1))
- PG_RETURN_NULL();
-
/*
! * 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 don't have a known composite type oid here.
*/
! tupType = argtype;
! tupTypmod = -1;
! }
! else
! {
! rec = PG_GETARG_HEAPTUPLEHEADER(0);
! if (PG_ARGISNULL(1))
! PG_RETURN_POINTER(rec);
! /* Extract type info from the tuple itself */
! tupType = HeapTupleHeaderGetTypeId(rec);
! tupTypmod = HeapTupleHeaderGetTypMod(rec);
}
}
- else
- {
- /* json{b}_to_record case */
- if (PG_ARGISNULL(0))
- PG_RETURN_NULL();
-
- if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("function returning record called in context "
- "that cannot accept type record"),
- errhint("Try calling the function in the FROM clause "
- "using a column definition list.")));
! Assert(tupdesc);
/*
! * Add tupdesc to the cache and set the appropriate values of
! * tupType/tupTypmod for proper cache usage in populate_composite().
*/
! cache->io.tupdesc = tupdesc;
! tupType = tupdesc->tdtypeid;
! tupTypmod = tupdesc->tdtypmod;
}
jsv.is_json = jtype == JSONOID;
--- 3211,3295 ----
Assert(jtype == JSONOID || jtype == JSONBOID);
/*
! * If first time through, identify input/result record type. Note that
! * this stanza looks only at fcinfo context, which can't change during the
! * query; so we may not be able to fully resolve a RECORD input type yet.
*/
if (!cache)
+ {
fcinfo->flinfo->fn_extra = cache =
MemoryContextAllocZero(fnmcxt, sizeof(*cache));
! if (have_record_arg)
{
! /*
! * json{b}_populate_record case: result type will be same as first
! * argument's.
! */
! cache->argtype = get_fn_expr_argtype(fcinfo->flinfo, 0);
! prepare_column_cache(&cache->c,
! cache->argtype, -1,
! fnmcxt, false);
! if (cache->c.typcat != TYPECAT_COMPOSITE &&
! cache->c.typcat != TYPECAT_COMPOSITE_DOMAIN)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("first argument of %s must be a row type",
funcname)));
}
! else
{
/*
! * json{b}_to_record case: result type is specified by calling
! * query. Here it is syntactically impossible to specify the
! * target type as domain-over-composite.
*/
! TupleDesc tupdesc;
! MemoryContext old_cxt;
! if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
! ereport(ERROR,
! (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! errmsg("function returning record called in context "
! "that cannot accept type record"),
! errhint("Try calling the function in the FROM clause "
! "using a column definition list.")));
! Assert(tupdesc);
! cache->argtype = tupdesc->tdtypeid;
!
! /* Save identified tupdesc */
! old_cxt = MemoryContextSwitchTo(fnmcxt);
! cache->c.io.composite.tupdesc = CreateTupleDescCopy(tupdesc);
! cache->c.io.composite.base_typid = tupdesc->tdtypeid;
! cache->c.io.composite.base_typmod = tupdesc->tdtypmod;
! MemoryContextSwitchTo(old_cxt);
}
}
! /* Collect record arg if we have one */
! if (have_record_arg && !PG_ARGISNULL(0))
! {
! rec = PG_GETARG_HEAPTUPLEHEADER(0);
/*
! * When declared arg type is RECORD, identify actual record type from
! * the tuple itself. Note the lookup_rowtype_tupdesc call in
! * update_cached_tupdesc will fail if we're unable to do this.
*/
! if (cache->argtype == RECORDOID)
! cache->c.io.composite.base_typmod = HeapTupleHeaderGetTypMod(rec);
! }
! else
! rec = NULL;
! /* If no JSON argument, just return the record (if any) unchanged */
! if (PG_ARGISNULL(json_arg_num))
! {
! if (rec)
! PG_RETURN_POINTER(rec);
! else
! PG_RETURN_NULL();
}
jsv.is_json = jtype == JSONOID;
*************** populate_record_worker(FunctionCallInfo
*** 3245,3258 ****
jbv.val.binary.len = VARSIZE(jb) - VARHDRSZ;
}
! rettuple = populate_composite(&cache->io, tupType, tupTypmod,
! NULL, fnmcxt, rec, &jsv);
!
! if (tupdesc)
! {
! cache->io.tupdesc = NULL;
! ReleaseTupleDesc(tupdesc);
! }
PG_RETURN_DATUM(rettuple);
}
--- 3315,3322 ----
jbv.val.binary.len = VARSIZE(jb) - VARHDRSZ;
}
! rettuple = populate_composite(&cache->c.io.composite, cache->argtype,
! NULL, fnmcxt, rec, &jsv, false);
PG_RETURN_DATUM(rettuple);
}
*************** json_to_recordset(PG_FUNCTION_ARGS)
*** 3438,3450 ****
static void
populate_recordset_record(PopulateRecordsetState *state, JsObject *obj)
{
HeapTupleData tuple;
- HeapTupleHeader tuphead = populate_record(state->ret_tdesc,
- state->my_extra,
- state->rec,
- state->fn_mcxt,
- obj);
tuple.t_len = HeapTupleHeaderGetDatumLength(tuphead);
ItemPointerSetInvalid(&(tuple.t_self));
tuple.t_tableOid = InvalidOid;
--- 3502,3529 ----
static void
populate_recordset_record(PopulateRecordsetState *state, JsObject *obj)
{
+ PopulateRecordsetCache *cache = state->cache;
+ HeapTupleHeader tuphead;
HeapTupleData tuple;
+ /* acquire/update cached tuple descriptor */
+ update_cached_tupdesc(&cache->c.io.composite, cache->fn_mcxt);
+
+ /* replace record fields from json */
+ tuphead = populate_record(cache->c.io.composite.tupdesc,
+ &cache->c.io.composite.record_io,
+ state->rec,
+ cache->fn_mcxt,
+ obj);
+
+ /* if it's domain over composite, check domain constraints */
+ if (cache->argtype != cache->c.io.composite.base_typid)
+ domain_check(HeapTupleHeaderGetDatum(tuphead), false,
+ cache->argtype,
+ &cache->c.io.composite.domain_info,
+ cache->fn_mcxt);
+
+ /* ok, save into tuplestore */
tuple.t_len = HeapTupleHeaderGetDatumLength(tuphead);
ItemPointerSetInvalid(&(tuple.t_self));
tuple.t_tableOid = InvalidOid;
*************** populate_recordset_worker(FunctionCallIn
*** 3465,3489 ****
ReturnSetInfo *rsi;
MemoryContext old_cxt;
HeapTupleHeader rec;
! TupleDesc tupdesc;
PopulateRecordsetState *state;
- if (have_record_arg)
- {
- Oid argtype = get_fn_expr_argtype(fcinfo->flinfo, 0);
-
- if (!type_is_rowtype(argtype))
- ereport(ERROR,
- (errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("first argument of %s must be a row type",
- funcname)));
- }
-
rsi = (ReturnSetInfo *) fcinfo->resultinfo;
if (!rsi || !IsA(rsi, ReturnSetInfo) ||
! (rsi->allowedModes & SFRM_Materialize) == 0 ||
! rsi->expectedDesc == NULL)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("set-valued function called in context that "
--- 3544,3556 ----
ReturnSetInfo *rsi;
MemoryContext old_cxt;
HeapTupleHeader rec;
! PopulateRecordsetCache *cache = fcinfo->flinfo->fn_extra;
PopulateRecordsetState *state;
rsi = (ReturnSetInfo *) fcinfo->resultinfo;
if (!rsi || !IsA(rsi, ReturnSetInfo) ||
! (rsi->allowedModes & SFRM_Materialize) == 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("set-valued function called in context that "
*************** populate_recordset_worker(FunctionCallIn
*** 3492,3531 ****
rsi->returnMode = SFRM_Materialize;
/*
! * get the tupdesc from the result set info - it must be a record type
! * because we already checked that arg1 is a record type, or we're in a
! * to_record function which returns a setof record.
*/
! if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
! ereport(ERROR,
! (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! errmsg("function returning record called in context "
! "that cannot accept type record")));
/* if the json is null send back an empty set */
if (PG_ARGISNULL(json_arg_num))
PG_RETURN_NULL();
- if (!have_record_arg || PG_ARGISNULL(0))
- rec = NULL;
- else
- rec = PG_GETARG_HEAPTUPLEHEADER(0);
-
state = palloc0(sizeof(PopulateRecordsetState));
! /* make these in a sufficiently long-lived memory context */
old_cxt = MemoryContextSwitchTo(rsi->econtext->ecxt_per_query_memory);
- state->ret_tdesc = CreateTupleDescCopy(tupdesc);
- BlessTupleDesc(state->ret_tdesc);
state->tuple_store = tuplestore_begin_heap(rsi->allowedModes &
SFRM_Materialize_Random,
false, work_mem);
MemoryContextSwitchTo(old_cxt);
state->function_name = funcname;
! state->my_extra = (RecordIOData **) &fcinfo->flinfo->fn_extra;
state->rec = rec;
- state->fn_mcxt = fcinfo->flinfo->fn_mcxt;
if (jtype == JSONOID)
{
--- 3559,3652 ----
rsi->returnMode = SFRM_Materialize;
/*
! * If first time through, identify input/result record type. Note that
! * this stanza looks only at fcinfo context, which can't change during the
! * query; so we may not be able to fully resolve a RECORD input type yet.
*/
! if (!cache)
! {
! fcinfo->flinfo->fn_extra = cache =
! MemoryContextAllocZero(fcinfo->flinfo->fn_mcxt, sizeof(*cache));
! cache->fn_mcxt = fcinfo->flinfo->fn_mcxt;
!
! if (have_record_arg)
! {
! /*
! * json{b}_populate_recordset case: result type will be same as
! * first argument's.
! */
! cache->argtype = get_fn_expr_argtype(fcinfo->flinfo, 0);
! prepare_column_cache(&cache->c,
! cache->argtype, -1,
! cache->fn_mcxt, false);
! if (cache->c.typcat != TYPECAT_COMPOSITE &&
! cache->c.typcat != TYPECAT_COMPOSITE_DOMAIN)
! ereport(ERROR,
! (errcode(ERRCODE_DATATYPE_MISMATCH),
! errmsg("first argument of %s must be a row type",
! funcname)));
! }
! else
! {
! /*
! * json{b}_to_recordset case: result type is specified by calling
! * query. Here it is syntactically impossible to specify the
! * target type as domain-over-composite.
! */
! TupleDesc tupdesc;
!
! if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
! ereport(ERROR,
! (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! errmsg("function returning record called in context "
! "that cannot accept type record"),
! errhint("Try calling the function in the FROM clause "
! "using a column definition list.")));
!
! Assert(tupdesc);
! cache->argtype = tupdesc->tdtypeid;
!
! /* Save identified tupdesc */
! old_cxt = MemoryContextSwitchTo(cache->fn_mcxt);
! cache->c.io.composite.tupdesc = CreateTupleDescCopy(tupdesc);
! cache->c.io.composite.base_typid = tupdesc->tdtypeid;
! cache->c.io.composite.base_typmod = tupdesc->tdtypmod;
! MemoryContextSwitchTo(old_cxt);
! }
! }
!
! /* Collect record arg if we have one */
! if (have_record_arg && !PG_ARGISNULL(0))
! {
! rec = PG_GETARG_HEAPTUPLEHEADER(0);
!
! /*
! * When declared arg type is RECORD, identify actual record type from
! * the tuple itself. Note the lookup_rowtype_tupdesc call in
! * update_cached_tupdesc will fail if we're unable to do this.
! */
! if (cache->argtype == RECORDOID)
! cache->c.io.composite.base_typmod = HeapTupleHeaderGetTypMod(rec);
! }
! else
! rec = NULL;
/* if the json is null send back an empty set */
if (PG_ARGISNULL(json_arg_num))
PG_RETURN_NULL();
state = palloc0(sizeof(PopulateRecordsetState));
! /* make tuplestore in a sufficiently long-lived memory context */
old_cxt = MemoryContextSwitchTo(rsi->econtext->ecxt_per_query_memory);
state->tuple_store = tuplestore_begin_heap(rsi->allowedModes &
SFRM_Materialize_Random,
false, work_mem);
MemoryContextSwitchTo(old_cxt);
state->function_name = funcname;
! state->cache = cache;
state->rec = rec;
if (jtype == JSONOID)
{
*************** populate_recordset_worker(FunctionCallIn
*** 3592,3598 ****
}
rsi->setResult = state->tuple_store;
! rsi->setDesc = state->ret_tdesc;
PG_RETURN_NULL();
}
--- 3713,3719 ----
}
rsi->setResult = state->tuple_store;
! rsi->setDesc = cache->c.io.composite.tupdesc;
PG_RETURN_NULL();
}
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 84759b6..b1e70a0 100644
*** a/src/backend/utils/adt/ruleutils.c
--- b/src/backend/utils/adt/ruleutils.c
*************** get_name_for_var_field(Var *var, int fie
*** 6731,6747 ****
/*
* 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
! * lookup_rowtype_tupdesc, which will probably fail too, but will ereport
! * an acceptable message.
*/
if (!IsA(var, Var) ||
var->vartype != RECORDOID)
{
! if (get_expr_result_type((Node *) var, NULL, &tupleDesc) != TYPEFUNC_COMPOSITE)
! tupleDesc = lookup_rowtype_tupdesc_copy(exprType((Node *) var),
! exprTypmod((Node *) var));
! Assert(tupleDesc);
/* Got the tupdesc, so we can extract the field name */
Assert(fieldno >= 1 && fieldno <= tupleDesc->natts);
return NameStr(TupleDescAttr(tupleDesc, fieldno - 1)->attname);
--- 6731,6742 ----
/*
* 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_tupdesc().
*/
if (!IsA(var, Var) ||
var->vartype != RECORDOID)
{
! tupleDesc = get_expr_result_tupdesc((Node *) var, false);
/* Got the tupdesc, so we can extract the field name */
Assert(fieldno >= 1 && fieldno <= tupleDesc->natts);
return NameStr(TupleDescAttr(tupleDesc, fieldno - 1)->attname);
*************** get_name_for_var_field(Var *var, int fie
*** 7044,7057 ****
/*
* 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
! * 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)
! tupleDesc = lookup_rowtype_tupdesc_copy(exprType(expr),
! exprTypmod(expr));
! Assert(tupleDesc);
/* Got the tupdesc, so we can extract the field name */
Assert(fieldno >= 1 && fieldno <= tupleDesc->natts);
return NameStr(TupleDescAttr(tupleDesc, fieldno - 1)->attname);
--- 7039,7047 ----
/*
* We now have an expression we can't expand any more, so see if
! * get_expr_result_tupdesc() can do anything with it.
*/
! tupleDesc = get_expr_result_tupdesc(expr, false);
/* Got the tupdesc, so we can extract the field name */
Assert(fieldno >= 1 && fieldno <= tupleDesc->natts);
return NameStr(TupleDescAttr(tupleDesc, fieldno - 1)->attname);
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index b7a14dc..48961e3 100644
*** a/src/backend/utils/cache/lsyscache.c
--- b/src/backend/utils/cache/lsyscache.c
*************** get_typtype(Oid typid)
*** 2398,2409 ****
* type_is_rowtype
*
* Convenience function to determine whether a type OID represents
! * a "rowtype" type --- either RECORD or a named composite type.
*/
bool
type_is_rowtype(Oid typid)
{
! return (typid == RECORDOID || get_typtype(typid) == TYPTYPE_COMPOSITE);
}
/*
--- 2398,2423 ----
* type_is_rowtype
*
* Convenience function to determine whether a type OID represents
! * a "rowtype" type --- either RECORD or a named composite type
! * (including a domain over a named composite type).
*/
bool
type_is_rowtype(Oid typid)
{
! 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;
}
/*
diff --git a/src/backend/utils/cache/typcache.c b/src/backend/utils/cache/typcache.c
index 61ce7dc..7aadc5d 100644
*** a/src/backend/utils/cache/typcache.c
--- b/src/backend/utils/cache/typcache.c
*************** static TypeCacheEntry *firstDomainTypeEn
*** 96,101 ****
--- 96,102 ----
#define TCFLAGS_HAVE_FIELD_EQUALITY 0x004000
#define TCFLAGS_HAVE_FIELD_COMPARE 0x008000
#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
*************** lookup_type_cache(Oid type_id, int flags
*** 747,753 ****
/*
* If requested, get information about a domain type
*/
! if ((flags & TYPECACHE_DOMAIN_INFO) &&
(typentry->flags & TCFLAGS_CHECKED_DOMAIN_CONSTRAINTS) == 0 &&
typentry->typtype == TYPTYPE_DOMAIN)
{
--- 748,762 ----
/*
* If requested, get information about a domain type
*/
! 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->typtype == TYPTYPE_DOMAIN)
{
*************** InitDomainConstraintRef(Oid type_id, Dom
*** 1166,1172 ****
MemoryContext refctx, bool need_exprstate)
{
/* Look up the typcache entry --- we assume it survives indefinitely */
! ref->tcache = lookup_type_cache(type_id, TYPECACHE_DOMAIN_INFO);
ref->need_exprstate = need_exprstate;
/* For safety, establish the callback before acquiring a refcount */
ref->refctx = refctx;
--- 1175,1181 ----
MemoryContext refctx, bool need_exprstate)
{
/* Look up the typcache entry --- we assume it survives indefinitely */
! ref->tcache = lookup_type_cache(type_id, TYPECACHE_DOMAIN_CONSTR_INFO);
ref->need_exprstate = need_exprstate;
/* For safety, establish the callback before acquiring a refcount */
ref->refctx = refctx;
*************** DomainHasConstraints(Oid type_id)
*** 1257,1263 ****
* 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.
*/
! typentry = lookup_type_cache(type_id, TYPECACHE_DOMAIN_INFO);
return (typentry->domainData != NULL);
}
--- 1266,1272 ----
* 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.
*/
! typentry = lookup_type_cache(type_id, TYPECACHE_DOMAIN_CONSTR_INFO);
return (typentry->domainData != NULL);
}
*************** cache_record_field_properties(TypeCacheE
*** 1405,1410 ****
--- 1414,1442 ----
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;
}
*************** lookup_rowtype_tupdesc_copy(Oid type_id,
*** 1619,1624 ****
--- 1651,1703 ----
}
/*
+ * 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.
*/
static uint32
*************** TypeCacheRelCallback(Datum arg, Oid reli
*** 1929,1957 ****
hash_seq_init(&status, TypeCacheHash);
while ((typentry = (TypeCacheEntry *) hash_seq_search(&status)) != NULL)
{
! if (typentry->typtype != TYPTYPE_COMPOSITE)
! continue; /* skip non-composites */
! /* Skip if no match, unless we're zapping all composite types */
! if (relid != typentry->typrelid && relid != InvalidOid)
! continue;
! /* Delete tupdesc if we have it */
! if (typentry->tupDesc != NULL)
{
/*
! * Release our refcount, and free the tupdesc if none remain.
! * (Can't use DecrTupleDescRefCount because this reference is not
! * logged in current resource owner.)
*/
! Assert(typentry->tupDesc->tdrefcount > 0);
! if (--typentry->tupDesc->tdrefcount == 0)
! FreeTupleDesc(typentry->tupDesc);
! typentry->tupDesc = NULL;
}
-
- /* Reset equality/comparison/hashing validity information */
- typentry->flags = 0;
}
}
--- 2008,2047 ----
hash_seq_init(&status, TypeCacheHash);
while ((typentry = (TypeCacheEntry *) hash_seq_search(&status)) != NULL)
{
! if (typentry->typtype == TYPTYPE_COMPOSITE)
! {
! /* Skip if no match, unless we're zapping all composite types */
! if (relid != typentry->typrelid && relid != InvalidOid)
! continue;
! /* Delete tupdesc if we have it */
! if (typentry->tupDesc != NULL)
! {
! /*
! * Release our refcount, and free the tupdesc if none remain.
! * (Can't use DecrTupleDescRefCount because this reference is
! * not logged in current resource owner.)
! */
! Assert(typentry->tupDesc->tdrefcount > 0);
! if (--typentry->tupDesc->tdrefcount == 0)
! FreeTupleDesc(typentry->tupDesc);
! typentry->tupDesc = NULL;
! }
! /* Reset equality/comparison/hashing validity information */
! 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;
}
}
}
diff --git a/src/backend/utils/fmgr/funcapi.c b/src/backend/utils/fmgr/funcapi.c
index 9c3f451..f4a3cce 100644
*** a/src/backend/utils/fmgr/funcapi.c
--- b/src/backend/utils/fmgr/funcapi.c
*************** static TypeFuncClass internal_get_result
*** 39,45 ****
static bool resolve_polymorphic_tupdesc(TupleDesc tupdesc,
oidvector *declared_args,
Node *call_expr);
! static TypeFuncClass get_type_func_class(Oid typid);
/*
--- 39,45 ----
static bool resolve_polymorphic_tupdesc(TupleDesc tupdesc,
oidvector *declared_args,
Node *call_expr);
! static TypeFuncClass get_type_func_class(Oid typid, Oid *base_typeid);
/*
*************** get_expr_result_type(Node *expr,
*** 246,259 ****
{
/* handle as a generic expression; no chance to resolve RECORD */
Oid typid = exprType(expr);
if (resultTypeId)
*resultTypeId = typid;
if (resultTupleDesc)
*resultTupleDesc = NULL;
! result = get_type_func_class(typid);
! if (result == TYPEFUNC_COMPOSITE && resultTupleDesc)
! *resultTupleDesc = lookup_rowtype_tupdesc_copy(typid, -1);
}
return result;
--- 246,262 ----
{
/* handle as a generic expression; no chance to resolve RECORD */
Oid typid = exprType(expr);
+ Oid base_typid;
if (resultTypeId)
*resultTypeId = typid;
if (resultTupleDesc)
*resultTupleDesc = NULL;
! result = get_type_func_class(typid, &base_typid);
! if ((result == TYPEFUNC_COMPOSITE ||
! result == TYPEFUNC_COMPOSITE_DOMAIN) &&
! resultTupleDesc)
! *resultTupleDesc = lookup_rowtype_tupdesc_copy(base_typid, -1);
}
return result;
*************** internal_get_result_type(Oid funcid,
*** 296,301 ****
--- 299,305 ----
HeapTuple tp;
Form_pg_proc procform;
Oid rettype;
+ Oid base_rettype;
TupleDesc tupdesc;
/* First fetch the function's pg_proc row to inspect its rettype */
*************** internal_get_result_type(Oid funcid,
*** 363,374 ****
*resultTupleDesc = NULL; /* default result */
/* Classify the result type */
! result = get_type_func_class(rettype);
switch (result)
{
case TYPEFUNC_COMPOSITE:
if (resultTupleDesc)
! *resultTupleDesc = lookup_rowtype_tupdesc_copy(rettype, -1);
/* Named composite types can't have any polymorphic columns */
break;
case TYPEFUNC_SCALAR:
--- 367,379 ----
*resultTupleDesc = NULL; /* default result */
/* Classify the result type */
! result = get_type_func_class(rettype, &base_rettype);
switch (result)
{
case TYPEFUNC_COMPOSITE:
+ case TYPEFUNC_COMPOSITE_DOMAIN:
if (resultTupleDesc)
! *resultTupleDesc = lookup_rowtype_tupdesc_copy(base_rettype, -1);
/* Named composite types can't have any polymorphic columns */
break;
case TYPEFUNC_SCALAR:
*************** internal_get_result_type(Oid funcid,
*** 394,399 ****
--- 399,444 ----
}
/*
+ * 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,
* replace any polymorphic columns (ANYELEMENT etc) with correct data types
* deduced from the input arguments. Returns TRUE if able to deduce all types,
*************** resolve_polymorphic_argtypes(int numargs
*** 741,763 ****
/*
* get_type_func_class
* Given the type OID, obtain its TYPEFUNC classification.
*
* This is intended to centralize a bunch of formerly ad-hoc code for
* classifying types. The categories used here are useful for deciding
* how to handle functions returning the datatype.
*/
static TypeFuncClass
! get_type_func_class(Oid typid)
{
switch (get_typtype(typid))
{
case TYPTYPE_COMPOSITE:
return TYPEFUNC_COMPOSITE;
case TYPTYPE_BASE:
- case TYPTYPE_DOMAIN:
case TYPTYPE_ENUM:
case TYPTYPE_RANGE:
return TYPEFUNC_SCALAR;
case TYPTYPE_PSEUDO:
if (typid == RECORDOID)
return TYPEFUNC_RECORD;
--- 786,816 ----
/*
* get_type_func_class
* 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
* classifying types. The categories used here are useful for deciding
* how to handle functions returning the datatype.
*/
static TypeFuncClass
! get_type_func_class(Oid typid, Oid *base_typeid)
{
+ *base_typeid = typid;
+
switch (get_typtype(typid))
{
case TYPTYPE_COMPOSITE:
return TYPEFUNC_COMPOSITE;
case TYPTYPE_BASE:
case TYPTYPE_ENUM:
case TYPTYPE_RANGE:
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:
if (typid == RECORDOID)
return TYPEFUNC_RECORD;
*************** RelationNameGetTupleDesc(const char *rel
*** 1320,1335 ****
TupleDesc
TypeGetTupleDesc(Oid typeoid, List *colaliases)
{
! TypeFuncClass functypclass = get_type_func_class(typeoid);
TupleDesc tupdesc = NULL;
/*
! * Build a suitable tupledesc representing the output rows
*/
if (functypclass == TYPEFUNC_COMPOSITE)
{
/* Composite data type, e.g. a table's row type */
! tupdesc = lookup_rowtype_tupdesc_copy(typeoid, -1);
if (colaliases != NIL)
{
--- 1373,1392 ----
TupleDesc
TypeGetTupleDesc(Oid typeoid, List *colaliases)
{
! Oid base_typeoid;
! TypeFuncClass functypclass = get_type_func_class(typeoid, &base_typeoid);
TupleDesc tupdesc = NULL;
/*
! * 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)
{
/* Composite data type, e.g. a table's row type */
! tupdesc = lookup_rowtype_tupdesc_copy(base_typeoid, -1);
if (colaliases != NIL)
{
diff --git a/src/include/access/htup_details.h b/src/include/access/htup_details.h
index fa04a63..b0d4c54 100644
*** a/src/include/access/htup_details.h
--- b/src/include/access/htup_details.h
*************** typedef struct DatumTupleFields
*** 134,139 ****
--- 134,144 ----
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
* widen to 64 bits.
*/
diff --git a/src/include/access/tupdesc.h b/src/include/access/tupdesc.h
index c15610e..2be5af1 100644
*** a/src/include/access/tupdesc.h
--- b/src/include/access/tupdesc.h
*************** typedef struct tupleConstr
*** 60,65 ****
--- 60,71 ----
* row type, or a value >= 0 to allow the rowtype to be looked up in the
* 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)
* are reference-counted: they can be deleted when their reference count goes
* to zero. Tuple descriptors created by the executor need no reference
diff --git a/src/include/funcapi.h b/src/include/funcapi.h
index 951af2a..7c52de9 100644
*** a/src/include/funcapi.h
--- b/src/include/funcapi.h
*************** typedef struct FuncCallContext
*** 143,148 ****
--- 143,152 ----
* get_call_result_type. Note: the cases in which rowtypes cannot be
* determined are different from the cases for get_call_result_type.
* 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.
*----------
*/
*************** typedef enum TypeFuncClass
*** 151,156 ****
--- 155,161 ----
{
TYPEFUNC_SCALAR, /* scalar result type */
TYPEFUNC_COMPOSITE, /* determinable rowtype result */
+ TYPEFUNC_COMPOSITE_DOMAIN, /* domain over determinable rowtype result */
TYPEFUNC_RECORD, /* indeterminate rowtype result */
TYPEFUNC_OTHER /* bogus type, eg pseudotype */
} TypeFuncClass;
*************** extern TypeFuncClass get_func_result_typ
*** 165,170 ****
--- 170,177 ----
Oid *resultTypeId,
TupleDesc *resultTupleDesc);
+ extern TupleDesc get_expr_result_tupdesc(Node *expr, bool noError);
+
extern bool resolve_polymorphic_argtypes(int numargs, Oid *argtypes,
char *argmodes,
Node *call_expr);
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index ccb5123..c2929ac 100644
*** a/src/include/nodes/primnodes.h
--- b/src/include/nodes/primnodes.h
*************** typedef struct Var
*** 166,172 ****
Index varno; /* index of this var's relation in the range
* table, or INNER_VAR/OUTER_VAR/INDEX_VAR */
AttrNumber varattno; /* attribute number of this var, or zero for
! * all */
Oid vartype; /* pg_type OID for the type of this var */
int32 vartypmod; /* pg_attribute typmod value */
Oid varcollid; /* OID of collation, or InvalidOid if none */
--- 166,172 ----
Index varno; /* index of this var's relation in the range
* table, or INNER_VAR/OUTER_VAR/INDEX_VAR */
AttrNumber varattno; /* attribute number of this var, or zero for
! * all attrs ("whole-row Var") */
Oid vartype; /* pg_type OID for the type of this var */
int32 vartypmod; /* pg_attribute typmod value */
Oid varcollid; /* OID of collation, or InvalidOid if none */
*************** typedef struct FieldSelect
*** 755,760 ****
--- 755,763 ----
* the assign case of ArrayRef, this is used to implement UPDATE of a
* 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
* fields. The parser only generates FieldStores with single-element lists,
* but the planner will collapse multiple updates of the same base column
*************** typedef struct ArrayCoerceExpr
*** 849,855 ****
* 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
* used to convert a whole-row value of an inheritance child table into a
! * valid whole-row value of its parent table's rowtype.
* ----------------
*/
--- 852,859 ----
* 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
* used to convert a whole-row value of an inheritance child table into a
! * 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).
* ----------------
*/
*************** typedef struct RowExpr
*** 987,992 ****
--- 991,999 ----
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
* associated with specific RECORD types at runtime, it will differ for
* different backends, and so cannot safely be stored in stored
diff --git a/src/include/parser/parse_type.h b/src/include/parser/parse_type.h
index 7b843d0..af1e314 100644
*** a/src/include/parser/parse_type.h
--- b/src/include/parser/parse_type.h
*************** extern Oid typeTypeCollation(Type typ);
*** 46,55 ****
extern Datum stringTypeDatum(Type tp, char *string, int32 atttypmod);
extern Oid typeidTypeRelid(Oid type_id);
extern TypeName *typeStringToTypeName(const char *str);
extern void parseTypeString(const char *str, Oid *typeid_p, int32 *typmod_p, bool missing_ok);
! #define ISCOMPLEX(typeid) (typeidTypeRelid(typeid) != InvalidOid)
#endif /* PARSE_TYPE_H */
--- 46,57 ----
extern Datum stringTypeDatum(Type tp, char *string, int32 atttypmod);
extern Oid typeidTypeRelid(Oid type_id);
+ extern Oid typeOrDomainTypeRelid(Oid type_id);
extern TypeName *typeStringToTypeName(const char *str);
extern void parseTypeString(const char *str, Oid *typeid_p, int32 *typmod_p, bool missing_ok);
! /* true if typeid is composite, or domain over composite, but not RECORD */
! #define ISCOMPLEX(typeid) (typeOrDomainTypeRelid(typeid) != InvalidOid)
#endif /* PARSE_TYPE_H */
diff --git a/src/include/utils/typcache.h b/src/include/utils/typcache.h
index 41b645a..ea799a8 100644
*** a/src/include/utils/typcache.h
--- b/src/include/utils/typcache.h
*************** typedef struct TypeCacheEntry
*** 92,97 ****
--- 92,104 ----
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
* if domain has no constraints, or if information hasn't been requested.
*/
*************** typedef struct TypeCacheEntry
*** 123,131 ****
#define TYPECACHE_BTREE_OPFAMILY 0x0200
#define TYPECACHE_HASH_OPFAMILY 0x0400
#define TYPECACHE_RANGE_INFO 0x0800
! #define TYPECACHE_DOMAIN_INFO 0x1000
! #define TYPECACHE_HASH_EXTENDED_PROC 0x2000
! #define TYPECACHE_HASH_EXTENDED_PROC_FINFO 0x4000
/*
* Callers wishing to maintain a long-lived reference to a domain's constraint
--- 130,139 ----
#define TYPECACHE_BTREE_OPFAMILY 0x0200
#define TYPECACHE_HASH_OPFAMILY 0x0400
#define TYPECACHE_RANGE_INFO 0x0800
! #define TYPECACHE_DOMAIN_BASE_INFO 0x1000
! #define TYPECACHE_DOMAIN_CONSTR_INFO 0x2000
! #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
*************** extern TupleDesc lookup_rowtype_tupdesc_
*** 163,168 ****
--- 171,179 ----
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 int compare_values_of_enum(TypeCacheEntry *tcache, Oid arg1, Oid arg2);
diff --git a/src/test/regress/expected/domain.out b/src/test/regress/expected/domain.out
index 1e62c57..f7f3948 100644
*** a/src/test/regress/expected/domain.out
--- b/src/test/regress/expected/domain.out
*************** select pg_typeof('{1,2,3}'::dia || 42);
*** 198,203 ****
--- 198,291 ----
(1 row)
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
create type comptype as (r float8, i float8);
create domain dcomptypea as comptype[];
*************** insert into ddtest2 values('{(-1)}');
*** 762,767 ****
--- 850,863 ----
alter domain posint add constraint c1 check(value >= 0);
ERROR: cannot alter type "posint" because column "ddtest2.f1" uses it
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
create domain ddtest1d as ddtest1[];
create table ddtest2(f1 ddtest1d);
diff --git a/src/test/regress/expected/json.out b/src/test/regress/expected/json.out
index d7abae9..f57d1ab 100644
*** a/src/test/regress/expected/json.out
--- b/src/test/regress/expected/json.out
*************** create type jpop as (a text, b int, c ti
*** 1316,1321 ****
--- 1316,1323 ----
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_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 (
i int,
ia _int4,
*************** SELECT rec FROM json_populate_record(
*** 1740,1745 ****
--- 1742,1771 ----
(abc,3,"Thu Jan 02 00:00:00 2003")
(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
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
*************** select * from json_populate_recordset(ro
*** 1806,1811 ****
--- 1832,1862 ----
{"z":true} | 3 | Fri Jan 20 10:42:53 2012
(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()
CREATE TEMP TABLE jspoptest (js json);
INSERT INTO jspoptest
*************** DROP TYPE jsrec_i_not_null;
*** 1828,1833 ****
--- 1879,1886 ----
DROP DOMAIN js_int_not_null;
DROP DOMAIN js_int_array_1d;
DROP DOMAIN js_int_array_2d;
+ DROP DOMAIN j_ordered_pair;
+ DROP TYPE j_unordered_pair;
--json_typeof() function
select value, json_typeof(value)
from (values (json '123.4'),
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index dcea6a4..7f40ced 100644
*** a/src/test/regress/expected/jsonb.out
--- b/src/test/regress/expected/jsonb.out
*************** CREATE TYPE jbpop AS (a text, b int, c t
*** 1900,1905 ****
--- 1900,1907 ----
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_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 (
i int,
ia _int4,
*************** SELECT rec FROM jsonb_populate_record(
*** 2324,2329 ****
--- 2326,2355 ----
(abc,3,"Thu Jan 02 00:00:00 2003")
(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
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
*************** SELECT * FROM jsonb_populate_recordset(r
*** 2383,2388 ****
--- 2409,2439 ----
{"z": true} | 3 | Fri Jan 20 10:42:53 2012
(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
select * from jsonb_to_record('{"a":1,"b":"foo","c":"bar"}')
as x(a int, b text, d text);
*************** DROP TYPE jsbrec_i_not_null;
*** 2482,2487 ****
--- 2533,2540 ----
DROP DOMAIN jsb_int_not_null;
DROP DOMAIN jsb_int_array_1d;
DROP DOMAIN jsb_int_array_2d;
+ DROP DOMAIN jb_ordered_pair;
+ DROP TYPE jb_unordered_pair;
-- indexing
SELECT count(*) FROM testjsonb WHERE j @> '{"wait":null}';
count
diff --git a/src/test/regress/sql/domain.sql b/src/test/regress/sql/domain.sql
index 8fb3e20..5201f00 100644
*** a/src/test/regress/sql/domain.sql
--- b/src/test/regress/sql/domain.sql
*************** select pg_typeof('{1,2,3}'::dia || 42);
*** 120,125 ****
--- 120,164 ----
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
create type comptype as (r float8, i float8);
*************** insert into ddtest2 values('{(-1)}');
*** 500,505 ****
--- 539,552 ----
alter domain posint add constraint c1 check(value >= 0);
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
create domain ddtest1d as ddtest1[];
create table ddtest2(f1 ddtest1d);
diff --git a/src/test/regress/sql/json.sql b/src/test/regress/sql/json.sql
index 506e3a8..0859353 100644
*** a/src/test/regress/sql/json.sql
--- b/src/test/regress/sql/json.sql
*************** CREATE DOMAIN js_int_not_null AS int
*** 388,393 ****
--- 388,396 ----
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 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 (
i int,
ia _int4,
*************** SELECT rec FROM json_populate_record(
*** 516,521 ****
--- 519,533 ----
'{"rec": {"a": "abc", "c": "01.02.2003", "x": 43.2}}'
) 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
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(nu
*** 532,537 ****
--- 544,558 ----
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;
+ -- 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()
CREATE TEMP TABLE jspoptest (js json);
*************** DROP TYPE jsrec_i_not_null;
*** 550,555 ****
--- 571,578 ----
DROP DOMAIN js_int_not_null;
DROP DOMAIN js_int_array_1d;
DROP DOMAIN js_int_array_2d;
+ DROP DOMAIN j_ordered_pair;
+ DROP TYPE j_unordered_pair;
--json_typeof() function
select value, json_typeof(value)
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 57fff3b..9cad707 100644
*** a/src/test/regress/sql/jsonb.sql
--- b/src/test/regress/sql/jsonb.sql
*************** CREATE DOMAIN jsb_int_not_null AS int
*** 488,493 ****
--- 488,496 ----
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 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 (
i int,
ia _int4,
*************** SELECT rec FROM jsonb_populate_record(
*** 616,621 ****
--- 619,633 ----
'{"rec": {"a": "abc", "c": "01.02.2003", "x": 43.2}}'
) 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
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(N
*** 628,633 ****
--- 640,654 ----
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;
+ -- 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
select * from jsonb_to_record('{"a":1,"b":"foo","c":"bar"}')
*************** DROP TYPE jsbrec_i_not_null;
*** 673,678 ****
--- 694,701 ----
DROP DOMAIN jsb_int_not_null;
DROP DOMAIN jsb_int_array_1d;
DROP DOMAIN jsb_int_array_2d;
+ DROP DOMAIN jb_ordered_pair;
+ DROP TYPE jb_unordered_pair;
-- indexing
SELECT count(*) FROM testjsonb WHERE j @> '{"wait":null}';