As previously mentioned, I'm working on implementing [subject]. I think I've mostly worked it out, but I'm having trouble with the GENERATED ALWAYS case.
My changes (see attached patch) can be summarized as follows: - in backend/utils/adt/misc.c: - added a force_default_value() function which takes a string argument (the name of the column to force to default) and currently does nothing. - in backend/parser/gram.y: - when GENERATED ... AS ... is encountered in a column definition, it adds a node of the new T_Generated type to the constraint list. This node contains a bool that differentiates between BY DEFAULT and ALWAYS, and a pointer to a CreateSeqStmt (for IDENTITY '(' OptSeqList ')') or a List constructed by the a_expr production (for '(' a_expr ')') - in backend/parser/analyze.c: - factored out the code from transformColumnDefinition() that creates the sequence and the DEFAULT constraint into a separate CreateSerialColumn() function which takes as one of its arguments is a List of sequence options. The SERIAL code passes in a NIL list, while the GENERATED AS IDENTITY code passes in the option list from the CreateSeqStmt. - added a CreateAlwaysDefaultColumn() function which synthesizes a CreateTrigStmt equivalent to CREATE TRIGGER foo BEFORE INSERT ON bar FOR EACH ROW EXECUTE PROCEDURE force_default_value ('baz') and adds it to the work list. This function is called by transformColumnDefinition() if a Generated node with always set to true is encountered. Now I must be doing something wrong in CreateAlwaysDefaultColumn(), because the CreateTrigStmt fails to execute: | des=# create table test ( id int generated always as identity ( minvalue 1000 ), word text ); | NOTICE: CREATE TABLE will create implicit sequence "test_id_seq" for SERIAL column "test.id" | NOTICE: CREATE TABLE will create implicit trigger "test_id_always_default" for column "test.id" | ERROR: relation "public.test" does not exist GENERATED BY DEFAULT AS IDENTITY works fine though, so I must have done *something* right: | des=# create table test ( id int generated by default as identity ( minvalue 1000 ), word text ); | NOTICE: CREATE TABLE will create implicit sequence "test_id_seq" for SERIAL column "test.id" | CREATE TABLE | des=# select sequence_name, last_value, min_value, max_value from test_id_seq; | sequence_name | last_value | min_value | max_value | ---------------+------------+-----------+--------------------- | test_id_seq | 1000 | 1000 | 9223372036854775807 | (1 row) | On the other hand, I seem to have botched the definition of force_default_value() in include/catalog/pg_proc.h, because adding the trigger manually doesn't seem to work either: | des=# \df force_default_value | List of functions | Result data type | Schema | Name | Argument data types | ------------------+------------+---------------------+--------------------- | "trigger" | pg_catalog | force_default_value | text | (1 row) | | des=# create trigger test_id_always_default before insert on test for each row execute procedure force_default_value ('id'); | ERROR: function force_default_value() does not exist Any suggestions? DES -- Dag-Erling Smørgrav - [EMAIL PROTECTED]
Index: src/backend/parser/analyze.c =================================================================== RCS file: /home/pqcvs/pgsql-server/src/backend/parser/analyze.c,v retrieving revision 1.283 diff -u -u -r1.283 analyze.c --- src/backend/parser/analyze.c 1 Aug 2003 00:15:22 -0000 1.283 +++ src/backend/parser/analyze.c 3 Aug 2003 11:39:12 -0000 @@ -917,13 +917,116 @@ } static void +CreateSerialColumn(ParseState *pstate, CreateStmtContext *cxt, + ColumnDef *column, List *seqopts) +{ + Constraint *constraint; + char *sname; + char *snamespace; + char *qstring; + A_Const *snamenode; + FuncCall *funccallnode; + CreateSeqStmt *seqstmt; + + /* + * Determine name and namespace to use for the sequence. + */ + sname = makeObjectName(cxt->relation->relname, column->colname, "seq"); + snamespace = get_namespace_name(RangeVarGetCreationNamespace(cxt->relation)); + + ereport(NOTICE, + (errmsg("%s will create implicit sequence \"%s\" for SERIAL column \"%s.%s\"", + cxt->stmtType, sname, + cxt->relation->relname, column->colname))); + + /* + * Build a CREATE SEQUENCE command to create the sequence object, + * and add it to the list of things to be done before this + * CREATE/ALTER TABLE. + */ + seqstmt = makeNode(CreateSeqStmt); + seqstmt->sequence = makeRangeVar(snamespace, sname); + seqstmt->options = seqopts; + + cxt->blist = lappend(cxt->blist, seqstmt); + + /* + * Mark the ColumnDef so that during execution, an appropriate + * dependency will be added from the sequence to the column. + */ + column->support = makeRangeVar(snamespace, sname); + + /* + * Create appropriate constraints for SERIAL. We do this in full, + * rather than shortcutting, so that we will detect any + * conflicting constraints the user wrote (like a different + * DEFAULT). + * + * Create an expression tree representing the function call + * nextval('"sequencename"') + */ + qstring = quote_qualified_identifier(snamespace, sname); + snamenode = makeNode(A_Const); + snamenode->val.type = T_String; + snamenode->val.val.str = qstring; + funccallnode = makeNode(FuncCall); + funccallnode->funcname = SystemFuncName("nextval"); + funccallnode->args = makeList1(snamenode); + funccallnode->agg_star = false; + funccallnode->agg_distinct = false; + + constraint = makeNode(Constraint); + constraint->contype = CONSTR_DEFAULT; + constraint->name = sname; + constraint->raw_expr = (Node *) funccallnode; + constraint->cooked_expr = NULL; + constraint->keys = NIL; + column->constraints = lappend(column->constraints, constraint); + + constraint = makeNode(Constraint); + constraint->contype = CONSTR_NOTNULL; + column->constraints = lappend(column->constraints, constraint); +} + +static void +CreateAlwaysDefaultColumn(ParseState *pstate, CreateStmtContext *cxt, + ColumnDef *column) +{ + char *tname; + char *tnamespace; + CreateTrigStmt *trigstmt; + + tname = makeObjectName(cxt->relation->relname, + column->colname, "always_default"); + tnamespace = get_namespace_name(RangeVarGetCreationNamespace(cxt->relation)); + + ereport(NOTICE, + (errmsg("%s will create implicit trigger \"%s\" for column \"%s.%s\"", + cxt->stmtType, tname, + cxt->relation->relname, column->colname))); + + trigstmt = makeNode(CreateTrigStmt); + trigstmt->trigname = tname; + trigstmt->relation = makeRangeVar(tnamespace, cxt->relation->relname); + trigstmt->funcname = SystemFuncName("force_default_value"); + trigstmt->args = makeList1(makeString(column->colname)); + trigstmt->before = true; + trigstmt->row = true; + trigstmt->actions[0] = 'i'; + + cxt->blist = lappend(cxt->blist, trigstmt); +} + +static void transformColumnDefinition(ParseState *pstate, CreateStmtContext *cxt, ColumnDef *column) { bool is_serial; + bool is_always_default; bool saw_nullable; Constraint *constraint; List *clist; + List *seqopts; cxt->columns = lappend(cxt->columns, column); @@ -937,6 +1040,7 @@ strcmp(typname, "serial4") == 0) { is_serial = true; + seqopts = NIL; column->typename->names = NIL; column->typename->typeid = INT4OID; } @@ -944,6 +1048,7 @@ strcmp(typname, "serial8") == 0) { is_serial = true; + seqopts = NIL; column->typename->names = NIL; column->typename->typeid = INT8OID; } @@ -952,76 +1057,62 @@ /* Do necessary work on the column type declaration */ transformColumnType(pstate, column); - /* Special actions for SERIAL pseudo-types */ - if (is_serial) + /* Check for GENERATED ... AS ... */ + is_always_default = false; + foreach(clist, column->constraints) { - char *sname; - char *snamespace; - char *qstring; - A_Const *snamenode; - FuncCall *funccallnode; - CreateSeqStmt *seqstmt; + Generated *generated; - /* - * Determine name and namespace to use for the sequence. - */ - sname = makeObjectName(cxt->relation->relname, column->colname, "seq"); - snamespace = get_namespace_name(RangeVarGetCreationNamespace(cxt->relation)); - - ereport(NOTICE, - (errmsg("%s will create implicit sequence \"%s\" for SERIAL column \"%s.%s\"", - cxt->stmtType, sname, - cxt->relation->relname, column->colname))); + generated = lfirst(clist); + if (!IsA(generated, Generated)) + continue; - /* - * Build a CREATE SEQUENCE command to create the sequence object, - * and add it to the list of things to be done before this - * CREATE/ALTER TABLE. - */ - seqstmt = makeNode(CreateSeqStmt); - seqstmt->sequence = makeRangeVar(snamespace, sname); - seqstmt->options = NIL; + if (generated->always) + is_always_default = true; - cxt->blist = lappend(cxt->blist, seqstmt); + if (IsA(generated->expr, CreateSeqStmt)) { /* AS IDENTITY */ + CreateSeqStmt *stmt = (CreateSeqStmt *)generated->expr; - /* - * Mark the ColumnDef so that during execution, an appropriate - * dependency will be added from the sequence to the column. - */ - column->support = makeRangeVar(snamespace, sname); + /* is this column already serial? */ + if (is_serial) { + ereport(WARNING, + (errmsg("multiple SERIAL / GENERATED AS IDENTITY on column \"%s.%s\"", + cxt->relation->relname, column->colname))); + continue; + } + + /* check the type */ + column->typename->typeid = LookupTypeName(column->typename); + if (column->typename->typeid != InvalidOid) + column->typename->names = NIL; + if (column->typename->typeid != INT4OID) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("GENERATED AS IDENTITY columns must be of integer type"))); - /* - * Create appropriate constraints for SERIAL. We do this in full, - * rather than shortcutting, so that we will detect any - * conflicting constraints the user wrote (like a different - * DEFAULT). - * - * Create an expression tree representing the function call - * nextval('"sequencename"') - */ - qstring = quote_qualified_identifier(snamespace, sname); - snamenode = makeNode(A_Const); - snamenode->val.type = T_String; - snamenode->val.val.str = qstring; - funccallnode = makeNode(FuncCall); - funccallnode->funcname = SystemFuncName("nextval"); - funccallnode->args = makeList1(snamenode); - funccallnode->agg_star = false; - funccallnode->agg_distinct = false; - - constraint = makeNode(Constraint); - constraint->contype = CONSTR_DEFAULT; - constraint->name = sname; - constraint->raw_expr = (Node *) funccallnode; - constraint->cooked_expr = NULL; - constraint->keys = NIL; - column->constraints = lappend(column->constraints, constraint); - - constraint = makeNode(Constraint); - constraint->contype = CONSTR_NOTNULL; - column->constraints = lappend(column->constraints, constraint); + is_serial = true; + seqopts = stmt->options; + stmt->options = NIL; + } else { /* AS ( <expr> ) */ + /* + * "GENERATED BY DEFAULT AS ( <expr> )" is not valid SQL, + * though it might make sense to accept it as an alternate + * syntax for "DEFAULT expr". + */ + Assert(is_always_default); + + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("GENERATED AS ( <expr> ) is not yet supported"))); + } } + if (is_serial) + CreateSerialColumn(pstate, cxt, column, seqopts); + + if (is_always_default) + CreateAlwaysDefaultColumn(pstate, cxt, column); + /* Process column constraints, if any... */ transformConstraintAttrs(column->constraints); @@ -1045,6 +1136,12 @@ continue; } + /* + * GENERATE ... AS ... was handled earlier + */ + if (IsA(constraint, Generated)) + continue; + Assert(IsA(constraint, Constraint)); switch (constraint->contype) @@ -1188,7 +1285,7 @@ inhRelation->relation->relname))); /* - * Check for SELECT privilages + * Check for SELECT privilages */ aclresult = pg_class_aclcheck(RelationGetRelid(relation), GetUserId(), ACL_SELECT); Index: src/backend/parser/gram.y =================================================================== RCS file: /home/pqcvs/pgsql-server/src/backend/parser/gram.y,v retrieving revision 2.427 diff -u -u -r2.427 gram.y --- src/backend/parser/gram.y 19 Jul 2003 20:20:52 -0000 2.427 +++ src/backend/parser/gram.y 3 Aug 2003 10:55:19 -0000 @@ -317,6 +317,7 @@ %type <list> constraints_set_list %type <boolean> constraints_set_mode +%type <node> GeneratedAs /* * If you make any token changes, update the keyword table in @@ -326,7 +327,7 @@ /* ordinary key words in alphabetical order */ %token <keyword> ABORT_P ABSOLUTE_P ACCESS ACTION ADD AFTER - AGGREGATE ALL ALTER ANALYSE ANALYZE AND ANY ARRAY AS ASC + AGGREGATE ALL ALTER ALWAYS ANALYSE ANALYZE AND ANY ARRAY AS ASC ASSERTION ASSIGNMENT AT AUTHORIZATION BACKWARD BEFORE BEGIN_P BETWEEN BIGINT BINARY BIT @@ -349,11 +350,11 @@ FALSE_P FETCH FIRST_P FLOAT_P FOR FORCE FOREIGN FORWARD FREEZE FROM FULL FUNCTION - GLOBAL GRANT GROUP_P + GENERATED GLOBAL GRANT GROUP_P HANDLER HAVING HOLD HOUR_P - ILIKE IMMEDIATE IMMUTABLE IMPLICIT_P IN_P INCLUDING INCREMENT + IDENTITY ILIKE IMMEDIATE IMMUTABLE IMPLICIT_P IN_P INCLUDING INCREMENT INDEX INHERITS INITIALLY INNER_P INOUT INPUT_P INSENSITIVE INSERT INSTEAD INT_P INTEGER INTERSECT INTERVAL INTO INVOKER IS ISNULL ISOLATION @@ -1604,6 +1605,39 @@ n->fk_del_action = (char) ($5 & 0xFF); n->deferrable = FALSE; n->initdeferred = FALSE; + $$ = (Node *)n; + } + | GENERATED ALWAYS GeneratedAs + { + Generated *n = makeNode(Generated); + n->always = true; + n->expr = $3; + $$ = (Node *)n; + } + | GENERATED BY DEFAULT GeneratedAs + { + Generated *n = makeNode(Generated); + n->always = false; + n->expr = $4; + $$ = (Node *)n; + } + ; + +GeneratedAs: + AS '(' a_expr ')' + { + $$ = $3; + } + | AS IDENTITY + { + CreateSeqStmt *n = makeNode(CreateSeqStmt); + n->options = NIL; + $$ = (Node *)n; + } + | AS IDENTITY '(' OptSeqList ')' + { + CreateSeqStmt *n = makeNode(CreateSeqStmt); + n->options = $4; $$ = (Node *)n; } ; Index: src/backend/parser/keywords.c =================================================================== RCS file: /home/pqcvs/pgsql-server/src/backend/parser/keywords.c,v retrieving revision 1.140 diff -u -u -r1.140 keywords.c --- src/backend/parser/keywords.c 25 Jun 2003 03:40:18 -0000 1.140 +++ src/backend/parser/keywords.c 2 Aug 2003 22:46:40 -0000 @@ -39,6 +39,7 @@ {"aggregate", AGGREGATE}, {"all", ALL}, {"alter", ALTER}, + {"always", ALWAYS}, {"analyse", ANALYSE}, /* British spelling */ {"analyze", ANALYZE}, {"and", AND}, @@ -141,6 +142,7 @@ {"from", FROM}, {"full", FULL}, {"function", FUNCTION}, + {"generated", GENERATED}, {"global", GLOBAL}, {"grant", GRANT}, {"group", GROUP_P}, @@ -148,6 +150,7 @@ {"having", HAVING}, {"hold", HOLD}, {"hour", HOUR_P}, + {"identity", IDENTITY}, {"ilike", ILIKE}, {"immediate", IMMEDIATE}, {"immutable", IMMUTABLE}, Index: src/backend/utils/adt/misc.c =================================================================== RCS file: /home/pqcvs/pgsql-server/src/backend/utils/adt/misc.c,v retrieving revision 1.30 diff -u -u -r1.30 misc.c --- src/backend/utils/adt/misc.c 27 Jun 2003 17:07:03 -0000 1.30 +++ src/backend/utils/adt/misc.c 3 Aug 2003 11:29:05 -0000 @@ -18,6 +18,7 @@ #include <time.h> #include "commands/dbcommands.h" +#include "commands/trigger.h" #include "miscadmin.h" #include "utils/builtins.h" @@ -57,4 +58,30 @@ namestrcpy(db, get_database_name(MyDatabaseId)); PG_RETURN_NAME(db); +} + +/* + * force_default_value() + * Force a column to its default value + */ +Datum +force_default_value(PG_FUNCTION_ARGS) +{ + TriggerData *trigdata; + int tgnargs; + char **tgargs; + + if (!CALLED_AS_TRIGGER(fcinfo)) + ereport(ERROR, + (errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED), + errmsg("%s() was not fired by trigger manager", + PG_FUNCNAME_MACRO))); + + trigdata = (TriggerData *)fcinfo->context; + tgnargs = trigdata->tg_trigger->tgnargs; + tgargs = trigdata->tg_trigger->tgargs; + + ereport(NOTICE, (errmsg("%s() was fired!", PG_FUNCNAME_MACRO))); + + return PointerGetDatum(NULL); } Index: src/include/catalog/pg_proc.h =================================================================== RCS file: /home/pqcvs/pgsql-server/src/include/catalog/pg_proc.h,v retrieving revision 1.310 diff -u -u -r1.310 pg_proc.h --- src/include/catalog/pg_proc.h 30 Jul 2003 22:56:24 -0000 1.310 +++ src/include/catalog/pg_proc.h 3 Aug 2003 11:18:51 -0000 @@ -3419,6 +3419,9 @@ DATA(insert OID = 2509 ( pg_get_expr PGNSP PGUID 12 f f t f s 3 25 "25 26 16" pg_get_expr_ext - _null_ )); DESCR("deparse an encoded expression with pretty-print option"); +DATA(insert OID = 2510 ( force_default_value PGNSP PGUID 12 f f t f v 1 2279 "25" force_default_value - _null_ )); +DESCR("force default value for GENERATED ALWAYS columns"); + /* * Symbolic values for provolatile column: these indicate whether the result Index: src/include/nodes/nodes.h =================================================================== RCS file: /home/pqcvs/pgsql-server/src/include/nodes/nodes.h,v retrieving revision 1.144 diff -u -u -r1.144 nodes.h --- src/include/nodes/nodes.h 3 Jul 2003 16:34:25 -0000 1.144 +++ src/include/nodes/nodes.h 2 Aug 2003 23:11:36 -0000 @@ -283,6 +283,7 @@ T_CreateOpClassItem, T_CompositeTypeStmt, T_InhRelation, + T_Generated, /* * TAGS FOR FUNCTION-CALL CONTEXT AND RESULTINFO NODES (see fmgr.h) Index: src/include/nodes/parsenodes.h =================================================================== RCS file: /home/pqcvs/pgsql-server/src/include/nodes/parsenodes.h,v retrieving revision 1.243 diff -u -u -r1.243 parsenodes.h --- src/include/nodes/parsenodes.h 3 Jul 2003 16:34:25 -0000 1.243 +++ src/include/nodes/parsenodes.h 3 Aug 2003 10:21:44 -0000 @@ -975,6 +975,16 @@ bool skip_validation; /* skip validation of existing rows? */ } FkConstraint; +/* + * Definitions for GENERATED ... AS ... in CreateStmt + */ +typedef struct Generated +{ + NodeTag type; + bool always; /* true if ALWAYS, false if BY DEFAULT */ + Node *expr; /* Expression or CreateSeqStmt */ +} Generated; + /* ---------------------- * Create/Drop TRIGGER Statements * ---------------------- Index: src/include/utils/builtins.h =================================================================== RCS file: /home/pqcvs/pgsql-server/src/include/utils/builtins.h,v retrieving revision 1.224 diff -u -u -r1.224 builtins.h --- src/include/utils/builtins.h 30 Jul 2003 22:56:24 -0000 1.224 +++ src/include/utils/builtins.h 3 Aug 2003 11:13:49 -0000 @@ -337,6 +337,7 @@ extern Datum nullvalue(PG_FUNCTION_ARGS); extern Datum nonnullvalue(PG_FUNCTION_ARGS); extern Datum current_database(PG_FUNCTION_ARGS); +extern Datum force_default_value(PG_FUNCTION_ARGS); /* not_in.c */ extern Datum int4notin(PG_FUNCTION_ARGS);
---------------------------(end of broadcast)--------------------------- TIP 6: Have you searched our list archives? http://archives.postgresql.org