Re: Domain Support -- another round

From: Peter Eisentraut <peter_e(at)gmx(dot)net>
To: Rod Taylor <rbt(at)zort(dot)ca>
Cc: <pgsql-patches(at)postgresql(dot)org>
Subject: Re: Domain Support -- another round
Date: 2002-03-12 00:14:46
Message-ID: Pine.LNX.4.30.0203111912100.690-100000@peter.localdomain
Views: Raw Message | Whole Thread | Download mbox | Resend email
Thread:
Lists: pgsql-hackers pgsql-patches

Random nitpicking below. Also, have you created a regression test?

> diff -rc pgsql.orig/doc/src/sgml/catalogs.sgml pgsqldomain/doc/src/sgml/catalogs.sgml
> *** pgsql.orig/doc/src/sgml/catalogs.sgml Thu Mar 7 11:35:32 2002
> --- pgsqldomain/doc/src/sgml/catalogs.sgml Thu Mar 7 22:24:23 2002
> ***************
> *** 2511,2516 ****
> --- 2511,2563 ----
> </row>
>
> <row>
> + <entry>typbasetype</entry>
> + <entry><type>oid</type></entry>
> + <entry></entry>
> + <entry><para>
> + <structfield>typbasetype</structfield> is the type that this one is based
> + off of. Normally references the domains parent type, and is 0 otherwise.

"based on"

> + </para></entry>
> + </row>
> +
> + <row>
> + <entry>typnotnull</entry>
> + <entry><type>boolean</type></entry>
> + <entry></entry>
> + <entry><para>
> + <structfield>typnotnull</structfield> represents a NOT NULL
> + constraint on a type. Normally used only for domains.

And unnormally...?

> + </para></entry>
> + </row>
> +
> + <row>
> + <entry>typmod</entry>
> + <entry><type>integer</type></entry>
> + <entry></entry>
> + <entry><para>
> + <structfield>typmod</structfield> records type-specific data
> + supplied at table creation time (for example, the maximum
> + length of a <type>varchar</type> column). It is passed to
> + type-specific input and output functions as the third
> + argument. The value will generally be -1 for types that do not
> + need typmod. This data is copied to
> + <structfield>pg_attribute.atttypmod</structfield> on creation
> + of a table using a domain as it's field type.
> + </para></entry>
> + </row>
> +
> + <row>
> + <entry>typdefaultbin</entry>
> + <entry><type>text</type></entry>
> + <entry></entry>
> + <entry><para>
> + <structfield>typdefaultbin</structfield> is NULL for types without a
> + default value. If it's not NULL, it contains the internal string
> + representation of the default expression node.
> + </para></entry>
> + </row>
> +
> + <row>
> <entry>typdefault</entry>
> <entry><type>text</type></entry>
> <entry></entry>
> diff -rc pgsql.orig/doc/src/sgml/ref/allfiles.sgml pgsqldomain/doc/src/sgml/ref/allfiles.sgml
> *** pgsql.orig/doc/src/sgml/ref/allfiles.sgml Thu Mar 7 11:35:32 2002
> --- pgsqldomain/doc/src/sgml/ref/allfiles.sgml Thu Mar 7 22:24:23 2002
> ***************
> *** 52,57 ****
> --- 52,58 ----
> <!entity createAggregate system "create_aggregate.sgml">
> <!entity createConstraint system "create_constraint.sgml">
> <!entity createDatabase system "create_database.sgml">
> + <!entity createDomain system "create_domain.sgml">

I don't see this file included.

> <!entity createFunction system "create_function.sgml">
> <!entity createGroup system "create_group.sgml">
> <!entity createIndex system "create_index.sgml">
> ***************
> *** 69,74 ****
> --- 70,76 ----
> <!entity delete system "delete.sgml">
> <!entity dropAggregate system "drop_aggregate.sgml">
> <!entity dropDatabase system "drop_database.sgml">
> + <!entity dropDomain system "drop_domain.sgml">
> <!entity dropFunction system "drop_function.sgml">
> <!entity dropGroup system "drop_group.sgml">
> <!entity dropIndex system "drop_index.sgml">
> diff -rc pgsql.orig/doc/src/sgml/ref/comment.sgml pgsqldomain/doc/src/sgml/ref/comment.sgml
> *** pgsql.orig/doc/src/sgml/ref/comment.sgml Thu Mar 7 11:35:33 2002
> --- pgsqldomain/doc/src/sgml/ref/comment.sgml Thu Mar 7 22:24:23 2002
> ***************
> *** 25,31 ****
> <synopsis>
> COMMENT ON
> [
> ! [ DATABASE | INDEX | RULE | SEQUENCE | TABLE | TYPE | VIEW ] <replaceable class="PARAMETER">object_name</replaceable> |
> COLUMN <replaceable class="PARAMETER">table_name</replaceable>.<replaceable class="PARAMETER">column_name</replaceable> |
> AGGREGATE <replaceable class="PARAMETER">agg_name</replaceable> (<replaceable class="PARAMETER">agg_type</replaceable>) |
> FUNCTION <replaceable class="PARAMETER">func_name</replaceable> (<replaceable class="PARAMETER">arg1</replaceable>, <replaceable class="PARAMETER">arg2</replaceable>, ...) |
> --- 25,31 ----
> <synopsis>
> COMMENT ON
> [
> ! [ DATABASE | DOMAIN | INDEX | RULE | SEQUENCE | TABLE | TYPE | VIEW ] <replaceable class="PARAMETER">object_name</replaceable> |
> COLUMN <replaceable class="PARAMETER">table_name</replaceable>.<replaceable class="PARAMETER">column_name</replaceable> |
> AGGREGATE <replaceable class="PARAMETER">agg_name</replaceable> (<replaceable class="PARAMETER">agg_type</replaceable>) |
> FUNCTION <replaceable class="PARAMETER">func_name</replaceable> (<replaceable class="PARAMETER">arg1</replaceable>, <replaceable class="PARAMETER">arg2</replaceable>, ...) |
> ***************
> *** 33,39 ****
> TRIGGER <replaceable class="PARAMETER">trigger_name</replaceable> ON <replaceable class="PARAMETER">table_name</replaceable>
> ] IS <replaceable class="PARAMETER">'text'</replaceable>
> </synopsis>
> !
> <refsect2 id="R2-SQL-COMMENT-1">
> <refsect2info>
> <date>1999-10-25</date>
> --- 33,39 ----
> TRIGGER <replaceable class="PARAMETER">trigger_name</replaceable> ON <replaceable class="PARAMETER">table_name</replaceable>
> ] IS <replaceable class="PARAMETER">'text'</replaceable>
> </synopsis>
> !
> <refsect2 id="R2-SQL-COMMENT-1">
> <refsect2info>
> <date>1999-10-25</date>
> ***************
> *** 64,70 ****
> </variablelist>
> </para>
> </refsect2>
> !
> <refsect2 id="R2-SQL-COMMENT-2">
> <refsect2info>
> <date>1998-09-08</date>
> --- 64,70 ----
> </variablelist>
> </para>
> </refsect2>
> !
> <refsect2 id="R2-SQL-COMMENT-2">
> <refsect2info>
> <date>1998-09-08</date>
> ***************
> *** 99,105 ****
> </title>
> <para>
> <command>COMMENT</command> stores a comment about a database object.
> ! Comments can be
> easily retrieved with <command>psql</command>'s
> <command>\dd</command>, <command>\d+</command>, or <command>\l+</command>
> commands. Other user interfaces to retrieve comments can be built atop
> --- 99,105 ----
> </title>
> <para>
> <command>COMMENT</command> stores a comment about a database object.
> ! Comments can be
> easily retrieved with <command>psql</command>'s
> <command>\dd</command>, <command>\d+</command>, or <command>\l+</command>
> commands. Other user interfaces to retrieve comments can be built atop
> ***************
> *** 141,146 ****
> --- 141,147 ----
>
> <programlisting>
> COMMENT ON DATABASE my_database IS 'Development Database';
> + COMMENT ON DOMAIN my_domain IS 'Domains are like abstracted fields';

This comment describes domains in general, not a specific domain.

> COMMENT ON INDEX my_index IS 'Enforces uniqueness on employee id';
> COMMENT ON RULE my_rule IS 'Logs UPDATES of employee records';
> COMMENT ON SEQUENCE my_sequence IS 'Used to generate primary keys';
> ***************
> *** 155,166 ****
> </programlisting>
> </para>
> </refsect1>
> !
> <refsect1 id="R1-SQL-COMMENT-3">
> <title>
> Compatibility
> </title>
> !
> <refsect2 id="R2-SQL-COMMENT-4">
> <refsect2info>
> <date>1998-09-08</date>
> --- 156,167 ----
> </programlisting>
> </para>
> </refsect1>
> !
> <refsect1 id="R1-SQL-COMMENT-3">
> <title>
> Compatibility
> </title>
> !
> <refsect2 id="R2-SQL-COMMENT-4">
> <refsect2info>
> <date>1998-09-08</date>
> diff -rc pgsql.orig/doc/src/sgml/reference.sgml pgsqldomain/doc/src/sgml/reference.sgml
> *** pgsql.orig/doc/src/sgml/reference.sgml Thu Mar 7 11:35:32 2002
> --- pgsqldomain/doc/src/sgml/reference.sgml Thu Mar 7 22:24:23 2002
> ***************
> *** 61,66 ****
> --- 61,67 ----
> &createAggregate;
> &createConstraint;
> &createDatabase;
> + &createDomain;
> &createFunction;
> &createGroup;
> &createIndex;
> ***************
> *** 78,83 ****
> --- 79,85 ----
> &delete;
> &dropAggregate;
> &dropDatabase;
> + &dropDomain;
> &dropFunction;
> &dropGroup;
> &dropIndex;
> ***************
> *** 115,121 ****
> &unlisten;
> &update;
> &vacuum;
> !
> </reference>
>
> <!--
> --- 117,123 ----
> &unlisten;
> &update;
> &vacuum;
> !
> </reference>
>
> <!--
> diff -rc pgsql.orig/src/backend/catalog/heap.c pgsqldomain/src/backend/catalog/heap.c
> *** pgsql.orig/src/backend/catalog/heap.c Thu Mar 7 11:35:33 2002
> --- pgsqldomain/src/backend/catalog/heap.c Thu Mar 7 22:24:23 2002
> ***************
> *** 49,54 ****
> --- 49,55 ----
> #include "optimizer/planmain.h"
> #include "optimizer/prep.h"
> #include "optimizer/var.h"
> + #include "parser/parse_coerce.h"
> #include "parser/parse_expr.h"
> #include "parser/parse_relation.h"
> #include "parser/parse_target.h"
> ***************
> *** 698,707 ****
> "oidin", /* receive procedure */
> "oidout", /* send procedure */
> NULL, /* array element type - irrelevant */
> NULL, /* default type value - none */
> true, /* passed by value */
> 'i', /* default alignment - same as for OID */
> ! 'p'); /* Not TOASTable */
> }
>
> /* --------------------------------
> --- 699,713 ----
> "oidin", /* receive procedure */
> "oidout", /* send procedure */
> NULL, /* array element type - irrelevant */
> + NULL, /* baseType Name -- typically for domaains */

spello

> NULL, /* default type value - none */
> + NULL, /* default type binary representation */
> true, /* passed by value */
> 'i', /* default alignment - same as for OID */
> ! 'p', /* Not TOASTable */
> ! -1, /* Type mod length */
> ! 0, /* array dimensions for typBaseType */
> ! false); /* Type NOT NULL */
> }
>
> /* --------------------------------
> ***************
> *** 1584,1589 ****
> --- 1590,1599 ----
> int numchecks;
> List *listptr;
>
> + /* Probably shouldn't be null by default */
> + Node *expr = NULL;
> +
> +
> /*
> * Get info about existing constraints.
> */
> ***************
> *** 1614,1681 ****
> foreach(listptr, rawColDefaults)
> {
> RawColumnDefault *colDef = (RawColumnDefault *) lfirst(listptr);
> - Node *expr;
> - Oid type_id;
>
> - Assert(colDef->raw_default != NULL);
>
> ! /*
> ! * Transform raw parsetree to executable expression.
> ! */
> ! expr = transformExpr(pstate, colDef->raw_default, EXPR_COLUMN_FIRST);
>
> ! /*
> ! * Make sure default expr does not refer to any vars.
> ! */
> ! if (contain_var_clause(expr))
> ! elog(ERROR, "cannot use column references in DEFAULT clause");
> !
> ! /*
> ! * No subplans or aggregates, either...
> ! */
> ! if (contain_subplans(expr))
> ! elog(ERROR, "cannot use subselects in DEFAULT clause");
> ! if (contain_agg_clause(expr))
> ! elog(ERROR, "cannot use aggregate functions in DEFAULT clause");
> !
> ! /*
> ! * Check that it will be possible to coerce the expression to the
> ! * column's type. We store the expression without coercion,
> ! * however, to avoid premature coercion in cases like
> ! *
> ! * CREATE TABLE tbl (fld datetime DEFAULT 'now'::text);
> ! *
> ! * NB: this should match the code in optimizer/prep/preptlist.c that
> ! * will actually do the coercion, to ensure we don't accept an
> ! * unusable default expression.
> ! */
> ! type_id = exprType(expr);
> ! if (type_id != InvalidOid)
> ! {
> ! Form_pg_attribute atp = rel->rd_att->attrs[colDef->attnum - 1];
> !
> ! if (type_id != atp->atttypid)
> ! {
> ! if (CoerceTargetExpr(NULL, expr, type_id,
> ! atp->atttypid, atp->atttypmod) == NULL)
> ! elog(ERROR, "Column \"%s\" is of type %s"
> ! " but default expression is of type %s"
> ! "\n\tYou will need to rewrite or cast the expression",
> ! NameStr(atp->attname),
> ! format_type_be(atp->atttypid),
> ! format_type_be(type_id));
> ! }
> ! }
> !
> ! /*
> ! * Might as well try to reduce any constant expressions.
> ! */
> ! expr = eval_const_expressions(expr);
> !
> ! /*
> ! * Must fix opids, in case any operators remain...
> ! */
> ! fix_opids(expr);
>
> /*
> * OK, store it.
> --- 1624,1636 ----
> foreach(listptr, rawColDefaults)
> {
> RawColumnDefault *colDef = (RawColumnDefault *) lfirst(listptr);
>
>
> ! Form_pg_attribute atp = rel->rd_att->attrs[colDef->attnum - 1];
>
> ! expr = cookDefault(pstate, colDef->raw_default
> ! , atp->atttypid, atp->atttypmod
> ! , NameStr(atp->attname));
>
> /*
> * OK, store it.
> ***************
> *** 1891,1896 ****
> --- 1846,1933 ----
> heap_freetuple(reltup);
> heap_close(relrel, RowExclusiveLock);
> }
> +
> + /*
> + * Take a raw default and convert it to a cooked format ready for
> + * storage.
> + *
> + * Parse state, attypid, attypmod and attname are required for
> + * CoerceTargetExpr() and more importantly transformExpr().
> + */
> + Node *
> + cookDefault(ParseState *pstate
> + , Node *raw_default
> + , Oid atttypid
> + , int32 atttypmod
> + , char *attname) {

Stick to the formatting please.

> +
> + Oid type_id;
> + Node *expr;
> +
> + Assert(raw_default != NULL);
> +
> + /*
> + * Transform raw parsetree to executable expression.
> + */
> + expr = transformExpr(pstate, raw_default, EXPR_COLUMN_FIRST);
> +
> + /*
> + * Make sure default expr does not refer to any vars.
> + */
> + if (contain_var_clause(expr))
> + elog(ERROR, "cannot use column references in DEFAULT clause");
> +
> + /*
> + * No subplans or aggregates, either...
> + */
> + if (contain_subplans(expr))
> + elog(ERROR, "cannot use subselects in DEFAULT clause");
> + if (contain_agg_clause(expr))
> + elog(ERROR, "cannot use aggregate functions in DEFAULT clause");
> +
> + /*
> + * Check that it will be possible to coerce the expression to the
> + * column's type. We store the expression without coercion,
> + * however, to avoid premature coercion in cases like
> + *
> + * CREATE TABLE tbl (fld datetime DEFAULT 'now'::text);
> + *
> + * NB: this should match the code in optimizer/prep/preptlist.c that
> + * will actually do the coercion, to ensure we don't accept an
> + * unusable default expression.
> + */
> + type_id = exprType(expr);
> + if (type_id != InvalidOid && atttypid != InvalidOid) {
> + if (type_id != atttypid) {
> +
> + /* Try coercing to the base type of the domain if available */
> + if (CoerceTargetExpr(pstate, expr, type_id,
> + getBaseType(atttypid),
> + atttypmod) == NULL) {
> +
> + elog(ERROR, "Column \"%s\" is of type %s"
> + " but default expression is of type %s"
> + "\n\tYou will need to rewrite or cast the expression",
> + attname,
> + format_type_be(atttypid),
> + format_type_be(type_id));
> + }
> + }
> + }
> +
> + /*
> + * Might as well try to reduce any constant expressions.
> + */
> + expr = eval_const_expressions(expr);
> +
> + /*
> + * Must fix opids, in case any operators remain...
> + */
> + fix_opids(expr);
> +
> + return(expr);
> + }
> +
>
> static void
> RemoveAttrDefaults(Relation rel)

> diff -rc pgsql.orig/src/backend/commands/creatinh.c pgsqldomain/src/backend/commands/creatinh.c
> *** pgsql.orig/src/backend/commands/creatinh.c Thu Mar 7 11:35:34 2002
> --- pgsqldomain/src/backend/commands/creatinh.c Thu Mar 7 23:16:06 2002
> ***************
> *** 39,45 ****
> static void StoreCatalogInheritance(Oid relationId, List *supers);
> static int findAttrByName(const char *attributeName, List *schema);
> static void setRelhassubclassInRelation(Oid relationId, bool relhassubclass);
> !
>
> /* ----------------------------------------------------------------
> * DefineRelation
> --- 39,45 ----
> static void StoreCatalogInheritance(Oid relationId, List *supers);
> static int findAttrByName(const char *attributeName, List *schema);
> static void setRelhassubclassInRelation(Oid relationId, bool relhassubclass);
> ! static List *MergeDomainAttributes(List *schema);
>
> /* ----------------------------------------------------------------
> * DefineRelation
> ***************
> *** 70,75 ****
> --- 70,82 ----
> StrNCpy(relname, stmt->relname, NAMEDATALEN);
>
> /*
> + * Inherit domain attributes into the known columns before table inheritance
> + * applies it's changes otherwise we risk adding double constraints
> + * to a domain thats inherited.
> + */
> + schema = MergeDomainAttributes(schema);
> +
> + /*
> * Look up inheritance ancestors and generate relation schema,
> * including inherited attributes.
> */
> ***************
> *** 235,240 ****
> --- 242,307 ----
> {
> AssertArg(name);
> heap_truncate(name);
> + }
> +
> +
> + /*
> + * MergeDomainAttributes
> + * Returns a new table schema with the constraints, types, and other
> + * attributes of the domain resolved for fields using the domain as
> + * their type.

I didn't know we had schemas yet. You should probably not overload that
term to mean "a list of database objects".

> + *
> + * Defaults are pulled out by the table attribute as required, similar to
> + * how all types defaults are processed.
> + */
> + static List *
> + MergeDomainAttributes(List *schema)
> + {
> + List *entry;
> +
> + /*
> + * Loop through the table elements supplied. These should
> + * never include inherited domains else they'll be
> + * double (or more) processed.
> + */
> + foreach(entry, schema)
> + {
> + ColumnDef *coldef = lfirst(entry);
> + HeapTuple tuple;
> + Form_pg_type typeTup;
> +
> +
> + tuple = SearchSysCache(TYPENAME,
> + CStringGetDatum(coldef->typename->name),
> + 0,0,0);
> +
> + if (!HeapTupleIsValid(tuple))
> + elog(ERROR, "MergeDomainAttributes: Type %s does not exist",
> + coldef->typename->name);
> +
> + typeTup = (Form_pg_type) GETSTRUCT(tuple);
> + if (typeTup->typtype == 'd') {
> + /*
> + * This is a domain, lets force the properties of the domain on to
> + * the new column.
> + */
> +
> + /* Enforce the typmod value */
> + coldef->typename->typmod = typeTup->typmod;
> +
> + /* Enforce type NOT NULL || column definition NOT NULL -> NOT NULL */
> + coldef->is_not_null |= typeTup->typnotnull;
> +
> + /* Enforce the element type in the event the domain is an array
> + *
> + * BUG: How do we fill out arrayBounds and attrname from typelem and typNDimms?
> + */
> +
> + }
> + ReleaseSysCache(tuple);
> + }
> +
> + return schema;
> }
>
> /*----------
> diff -rc pgsql.orig/src/backend/commands/define.c pgsqldomain/src/backend/commands/define.c
> *** pgsql.orig/src/backend/commands/define.c Thu Mar 7 11:35:34 2002
> --- pgsqldomain/src/backend/commands/define.c Thu Mar 7 22:24:23 2002
> ***************
> *** 40,45 ****
> --- 40,46 ----
>
> #include "access/heapam.h"
> #include "catalog/catname.h"
> + #include "catalog/heap.h"
> #include "catalog/pg_aggregate.h"
> #include "catalog/pg_language.h"
> #include "catalog/pg_operator.h"
> ***************
> *** 476,481 ****
> --- 477,798 ----
> }
>
> /*
> + * DefineDomain
> + * Registers a new domain.
> + */
> + void
> + DefineDomain(CreateDomainStmt *stmt)
> + {
> + int16 internalLength = -1; /* int2 */
> + int16 externalLength = -1; /* int2 */
> + char *inputName = NULL;
> + char *outputName = NULL;
> + char *sendName = NULL;
> + char *receiveName = NULL;
> +
> + /*
> + * Domains store the external representation in defaultValue
> + * and the interal Node representation in defaultValueBin
> + */
> + char *defaultValue = NULL;
> + char *defaultValueBin = NULL;
> +
> + bool byValue = false;
> + char delimiter = DEFAULT_TYPDELIM;
> + char alignment = 'i'; /* default alignment */
> + char storage = 'p'; /* default TOAST storage method */
> + char typtype;
> + Datum datum;
> + bool typNotNull = false;
> + char *elemName = NULL;
> + int32 typNDims = 0; /* No array dimensions by default */
> +
> + bool isnull;
> + Relation pg_type_rel;
> + TupleDesc pg_type_dsc;
> + HeapTuple typeTup;
> + char *typeName = stmt->typename->name;
> +
> + List *listptr;
> + List *schema = stmt->constraints;
> +
> + /*
> + * Domainnames, unlike typenames don't need to account for the '_'
> + * prefix. So they can be one character longer.
> + */
> + if (strlen(stmt->domainname) > (NAMEDATALEN - 1))
> + elog(ERROR, "CREATE DOMAIN: domain names must be %d characters or less",
> + NAMEDATALEN - 1);
> +
> +
> + /* Test for existing Domain (or type) of that name */
> + typeTup = SearchSysCache( TYPENAME
> + , PointerGetDatum(stmt->domainname)
> + , 0, 0, 0
> + );
> +
> + if (HeapTupleIsValid(typeTup))
> + {
> + elog(ERROR, "CREATE DOMAIN: domain or type %s already exists",
> + stmt->domainname);
> + }
> +
> + /*
> + * Get the information about old types
> + */
> + pg_type_rel = heap_openr(TypeRelationName, RowExclusiveLock);
> + pg_type_dsc = RelationGetDescr(pg_type_rel);
> +
> +
> + /*
> + * When the type is an array for some reason we don't actually receive
> + * the name here. We receive the base types name. Lets set Dims while
> + * were at it.
> + */
> + if (stmt->typename->arrayBounds > 0) {
> + typeName = makeArrayTypeName(stmt->typename->name);
> +
> + typNDims = length(stmt->typename->arrayBounds);
> + }
> +
> +
> + typeTup = SearchSysCache( TYPENAME
> + , PointerGetDatum(typeName)
> + , 0, 0, 0
> + );
> +
> + if (!HeapTupleIsValid(typeTup))
> + {
> + elog(ERROR, "CREATE DOMAIN: type %s does not exist",
> + stmt->typename->name);
> + }
> +
> +
> + /* Check that this is a basetype */
> + typtype = DatumGetChar(heap_getattr(typeTup, Anum_pg_type_typtype, pg_type_dsc, &isnull));
> + Assert(!isnull);
> +
> + /*
> + * What we really don't want is domains of domains. This could cause all sorts
> + * of neat issues if we allow that.
> + *
> + * With testing, we may determine complex types should be allowed
> + */
> + if (typtype != 'b') {
> + elog(ERROR, "DefineDomain: %s is not a basetype", stmt->typename->name);
> + }
> +
> + /* passed by value */
> + byValue = DatumGetBool(heap_getattr(typeTup, Anum_pg_type_typbyval, pg_type_dsc, &isnull));
> + Assert(!isnull);

You don't have to use heap_getattr here. You can use

byValue = ((Form_pg_type) GETSTRUCT(typeTup))->typbyval

Same for all the other ones that are fixed-length.

> +
> + /* Required Alignment */
> + alignment = DatumGetChar(heap_getattr(typeTup, Anum_pg_type_typalign, pg_type_dsc, &isnull));
> + Assert(!isnull);
> +
> + /* Storage Length */
> + internalLength = DatumGetInt16(heap_getattr(typeTup, Anum_pg_type_typlen, pg_type_dsc, &isnull));
> + Assert(!isnull);
> +
> + /* External Length (unused) */
> + externalLength = DatumGetInt16(heap_getattr(typeTup, Anum_pg_type_typprtlen, pg_type_dsc, &isnull));
> + Assert(!isnull);
> +
> + /* Array element Delimiter */
> + delimiter = DatumGetChar(heap_getattr(typeTup, Anum_pg_type_typdelim, pg_type_dsc, &isnull));
> + Assert(!isnull);
> +
> + /* Input Function Name */
> + datum = heap_getattr(typeTup, Anum_pg_type_typinput, pg_type_dsc, &isnull);
> + Assert(!isnull);
> +
> + inputName = DatumGetCString(DirectFunctionCall1(regprocout, datum));
> +
> + /* Output Function Name */
> + datum = heap_getattr(typeTup, Anum_pg_type_typoutput, pg_type_dsc, &isnull);
> + Assert(!isnull);
> +
> + outputName = DatumGetCString(DirectFunctionCall1(regprocout, datum));
> +
> + /* ReceiveName */
> + datum = heap_getattr(typeTup, Anum_pg_type_typreceive, pg_type_dsc, &isnull);
> + Assert(!isnull);
> +
> + receiveName = DatumGetCString(DirectFunctionCall1(regprocout, datum));
> +
> + /* SendName */
> + datum = heap_getattr(typeTup, Anum_pg_type_typsend, pg_type_dsc, &isnull);
> + Assert(!isnull);
> +
> + sendName = DatumGetCString(DirectFunctionCall1(regprocout, datum));
> +
> + /* TOAST Strategy */
> + storage = DatumGetChar(heap_getattr(typeTup, Anum_pg_type_typstorage, pg_type_dsc, &isnull));
> + Assert(!isnull);
> +
> + /* Inherited default value */
> + datum = heap_getattr(typeTup, Anum_pg_type_typdefault, pg_type_dsc, &isnull);
> + if (!isnull) {
> + defaultValue = DatumGetCString(DirectFunctionCall1(textout, datum));
> + }
> +
> + /*
> + * Pull out the typelem name of the parent OID.
> + *
> + * This is what enables us to make a domain of an array
> + */
> + datum = heap_getattr(typeTup, Anum_pg_type_typelem, pg_type_dsc, &isnull);
> + Assert(!isnull);
> +
> + if (DatumGetObjectId(datum) != InvalidOid) {
> + HeapTuple tup;
> +
> + tup = SearchSysCache( TYPEOID
> + , datum
> + , 0, 0, 0
> + );
> +
> + elemName = NameStr(((Form_pg_type) GETSTRUCT(tup))->typname);
> +
> + ReleaseSysCache(tup);
> + }
> +
> +
> + /*
> + * Run through constraints manually avoids the additional
> + * processing conducted by DefineRelation() and friends.
> + *
> + * Besides, we don't want any constraints to be cooked. We'll
> + * do that when the table is created via MergeDomainAttributes().
> + */
> + foreach(listptr, schema)
> + {
> + bool nullDefined = false;
> + Node *expr;
> + Constraint *colDef = lfirst(listptr);
> +
> + /* Used for the statement transformation */
> + ParseState *pstate;
> +
> + /*
> + * Create a dummy ParseState and insert the target relation as its
> + * sole rangetable entry. We need a ParseState for transformExpr.
> + */
> + pstate = make_parsestate(NULL);
> +
> + switch(colDef->contype) {
> + /*
> + * The inherited default value may be overridden by the user
> + * with the DEFAULT <expr> statement.
> + *
> + * We have to search the entire constraint tree returned as we
> + * don't want to cook or fiddle too much.
> + */
> + case CONSTR_DEFAULT:
> +
> + /*
> + * Cook the colDef->raw_expr into an expression to ensure
> + * that it can be done. We store the text version of the
> + * raw value.
> + *
> + * Note: Name is strictly for error message
> + */
> + expr = cookDefault(pstate, colDef->raw_expr
> + , typeTup->t_data->t_oid
> + , stmt->typename->typmod
> + , stmt->typename->name);
> +
> + /* Binary default required */
> + defaultValue = deparse_expression(expr,
> + deparse_context_for(stmt->domainname,
> + InvalidOid),
> + false);
> +
> + defaultValueBin = nodeToString(expr);
> +
> + break;
> +
> + /*
> + * Find the NULL constraint.
> + */
> + case CONSTR_NOTNULL:
> + if (nullDefined) {
> + elog(ERROR, "CREATE DOMAIN has conflicting NULL / NOT NULL constraint");
> + } else {
> + typNotNull = true;
> + nullDefined = true;
> + }
> +
> + break;
> +
> + case CONSTR_NULL:
> + if (nullDefined) {
> + elog(ERROR, "CREATE DOMAIN has conflicting NULL / NOT NULL constraint");
> + } else {
> + typNotNull = false;
> + nullDefined = true;
> + }
> +
> + break;
> +
> + case CONSTR_UNIQUE:
> + elog(ERROR, "CREATE DOMAIN / UNIQUE indecies not supported");
> + break;
> +
> + case CONSTR_PRIMARY:
> + elog(ERROR, "CREATE DOMAIN / PRIMARY KEY indecies not supported");
> + break;
> +
> +
> + case CONSTR_CHECK:
> +
> + elog(ERROR, "defineDomain: CHECK Constraints not supported");
> + break;
> +
> + case CONSTR_ATTR_DEFERRABLE:
> + case CONSTR_ATTR_NOT_DEFERRABLE:
> + case CONSTR_ATTR_DEFERRED:
> + case CONSTR_ATTR_IMMEDIATE:
> + elog(ERROR, "defineDomain: DEFERRABLE, NON DEFERRABLE, DEFERRED and IMMEDIATE not supported");
> + break;
> + }
> +
> + }
> +
> + /*
> + * Have TypeCreate do all the real work.
> + */
> + TypeCreate(stmt->domainname, /* type name */
> + InvalidOid, /* preassigned type oid (not done here) */
> + InvalidOid, /* relation oid (n/a here) */
> + internalLength, /* internal size */
> + externalLength, /* external size */
> + 'd', /* type-type (domain type) */
> + delimiter, /* array element delimiter */
> + inputName, /* input procedure */
> + outputName, /* output procedure */
> + receiveName, /* receive procedure */
> + sendName, /* send procedure */
> + elemName, /* element type name */
> + typeName, /* base type name */
> + defaultValue, /* default type value */
> + defaultValueBin, /* default type value */
> + byValue, /* passed by value */
> + alignment, /* required alignment */
> + storage, /* TOAST strategy */
> + stmt->typename->typmod, /* typeMod value */
> + typNDims, /* Array dimensions for base type */
> + typNotNull); /* Type NOT NULL */
> +
> + /*
> + * Now we can clean up.
> + */
> + ReleaseSysCache(typeTup);
> + heap_close(pg_type_rel, NoLock);
> + }
> +
> +
> + /*
> * DefineType
> * Registers a new type.
> */
> ***************
> *** 490,495 ****
> --- 807,814 ----
> char *sendName = NULL;
> char *receiveName = NULL;
> char *defaultValue = NULL;
> + char *defaultValueBin = NULL;
> + Node *defaultRaw = (Node *) NULL;
> bool byValue = false;
> char delimiter = DEFAULT_TYPDELIM;
> char *shadow_type;
> ***************
> *** 531,537 ****
> else if (strcasecmp(defel->defname, "element") == 0)
> elemName = defGetString(defel);
> else if (strcasecmp(defel->defname, "default") == 0)
> ! defaultValue = defGetString(defel);
> else if (strcasecmp(defel->defname, "passedbyvalue") == 0)
> byValue = true;
> else if (strcasecmp(defel->defname, "alignment") == 0)
> --- 850,856 ----
> else if (strcasecmp(defel->defname, "element") == 0)
> elemName = defGetString(defel);
> else if (strcasecmp(defel->defname, "default") == 0)
> ! defaultRaw = defel->arg;
> else if (strcasecmp(defel->defname, "passedbyvalue") == 0)
> byValue = true;
> else if (strcasecmp(defel->defname, "alignment") == 0)
> ***************
> *** 591,596 ****
> --- 910,941 ----
> if (outputName == NULL)
> elog(ERROR, "Define: \"output\" unspecified");
>
> +
> + if (defaultRaw) {
> + Node *expr;
> + ParseState *pstate;
> +
> + /*
> + * Create a dummy ParseState and insert the target relation as its
> + * sole rangetable entry. We need a ParseState for transformExpr.
> + */
> + pstate = make_parsestate(NULL);
> +
> + expr = cookDefault(pstate, defaultRaw,
> + InvalidOid,
> + -1,
> + typeName);
> +
> + /* Binary default required */
> + defaultValue = deparse_expression(expr,
> + deparse_context_for(typeName,
> + InvalidOid),
> + false);
> +
> + defaultValueBin = nodeToString(expr);
> + }
> +
> +
> /*
> * now have TypeCreate do all the real work.
> */
> ***************
> *** 606,615 ****
> receiveName, /* receive procedure */
> sendName, /* send procedure */
> elemName, /* element type name */
> defaultValue, /* default type value */
> byValue, /* passed by value */
> alignment, /* required alignment */
> ! storage); /* TOAST strategy */
>
> /*
> * When we create a base type (as opposed to a complex type) we need
> --- 951,965 ----
> receiveName, /* receive procedure */
> sendName, /* send procedure */
> elemName, /* element type name */
> + NULL, /* base type name (Non-zero for domains) */
> defaultValue, /* default type value */
> + defaultValueBin, /* default type value (Binary form) */
> byValue, /* passed by value */
> alignment, /* required alignment */
> ! storage, /* TOAST strategy */
> ! -1, /* typMod (Domains only) */
> ! 0, /* Array Dimensions of typbasetype */
> ! 'f'); /* Type NOT NULL */
>
> /*
> * When we create a base type (as opposed to a complex type) we need
> ***************
> *** 632,641 ****
> "array_in", /* receive procedure */
> "array_out", /* send procedure */
> typeName, /* element type name */
> NULL, /* never a default type value */
> false, /* never passed by value */
> alignment, /* see above */
> ! 'x'); /* ARRAY is always toastable */
>
> pfree(shadow_type);
> }
> --- 982,996 ----
> "array_in", /* receive procedure */
> "array_out", /* send procedure */
> typeName, /* element type name */
> + NULL, /* base type name */
> NULL, /* never a default type value */
> + NULL, /* binary default isn't sent either */
> false, /* never passed by value */
> alignment, /* see above */
> ! 'x', /* ARRAY is always toastable */
> ! -1, /* typMod (Domains only) */
> ! 0, /* Array dimensions of typbasetype */
> ! 'f'); /* Type NOT NULL */
>
> pfree(shadow_type);
> }

> diff -rc pgsql.orig/src/backend/nodes/copyfuncs.c pgsqldomain/src/backend/nodes/copyfuncs.c
> *** pgsql.orig/src/backend/nodes/copyfuncs.c Thu Mar 7 11:35:34 2002
> --- pgsqldomain/src/backend/nodes/copyfuncs.c Thu Mar 7 22:53:19 2002
> ***************
> *** 2227,2232 ****
> --- 2227,2247 ----
> return newnode;
> }
>
> + static CreateDomainStmt *
> + _copyCreateDomainStmt(CreateDomainStmt *from)
> + {
> + CreateDomainStmt *newnode = makeNode(CreateDomainStmt);
> +
> + if (from->domainname)
> + newnode->domainname = pstrdup(from->domainname);
> + if (from->typename)
> + newnode->typename = from->typename;

That's not a copy.

> + if (from->constraints)
> + newnode->constraints = from->constraints;
> +
> + return newnode;
> + }
> +
> static CreatedbStmt *
> _copyCreatedbStmt(CreatedbStmt *from)
> {
> ***************
> *** 3026,3031 ****
> --- 3041,3049 ----
> break;
> case T_FuncWithArgs:
> retval = _copyFuncWithArgs(from);
> + break;
> + case T_CreateDomainStmt:
> + retval = _copyCreateDomainStmt(from);
> break;
>
> default:

> diff -rc pgsql.orig/src/backend/parser/gram.y pgsqldomain/src/backend/parser/gram.y
> *** pgsql.orig/src/backend/parser/gram.y Thu Mar 7 11:35:35 2002
> --- pgsqldomain/src/backend/parser/gram.y Thu Mar 7 22:34:00 2002
> ***************
> *** 97,103 ****
>
> %}
>
> -
> %union
> {
> int ival;
> --- 97,102 ----
> ***************
> *** 135,141 ****
> ClosePortalStmt, ClusterStmt, CommentStmt, ConstraintsSetStmt,
> CopyStmt, CreateAsStmt, CreateGroupStmt, CreatePLangStmt,
> CreateSchemaStmt, CreateSeqStmt, CreateStmt, CreateTrigStmt,
> ! CreateUserStmt, CreatedbStmt, CursorStmt, DefineStmt, DeleteStmt,
> DropGroupStmt, DropPLangStmt, DropSchemaStmt, DropStmt, DropTrigStmt,
> DropUserStmt, DropdbStmt, ExplainStmt, FetchStmt,
> GrantStmt, IndexStmt, InsertStmt, ListenStmt, LoadStmt, LockStmt,
> --- 134,141 ----
> ClosePortalStmt, ClusterStmt, CommentStmt, ConstraintsSetStmt,
> CopyStmt, CreateAsStmt, CreateGroupStmt, CreatePLangStmt,
> CreateSchemaStmt, CreateSeqStmt, CreateStmt, CreateTrigStmt,
> ! CreateUserStmt, CreateDomainStmt, CreatedbStmt, CursorStmt,

Alphabetical order?

> ! DefineStmt, DeleteStmt,
> DropGroupStmt, DropPLangStmt, DropSchemaStmt, DropStmt, DropTrigStmt,
> DropUserStmt, DropdbStmt, ExplainStmt, FetchStmt,
> GrantStmt, IndexStmt, InsertStmt, ListenStmt, LoadStmt, LockStmt,
> ***************
> *** 289,294 ****
> --- 289,296 ----
> %type <list> constraints_set_namelist
> %type <boolean> constraints_set_mode
>
> + %type <boolean> opt_as
> +
> /*
> * If you make any token changes, remember to:
> * - use "yacc -d" and update parse.h
> ***************
> *** 343,349 ****
> WITHOUT
>
> /* Keywords (in SQL92 non-reserved words) */
> ! %token COMMITTED, SERIALIZABLE, TYPE_P
>
> /* Keywords for Postgres support (not in SQL92 reserved words)
> *
> --- 345,351 ----
> WITHOUT
>
> /* Keywords (in SQL92 non-reserved words) */
> ! %token COMMITTED, SERIALIZABLE, TYPE_P, DOMAIN_P
>
> /* Keywords for Postgres support (not in SQL92 reserved words)
> *
> ***************
> *** 446,451 ****
> --- 448,454 ----
> | CopyStmt
> | CreateStmt
> | CreateAsStmt
> + | CreateDomainStmt
> | CreateSchemaStmt
> | CreateGroupStmt
> | CreateSeqStmt
> ***************
> *** 776,783 ****
> --- 779,789 ----
> n->dbname = $3;
> $$ = (Node *)n;
> }
> + ;
>
>
> +
> +
> /*****************************************************************************
> *
> * Set PG internal variable
> ***************
> *** 1461,1467 ****
> n->name = NULL;
> if (exprIsNullConstant($2))
> {
> ! /* DEFAULT NULL should be reported as empty expr */
> n->raw_expr = NULL;
> }
> else
> --- 1467,1476 ----
> n->name = NULL;
> if (exprIsNullConstant($2))
> {
> ! /*
> ! * DEFAULT NULL should be reported as empty expr
> ! * Required for NOT NULL Domain overrides
> ! */
> n->raw_expr = NULL;
> }
> else
> ***************
> *** 2043,2055 ****
> | def_list ',' def_elem { $$ = lappend($1, $3); }
> ;
>
> ! def_elem: ColLabel '=' def_arg
> {
> $$ = makeNode(DefElem);
> $$->defname = $1;
> $$->arg = (Node *)$3;
> }
> ! | ColLabel
> {
> $$ = makeNode(DefElem);
> $$->defname = $1;
> --- 2052,2073 ----
> | def_list ',' def_elem { $$ = lappend($1, $3); }
> ;
>
> ! def_elem: DEFAULT '=' c_expr
> ! {
> ! $$ = makeNode(DefElem);
> ! $$->defname = "default";
> ! if (exprIsNullConstant($3))
> ! $$->arg = (Node *)NULL;
> ! else
> ! $$->arg = $3;
> ! }
> ! | ColId '=' def_arg
> {
> $$ = makeNode(DefElem);
> $$->defname = $1;
> $$->arg = (Node *)$3;
> }
> ! | ColId
> {
> $$ = makeNode(DefElem);
> $$->defname = $1;
> ***************
> *** 2078,2083 ****
> --- 2096,2110 ----
> DropStmt *n = makeNode(DropStmt);
> n->removeType = $2;
> n->names = $3;
> + n->behavior = RESTRICT; /* Restricted by default */
> + $$ = (Node *)n;
> + }
> + | DROP DOMAIN_P name_list drop_behavior
> + {
> + DropStmt *n = makeNode(DropStmt);
> + n->removeType = DROP_DOMAIN_P;
> + n->names = $3;
> + n->behavior = $4;
> $$ = (Node *)n;
> }
> ;
> ***************
> *** 2110,2116 ****
> * The COMMENT ON statement can take different forms based upon the type of
> * the object associated with the comment. The form of the statement is:
> *
> ! * COMMENT ON [ [ DATABASE | INDEX | RULE | SEQUENCE | TABLE | TYPE | VIEW ]
> * <objname> | AGGREGATE <aggname> (<aggtype>) | FUNCTION
> * <funcname> (arg1, arg2, ...) | OPERATOR <op>
> * (leftoperand_typ rightoperand_typ) | TRIGGER <triggername> ON
> --- 2137,2143 ----
> * The COMMENT ON statement can take different forms based upon the type of
> * the object associated with the comment. The form of the statement is:
> *
> ! * COMMENT ON [ [ DATABASE | DOMAIN | INDEX | RULE | SEQUENCE | TABLE | TYPE | VIEW ]
> * <objname> | AGGREGATE <aggname> (<aggtype>) | FUNCTION
> * <funcname> (arg1, arg2, ...) | OPERATOR <op>
> * (leftoperand_typ rightoperand_typ) | TRIGGER <triggername> ON
> ***************
> *** 2196,2201 ****
> --- 2223,2229 ----
> | RULE { $$ = RULE; }
> | SEQUENCE { $$ = SEQUENCE; }
> | TABLE { $$ = TABLE; }
> + | DOMAIN_P { $$ = TYPE_P; }
> | TYPE_P { $$ = TYPE_P; }
> | VIEW { $$ = VIEW; }
> ;
> ***************
> *** 3178,3183 ****
> --- 3206,3227 ----
> {
> $$ = lconsi(3, makeListi1(-1));
> }
> + ;
> +
> +
> + /*****************************************************************************
> + *
> + * DROP DATABASE
> + *
> + *
> + *****************************************************************************/
> +
> + DropdbStmt: DROP DATABASE database_name
> + {
> + DropdbStmt *n = makeNode(DropdbStmt);
> + n->dbname = $3;
> + $$ = (Node *)n;
> + }
> | OWNER opt_equal name
> {
> $$ = lconsi(4, makeList1($3));

This doesn't look right.

> ***************
> *** 3222,3243 ****
> }
> ;
>
> -
> /*****************************************************************************
> *
> ! * DROP DATABASE
> *
> *
> *****************************************************************************/
>
> ! DropdbStmt: DROP DATABASE database_name
> {
> ! DropdbStmt *n = makeNode(DropdbStmt);
> ! n->dbname = $3;
> $$ = (Node *)n;
> }
> ;
>
>
> /*****************************************************************************
> *
> --- 3266,3295 ----
> }
> ;
>
> /*****************************************************************************
> *
> ! * Manipulate a domain
> *
> *
> *****************************************************************************/
>
> ! CreateDomainStmt: CREATE DOMAIN_P name opt_as Typename ColQualList opt_collate
> {
> ! CreateDomainStmt *n = makeNode(CreateDomainStmt);
> ! n->domainname = $3;
> ! n->typename = $5;
> ! n->constraints = $6;
> !
> ! if ($7 != NULL)
> ! elog(NOTICE,"CREATE DOMAIN / COLLATE %s not yet "
> ! "implemented; clause ignored", $7);
> $$ = (Node *)n;
> }
> ;
>
> + opt_as: AS {$$ = TRUE; }
> + | /* EMPTY */ {$$ = FALSE; }
> + ;
>
> /*****************************************************************************
> *
> ***************
> *** 5879,5884 ****
> --- 5931,5937 ----
> | DEFERRED { $$ = "deferred"; }
> | DELETE { $$ = "delete"; }
> | DELIMITERS { $$ = "delimiters"; }
> + | DOMAIN_P { $$ = "domain"; }
> | DOUBLE { $$ = "double"; }
> | DROP { $$ = "drop"; }
> | EACH { $$ = "each"; }

> diff -rc pgsql.orig/src/backend/parser/parse_coerce.c pgsqldomain/src/backend/parser/parse_coerce.c
> *** pgsql.orig/src/backend/parser/parse_coerce.c Thu Mar 7 11:35:35 2002
> --- pgsqldomain/src/backend/parser/parse_coerce.c Thu Mar 7 22:24:24 2002
> ***************
> *** 38,43 ****
> --- 38,44 ----
> {
> Node *result;
>
> +

No.

> if (targetTypeId == inputTypeId ||
> targetTypeId == InvalidOid ||
> node == NULL)
> ***************
> *** 605,607 ****
> --- 606,637 ----
> }
> return result;
> } /* PreferredType() */
> +
> +
> + /*
> + * If the targetTypeId is a domain, we really want to coerce
> + * the tuple to the domain type -- not the domain itself
> + */
> + Oid
> + getBaseType(Oid inType)
> + {
> + HeapTuple tup;
> + Form_pg_type typTup;
> +
> + tup = SearchSysCache(TYPEOID,
> + ObjectIdGetDatum(inType),
> + 0, 0, 0);
> +
> + typTup = ((Form_pg_type) GETSTRUCT(tup));
> +
> + /*
> + * Assume that typbasetype exists and is a base type, where inType
> + * was a domain
> + */
> + if (typTup->typtype == 'd')
> + inType = typTup->typbasetype;
> +
> + ReleaseSysCache(tup);
> +
> + return inType;
> + }

> diff -rc pgsql.orig/src/backend/tcop/postgres.c pgsqldomain/src/backend/tcop/postgres.c
> *** pgsql.orig/src/backend/tcop/postgres.c Wed Mar 6 01:10:09 2002
> --- pgsqldomain/src/backend/tcop/postgres.c Thu Mar 7 22:24:24 2002
> ***************
> *** 2212,2217 ****
> --- 2212,2218 ----
> }
> break;
>
> + case T_CreateDomainStmt:
> case T_CreateStmt:
> tag = "CREATE";
> break;

The result tag for CREATE DOMAIN is CREATE DOMAIN. (Yes, there's actually
a standard about this.)

--
Peter Eisentraut peter_e(at)gmx(dot)net

In response to

Responses

Browse pgsql-hackers by date

  From Date Subject
Next Message Peter Eisentraut 2002-03-12 00:19:17 Re: psql and output from \?
Previous Message Ian Barwick 2002-03-11 23:55:27 Re: psql and output from \?

Browse pgsql-patches by date

  From Date Subject
Next Message Peter Eisentraut 2002-03-12 00:17:51 Re: psql: backslash fix
Previous Message Neil Conway 2002-03-12 00:05:40 Re: support for POSIX 1003.1-2001 hosts