aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/backend/catalog/heap.c62
-rw-r--r--src/backend/catalog/pg_attrdef.c6
-rw-r--r--src/backend/commands/tablecmds.c87
-rw-r--r--src/include/catalog/heap.h5
-rw-r--r--src/test/regress/expected/fast_default.out65
-rw-r--r--src/test/regress/sql/fast_default.sql44
6 files changed, 235 insertions, 34 deletions
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 7ef6f0f1cba..f7f36837d76 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -69,6 +69,7 @@
#include "pgstat.h"
#include "storage/lmgr.h"
#include "storage/predicate.h"
+#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/fmgroids.h"
#include "utils/inval.h"
@@ -2011,6 +2012,60 @@ RelationClearMissing(Relation rel)
}
/*
+ * StoreAttrMissingVal
+ *
+ * Set the missing value of a single attribute.
+ */
+void
+StoreAttrMissingVal(Relation rel, AttrNumber attnum, Datum missingval)
+{
+ Datum valuesAtt[Natts_pg_attribute] = {0};
+ bool nullsAtt[Natts_pg_attribute] = {0};
+ bool replacesAtt[Natts_pg_attribute] = {0};
+ Relation attrrel;
+ Form_pg_attribute attStruct;
+ HeapTuple atttup,
+ newtup;
+
+ /* This is only supported for plain tables */
+ Assert(rel->rd_rel->relkind == RELKIND_RELATION);
+
+ /* Fetch the pg_attribute row */
+ attrrel = table_open(AttributeRelationId, RowExclusiveLock);
+
+ atttup = SearchSysCache2(ATTNUM,
+ ObjectIdGetDatum(RelationGetRelid(rel)),
+ Int16GetDatum(attnum));
+ if (!HeapTupleIsValid(atttup)) /* shouldn't happen */
+ elog(ERROR, "cache lookup failed for attribute %d of relation %u",
+ attnum, RelationGetRelid(rel));
+ attStruct = (Form_pg_attribute) GETSTRUCT(atttup);
+
+ /* Make a one-element array containing the value */
+ missingval = PointerGetDatum(construct_array(&missingval,
+ 1,
+ attStruct->atttypid,
+ attStruct->attlen,
+ attStruct->attbyval,
+ attStruct->attalign));
+
+ /* Update the pg_attribute row */
+ valuesAtt[Anum_pg_attribute_atthasmissing - 1] = BoolGetDatum(true);
+ replacesAtt[Anum_pg_attribute_atthasmissing - 1] = true;
+
+ valuesAtt[Anum_pg_attribute_attmissingval - 1] = missingval;
+ replacesAtt[Anum_pg_attribute_attmissingval - 1] = true;
+
+ newtup = heap_modify_tuple(atttup, RelationGetDescr(attrrel),
+ valuesAtt, nullsAtt, replacesAtt);
+ CatalogTupleUpdate(attrrel, &newtup->t_self, newtup);
+
+ /* clean up */
+ ReleaseSysCache(atttup);
+ table_close(attrrel, RowExclusiveLock);
+}
+
+/*
* SetAttrMissing
*
* Set the missing value of a single attribute. This should only be used by
@@ -2392,13 +2447,8 @@ AddRelationNewConstraints(Relation rel,
castNode(Const, expr)->constisnull))
continue;
- /* If the DEFAULT is volatile we cannot use a missing value */
- if (colDef->missingMode &&
- contain_volatile_functions_after_planning((Expr *) expr))
- colDef->missingMode = false;
-
defOid = StoreAttrDefault(rel, colDef->attnum, expr, is_internal,
- colDef->missingMode);
+ false);
cooked = (CookedConstraint *) palloc(sizeof(CookedConstraint));
cooked->contype = CONSTR_DEFAULT;
diff --git a/src/backend/catalog/pg_attrdef.c b/src/backend/catalog/pg_attrdef.c
index 91dafc81021..a58815a5134 100644
--- a/src/backend/catalog/pg_attrdef.c
+++ b/src/backend/catalog/pg_attrdef.c
@@ -120,6 +120,12 @@ StoreAttrDefault(Relation rel, AttrNumber attnum,
valuesAtt[Anum_pg_attribute_atthasdef - 1] = true;
replacesAtt[Anum_pg_attribute_atthasdef - 1] = true;
+ /*
+ * Note: this code is dead so far as core Postgres is concerned,
+ * because no caller passes add_column_mode = true anymore. We keep
+ * it in back branches on the slight chance that some extension is
+ * depending on it.
+ */
if (rel->rd_rel->relkind == RELKIND_RELATION && add_column_mode &&
!attgenerated)
{
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index ce7d115667e..fd31216b27b 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -7321,14 +7321,7 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
rawEnt = (RawColumnDefault *) palloc(sizeof(RawColumnDefault));
rawEnt->attnum = attribute->attnum;
rawEnt->raw_default = copyObject(colDef->raw_default);
-
- /*
- * Attempt to skip a complete table rewrite by storing the specified
- * DEFAULT value outside of the heap. This may be disabled inside
- * AddRelationNewConstraints if the optimization cannot be applied.
- */
- rawEnt->missingMode = (colDef->generated != ATTRIBUTE_GENERATED_STORED);
-
+ rawEnt->missingMode = false; /* XXX vestigial */
rawEnt->generated = colDef->generated;
/*
@@ -7340,13 +7333,6 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
/* Make the additional catalog changes visible */
CommandCounterIncrement();
-
- /*
- * Did the request for a missing value work? If not we'll have to do a
- * rewrite
- */
- if (!rawEnt->missingMode)
- tab->rewrite |= AT_REWRITE_DEFAULT_VAL;
}
/*
@@ -7363,9 +7349,9 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
* rejects nulls. If there are any domain constraints then we construct
* an explicit NULL default value that will be passed through
* CoerceToDomain processing. (This is a tad inefficient, since it causes
- * rewriting the table which we really don't have to do, but the present
- * design of domain processing doesn't offer any simple way of checking
- * the constraints more directly.)
+ * rewriting the table which we really wouldn't have to do; but we do it
+ * to preserve the historical behavior that such a failure will be raised
+ * only if the table currently contains some rows.)
*
* Note: we use build_column_default, and not just the cooked default
* returned by AddRelationNewConstraints, so that the right thing happens
@@ -7384,6 +7370,9 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
*/
if (RELKIND_HAS_STORAGE(relkind))
{
+ bool has_domain_constraints;
+ bool has_missing = false;
+
/*
* For an identity column, we can't use build_column_default(),
* because the sequence ownership isn't set yet. So do it manually.
@@ -7396,14 +7385,13 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
nve->typeId = attribute->atttypid;
defval = (Expr *) nve;
-
- /* must do a rewrite for identity columns */
- tab->rewrite |= AT_REWRITE_DEFAULT_VAL;
}
else
defval = (Expr *) build_column_default(rel, attribute->attnum);
- if (!defval && DomainHasConstraints(attribute->atttypid))
+ /* Build CoerceToDomain(NULL) expression if needed */
+ has_domain_constraints = DomainHasConstraints(attribute->atttypid);
+ if (!defval && has_domain_constraints)
{
Oid baseTypeId;
int32 baseTypeMod;
@@ -7429,18 +7417,63 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
{
NewColumnValue *newval;
+ /* Prepare defval for execution, either here or in Phase 3 */
+ defval = expression_planner(defval);
+
+ /* Add the new default to the newvals list */
newval = (NewColumnValue *) palloc0(sizeof(NewColumnValue));
newval->attnum = attribute->attnum;
- newval->expr = expression_planner(defval);
+ newval->expr = defval;
newval->is_generated = (colDef->generated != '\0');
tab->newvals = lappend(tab->newvals, newval);
- }
- if (DomainHasConstraints(attribute->atttypid))
- tab->rewrite |= AT_REWRITE_DEFAULT_VAL;
+ /*
+ * Attempt to skip a complete table rewrite by storing the
+ * specified DEFAULT value outside of the heap. This is only
+ * allowed for plain relations and non-generated columns, and the
+ * default expression can't be volatile (stable is OK). Note that
+ * contain_volatile_functions deems CoerceToDomain immutable, but
+ * here we consider that coercion to a domain with constraints is
+ * volatile; else it might fail even when the table is empty.
+ */
+ if (rel->rd_rel->relkind == RELKIND_RELATION &&
+ !colDef->generated &&
+ !has_domain_constraints &&
+ !contain_volatile_functions((Node *) defval))
+ {
+ EState *estate;
+ ExprState *exprState;
+ Datum missingval;
+ bool missingIsNull;
+
+ /* Evaluate the default expression */
+ estate = CreateExecutorState();
+ exprState = ExecPrepareExpr(defval, estate);
+ missingval = ExecEvalExpr(exprState,
+ GetPerTupleExprContext(estate),
+ &missingIsNull);
+ /* If it turns out NULL, nothing to do; else store it */
+ if (!missingIsNull)
+ {
+ StoreAttrMissingVal(rel, attribute->attnum, missingval);
+ has_missing = true;
+ }
+ FreeExecutorState(estate);
+ }
+ else
+ {
+ /*
+ * Failed to use missing mode. We have to do a table rewrite
+ * to install the value --- unless it's a virtual generated
+ * column.
+ */
+ if (colDef->generated != ATTRIBUTE_GENERATED_VIRTUAL)
+ tab->rewrite |= AT_REWRITE_DEFAULT_VAL;
+ }
+ }
- if (!TupleDescAttr(rel->rd_att, attribute->attnum - 1)->atthasmissing)
+ if (!has_missing)
{
/*
* If the new column is NOT NULL, and there is no missing value,
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index 19c594458bd..1c67e329264 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -29,7 +29,7 @@ typedef struct RawColumnDefault
{
AttrNumber attnum; /* attribute to attach default to */
Node *raw_default; /* default value (untransformed parse tree) */
- bool missingMode; /* true if part of add column processing */
+ bool missingMode; /* obsolete, no longer used */
char generated; /* attgenerated setting */
} RawColumnDefault;
@@ -121,6 +121,9 @@ extern List *AddRelationNotNullConstraints(Relation rel,
List *old_notnulls);
extern void RelationClearMissing(Relation rel);
+
+extern void StoreAttrMissingVal(Relation rel, AttrNumber attnum,
+ Datum missingval);
extern void SetAttrMissing(Oid relid, char *attname, char *value);
extern Node *cookDefault(ParseState *pstate,
diff --git a/src/test/regress/expected/fast_default.out b/src/test/regress/expected/fast_default.out
index 272b57e48cd..ccbcdf8403f 100644
--- a/src/test/regress/expected/fast_default.out
+++ b/src/test/regress/expected/fast_default.out
@@ -257,6 +257,71 @@ SELECT comp();
(1 row)
DROP TABLE T;
+-- Test domains with default value for table rewrite.
+CREATE DOMAIN domain1 AS int DEFAULT 11; -- constant
+CREATE DOMAIN domain2 AS int DEFAULT random(min=>10, max=>100); -- volatile
+CREATE DOMAIN domain3 AS text DEFAULT foo(4); -- stable
+CREATE DOMAIN domain4 AS text[]
+ DEFAULT ('{"This", "is", "' || foo(4) || '","the", "real", "world"}')::TEXT[];
+CREATE TABLE t2 (a domain1);
+INSERT INTO t2 VALUES (1),(2);
+-- no table rewrite
+ALTER TABLE t2 ADD COLUMN b domain1 default 3;
+SELECT attnum, attname, atthasmissing, atthasdef, attmissingval
+FROM pg_attribute
+WHERE attnum > 0 AND attrelid = 't2'::regclass
+ORDER BY attnum;
+ attnum | attname | atthasmissing | atthasdef | attmissingval
+--------+---------+---------------+-----------+---------------
+ 1 | a | f | f |
+ 2 | b | t | t | {3}
+(2 rows)
+
+-- table rewrite should happen
+ALTER TABLE t2 ADD COLUMN c domain3 default left(random()::text,3);
+NOTICE: rewriting table t2 for reason 2
+-- no table rewrite
+ALTER TABLE t2 ADD COLUMN d domain4;
+SELECT attnum, attname, atthasmissing, atthasdef, attmissingval
+FROM pg_attribute
+WHERE attnum > 0 AND attrelid = 't2'::regclass
+ORDER BY attnum;
+ attnum | attname | atthasmissing | atthasdef | attmissingval
+--------+---------+---------------+-----------+-----------------------------------
+ 1 | a | f | f |
+ 2 | b | f | t |
+ 3 | c | f | t |
+ 4 | d | t | f | {"{This,is,abcd,the,real,world}"}
+(4 rows)
+
+-- table rewrite should happen
+ALTER TABLE t2 ADD COLUMN e domain2;
+NOTICE: rewriting table t2 for reason 2
+SELECT attnum, attname, atthasmissing, atthasdef, attmissingval
+FROM pg_attribute
+WHERE attnum > 0 AND attrelid = 't2'::regclass
+ORDER BY attnum;
+ attnum | attname | atthasmissing | atthasdef | attmissingval
+--------+---------+---------------+-----------+---------------
+ 1 | a | f | f |
+ 2 | b | f | t |
+ 3 | c | f | t |
+ 4 | d | f | f |
+ 5 | e | f | f |
+(5 rows)
+
+SELECT a, b, length(c) = 3 as c_ok, d, e >= 10 as e_ok FROM t2;
+ a | b | c_ok | d | e_ok
+---+---+------+-------------------------------+------
+ 1 | 3 | t | {This,is,abcd,the,real,world} | t
+ 2 | 3 | t | {This,is,abcd,the,real,world} | t
+(2 rows)
+
+DROP TABLE t2;
+DROP DOMAIN domain1;
+DROP DOMAIN domain2;
+DROP DOMAIN domain3;
+DROP DOMAIN domain4;
DROP FUNCTION foo(INT);
-- Fall back to full rewrite for volatile expressions
CREATE TABLE T(pk INT NOT NULL PRIMARY KEY);
diff --git a/src/test/regress/sql/fast_default.sql b/src/test/regress/sql/fast_default.sql
index 6e7f37b17b2..068dd0bc8aa 100644
--- a/src/test/regress/sql/fast_default.sql
+++ b/src/test/regress/sql/fast_default.sql
@@ -248,6 +248,50 @@ SELECT comp();
DROP TABLE T;
+-- Test domains with default value for table rewrite.
+CREATE DOMAIN domain1 AS int DEFAULT 11; -- constant
+CREATE DOMAIN domain2 AS int DEFAULT random(min=>10, max=>100); -- volatile
+CREATE DOMAIN domain3 AS text DEFAULT foo(4); -- stable
+CREATE DOMAIN domain4 AS text[]
+ DEFAULT ('{"This", "is", "' || foo(4) || '","the", "real", "world"}')::TEXT[];
+
+CREATE TABLE t2 (a domain1);
+INSERT INTO t2 VALUES (1),(2);
+
+-- no table rewrite
+ALTER TABLE t2 ADD COLUMN b domain1 default 3;
+
+SELECT attnum, attname, atthasmissing, atthasdef, attmissingval
+FROM pg_attribute
+WHERE attnum > 0 AND attrelid = 't2'::regclass
+ORDER BY attnum;
+
+-- table rewrite should happen
+ALTER TABLE t2 ADD COLUMN c domain3 default left(random()::text,3);
+
+-- no table rewrite
+ALTER TABLE t2 ADD COLUMN d domain4;
+
+SELECT attnum, attname, atthasmissing, atthasdef, attmissingval
+FROM pg_attribute
+WHERE attnum > 0 AND attrelid = 't2'::regclass
+ORDER BY attnum;
+
+-- table rewrite should happen
+ALTER TABLE t2 ADD COLUMN e domain2;
+
+SELECT attnum, attname, atthasmissing, atthasdef, attmissingval
+FROM pg_attribute
+WHERE attnum > 0 AND attrelid = 't2'::regclass
+ORDER BY attnum;
+
+SELECT a, b, length(c) = 3 as c_ok, d, e >= 10 as e_ok FROM t2;
+
+DROP TABLE t2;
+DROP DOMAIN domain1;
+DROP DOMAIN domain2;
+DROP DOMAIN domain3;
+DROP DOMAIN domain4;
DROP FUNCTION foo(INT);
-- Fall back to full rewrite for volatile expressions