aboutsummaryrefslogtreecommitdiff
path: root/src/backend/commands/copyfromparse.c
diff options
context:
space:
mode:
authorAndrew Dunstan <andrew@dunslane.net>2023-03-13 10:01:56 -0400
committerAndrew Dunstan <andrew@dunslane.net>2023-03-13 10:01:56 -0400
commit9f8377f7a27910bf0f35bf5169a8046731948a79 (patch)
treee0a19449e4cebb8923dfcd1bba95a1d3363faa32 /src/backend/commands/copyfromparse.c
parent7b14e20b12cc8358cad9bdd05dd6b7de7f73c431 (diff)
downloadpostgresql-9f8377f7a27910bf0f35bf5169a8046731948a79.tar.gz
postgresql-9f8377f7a27910bf0f35bf5169a8046731948a79.zip
Add a DEFAULT option to COPY FROM
This allows for a string which if an input field matches causes the column's default value to be inserted. The advantage of this is that the default can be inserted in some rows and not others, for which non-default data is available. The file_fdw extension is also modified to take allow use of this option. Israel Barth Rubio Discussion: https://postgr.es/m/CAO_rXXAcqesk6DsvioOZ5zmeEmpUN5ktZf-9=9yu+DTr0Xr8Uw@mail.gmail.com
Diffstat (limited to 'src/backend/commands/copyfromparse.c')
-rw-r--r--src/backend/commands/copyfromparse.c85
1 files changed, 77 insertions, 8 deletions
diff --git a/src/backend/commands/copyfromparse.c b/src/backend/commands/copyfromparse.c
index 91b564c2bcb..c346486cd31 100644
--- a/src/backend/commands/copyfromparse.c
+++ b/src/backend/commands/copyfromparse.c
@@ -842,9 +842,10 @@ NextCopyFromRawFields(CopyFromState cstate, char ***fields, int *nfields)
/*
* Read next tuple from file for COPY FROM. Return false if no more tuples.
*
- * 'econtext' is used to evaluate default expression for each column not
- * read from the file. It can be NULL when no default values are used, i.e.
- * when all columns are read from the file.
+ * 'econtext' is used to evaluate default expression for each column that is
+ * either not read from the file or is using the DEFAULT option of COPY FROM.
+ * It can be NULL when no default values are used, i.e. when all columns are
+ * read from the file, and DEFAULT option is unset.
*
* 'values' and 'nulls' arrays must be the same length as columns of the
* relation passed to BeginCopyFrom. This function fills the arrays.
@@ -870,6 +871,7 @@ NextCopyFrom(CopyFromState cstate, ExprContext *econtext,
/* Initialize all values for row to NULL */
MemSet(values, 0, num_phys_attrs * sizeof(Datum));
MemSet(nulls, true, num_phys_attrs * sizeof(bool));
+ cstate->defaults = (bool *) palloc0(num_phys_attrs * sizeof(bool));
if (!cstate->opts.binary)
{
@@ -938,12 +940,27 @@ NextCopyFrom(CopyFromState cstate, ExprContext *econtext,
cstate->cur_attname = NameStr(att->attname);
cstate->cur_attval = string;
- values[m] = InputFunctionCall(&in_functions[m],
- string,
- typioparams[m],
- att->atttypmod);
+
if (string != NULL)
nulls[m] = false;
+
+ if (cstate->defaults[m])
+ {
+ /*
+ * The caller must supply econtext and have switched into the
+ * per-tuple memory context in it.
+ */
+ Assert(econtext != NULL);
+ Assert(CurrentMemoryContext == econtext->ecxt_per_tuple_memory);
+
+ values[m] = ExecEvalExpr(defexprs[m], econtext, &nulls[m]);
+ }
+ else
+ values[m] = InputFunctionCall(&in_functions[m],
+ string,
+ typioparams[m],
+ att->atttypmod);
+
cstate->cur_attname = NULL;
cstate->cur_attval = NULL;
}
@@ -1019,10 +1036,12 @@ NextCopyFrom(CopyFromState cstate, ExprContext *econtext,
Assert(econtext != NULL);
Assert(CurrentMemoryContext == econtext->ecxt_per_tuple_memory);
- values[defmap[i]] = ExecEvalExpr(defexprs[i], econtext,
+ values[defmap[i]] = ExecEvalExpr(defexprs[defmap[i]], econtext,
&nulls[defmap[i]]);
}
+ pfree(cstate->defaults);
+
return true;
}
@@ -1663,6 +1682,31 @@ CopyReadAttributesText(CopyFromState cstate)
if (input_len == cstate->opts.null_print_len &&
strncmp(start_ptr, cstate->opts.null_print, input_len) == 0)
cstate->raw_fields[fieldno] = NULL;
+ /* Check whether raw input matched default marker */
+ else if (cstate->opts.default_print &&
+ input_len == cstate->opts.default_print_len &&
+ strncmp(start_ptr, cstate->opts.default_print, input_len) == 0)
+ {
+ /* fieldno is 0-indexed and attnum is 1-indexed */
+ int m = list_nth_int(cstate->attnumlist, fieldno) - 1;
+
+ if (cstate->defexprs[m] != NULL)
+ {
+ /* defaults contain entries for all physical attributes */
+ cstate->defaults[m] = true;
+ }
+ else
+ {
+ TupleDesc tupDesc = RelationGetDescr(cstate->rel);
+ Form_pg_attribute att = TupleDescAttr(tupDesc, m);
+
+ ereport(ERROR,
+ (errcode(ERRCODE_BAD_COPY_FILE_FORMAT),
+ errmsg("unexpected DEFAULT in COPY data"),
+ errdetail("Column \"%s\" has no DEFAULT value.",
+ NameStr(att->attname))));
+ }
+ }
else
{
/*
@@ -1852,6 +1896,31 @@ endfield:
if (!saw_quote && input_len == cstate->opts.null_print_len &&
strncmp(start_ptr, cstate->opts.null_print, input_len) == 0)
cstate->raw_fields[fieldno] = NULL;
+ /* Check whether raw input matched default marker */
+ else if (cstate->opts.default_print &&
+ input_len == cstate->opts.default_print_len &&
+ strncmp(start_ptr, cstate->opts.default_print, input_len) == 0)
+ {
+ /* fieldno is 0-index and attnum is 1-index */
+ int m = list_nth_int(cstate->attnumlist, fieldno) - 1;
+
+ if (cstate->defexprs[m] != NULL)
+ {
+ /* defaults contain entries for all physical attributes */
+ cstate->defaults[m] = true;
+ }
+ else
+ {
+ TupleDesc tupDesc = RelationGetDescr(cstate->rel);
+ Form_pg_attribute att = TupleDescAttr(tupDesc, m);
+
+ ereport(ERROR,
+ (errcode(ERRCODE_BAD_COPY_FILE_FORMAT),
+ errmsg("unexpected DEFAULT in COPY data"),
+ errdetail("Column \"%s\" has no DEFAULT value.",
+ NameStr(att->attname))));
+ }
+ }
fieldno++;
/* Done if we hit EOL instead of a delim */