1
0
mirror of https://github.com/postgres/postgres.git synced 2025-07-15 19:21:59 +03:00

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
This commit is contained in:
Andrew Dunstan
2023-03-13 10:01:56 -04:00
parent 7b14e20b12
commit 9f8377f7a2
15 changed files with 447 additions and 24 deletions

View File

@ -464,6 +464,12 @@ ProcessCopyOptions(ParseState *pstate,
errorConflictingDefElem(defel, pstate);
opts_out->null_print = defGetString(defel);
}
else if (strcmp(defel->defname, "default") == 0)
{
if (opts_out->default_print)
errorConflictingDefElem(defel, pstate);
opts_out->default_print = defGetString(defel);
}
else if (strcmp(defel->defname, "header") == 0)
{
if (header_specified)
@ -577,6 +583,11 @@ ProcessCopyOptions(ParseState *pstate,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("cannot specify NULL in BINARY mode")));
if (opts_out->binary && opts_out->default_print)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("cannot specify DEFAULT in BINARY mode")));
/* Set defaults for omitted options */
if (!opts_out->delim)
opts_out->delim = opts_out->csv_mode ? "," : "\t";
@ -612,6 +623,17 @@ ProcessCopyOptions(ParseState *pstate,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("COPY null representation cannot use newline or carriage return")));
if (opts_out->default_print)
{
opts_out->default_print_len = strlen(opts_out->default_print);
if (strchr(opts_out->default_print, '\r') != NULL ||
strchr(opts_out->default_print, '\n') != NULL)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("COPY default representation cannot use newline or carriage return")));
}
/*
* Disallow unsafe delimiter characters in non-CSV mode. We can't allow
* backslash because it would be ambiguous. We can't allow the other
@ -705,6 +727,35 @@ ProcessCopyOptions(ParseState *pstate,
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("CSV quote character must not appear in the NULL specification")));
if (opts_out->default_print)
{
if (!is_from)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("COPY DEFAULT only available using COPY FROM")));
/* Don't allow the delimiter to appear in the default string. */
if (strchr(opts_out->default_print, opts_out->delim[0]) != NULL)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("COPY delimiter must not appear in the DEFAULT specification")));
/* Don't allow the CSV quote char to appear in the default string. */
if (opts_out->csv_mode &&
strchr(opts_out->default_print, opts_out->quote[0]) != NULL)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("CSV quote character must not appear in the DEFAULT specification")));
/* Don't allow the NULL and DEFAULT string to be the same */
if (opts_out->null_print_len == opts_out->default_print_len &&
strncmp(opts_out->null_print, opts_out->default_print,
opts_out->null_print_len) == 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("NULL specification and DEFAULT specification cannot be the same")));
}
}
/*

View File

@ -1565,11 +1565,11 @@ BeginCopyFrom(ParseState *pstate,
&in_func_oid, &typioparams[attnum - 1]);
fmgr_info(in_func_oid, &in_functions[attnum - 1]);
/* Get default info if needed */
if (!list_member_int(cstate->attnumlist, attnum) && !att->attgenerated)
/* Get default info if available */
defexprs[attnum - 1] = NULL;
if (!att->attgenerated)
{
/* attribute is NOT to be copied from input */
/* use default value if one exists */
Expr *defexpr = (Expr *) build_column_default(cstate->rel,
attnum);
@ -1579,9 +1579,15 @@ BeginCopyFrom(ParseState *pstate,
defexpr = expression_planner(defexpr);
/* Initialize executable expression in copycontext */
defexprs[num_defaults] = ExecInitExpr(defexpr, NULL);
defmap[num_defaults] = attnum - 1;
num_defaults++;
defexprs[attnum - 1] = ExecInitExpr(defexpr, NULL);
/* if NOT copied from input */
/* use default value if one exists */
if (!list_member_int(cstate->attnumlist, attnum))
{
defmap[num_defaults] = attnum - 1;
num_defaults++;
}
/*
* If a default expression looks at the table being loaded,

View File

@ -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 */