aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/src/sgml/ref/copy.sgml25
-rw-r--r--src/backend/commands/copy.c399
-rw-r--r--src/test/regress/expected/copy2.out56
-rw-r--r--src/test/regress/sql/copy2.sql49
4 files changed, 319 insertions, 210 deletions
diff --git a/doc/src/sgml/ref/copy.sgml b/doc/src/sgml/ref/copy.sgml
index a1a5e9baa76..484b0a62087 100644
--- a/doc/src/sgml/ref/copy.sgml
+++ b/doc/src/sgml/ref/copy.sgml
@@ -1,5 +1,5 @@
<!--
-$Header: /cvsroot/pgsql/doc/src/sgml/ref/copy.sgml,v 1.33 2002/07/18 04:43:50 momjian Exp $
+$Header: /cvsroot/pgsql/doc/src/sgml/ref/copy.sgml,v 1.34 2002/07/30 16:55:05 momjian Exp $
PostgreSQL documentation
-->
@@ -199,12 +199,12 @@ ERROR: <replaceable>reason</replaceable>
whatever is in the table already).
</para>
- <para>
- When using the optional column list syntax, <command>COPY TO</command>
- and <command>COPY FROM</command> will only copy the specified
- columns' values to/from the table. If a column in the table
- is not in the column list, <command>COPY FROM</command> will insert
- default values for that column if a default value is defined.
+ <para>
+ If a list of columns is specified, <command>COPY</command> will
+ only copy the data in the specified columns to or from the table.
+ If there are any columns in the table that are not in the table,
+ <command>COPY FROM</command> will insert the default value for
+ that column.
</para>
<para>
@@ -266,8 +266,8 @@ ERROR: <replaceable>reason</replaceable>
</para>
<para>
- <command>COPY FROM</command> neither invokes rules nor acts on column
- defaults. It does invoke triggers and check constraints.
+ <command>COPY FROM</command> will invoke any triggers or check
+ constraints. However, it will not invoke rules.
</para>
<para>
@@ -330,7 +330,12 @@ ERROR: <replaceable>reason</replaceable>
The attribute values themselves are strings generated by the
output function, or acceptable to the input function, of each
attribute's data type. The specified null-value string is used in
- place of attributes that are NULL.
+ place of attributes that are NULL. When using <command>COPY
+ FROM</command> without a column list, each row of the input file
+ must contain data for each attribute in the table: no missing data
+ is allowed. Similarly, <command>COPY FROM</command> will raise
+ an error if it encounters any data in the input file that would
+ not be inserted into the table: extra data is not allowed.
</para>
<para>
If OIDS is specified, the OID is read or written as the first column,
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 272e45d384e..19e17ed2873 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -7,7 +7,7 @@
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/commands/copy.c,v 1.160 2002/07/20 05:16:57 momjian Exp $
+ * $Header: /cvsroot/pgsql/src/backend/commands/copy.c,v 1.161 2002/07/30 16:55:06 momjian Exp $
*
*-------------------------------------------------------------------------
*/
@@ -45,16 +45,21 @@
#define ISOCTAL(c) (((c) >= '0') && ((c) <= '7'))
#define OCTVALUE(c) ((c) - '0')
+typedef enum CopyReadResult
+{
+ NORMAL_ATTR,
+ END_OF_LINE,
+ END_OF_FILE
+} CopyReadResult;
/* non-export function prototypes */
static void CopyTo(Relation rel, List *attlist, bool binary, bool oids, FILE *fp, char *delim, char *null_print);
static void CopyFrom(Relation rel, List *attlist, bool binary, bool oids, FILE *fp, char *delim, char *null_print);
static Oid GetInputFunction(Oid type);
static Oid GetTypeElement(Oid type);
-static void CopyReadNewline(FILE *fp, int *newline);
-static char *CopyReadAttribute(FILE *fp, bool *isnull, char *delim, int *newline, char *null_print);
+static char *CopyReadAttribute(FILE *fp, const char *delim, CopyReadResult *result);
static void CopyAttributeOut(FILE *fp, char *string, char *delim);
-static void CopyAssertAttlist(Relation rel, List* attlist, bool from);
+static void CopyCheckAttlist(Relation rel, List *attlist);
static const char BinarySignature[12] = "PGBCOPY\n\377\r\n\0";
@@ -271,14 +276,10 @@ DoCopy(const CopyStmt *stmt)
bool pipe = (stmt->filename == NULL);
List *option;
List *attlist = stmt->attlist;
- DefElem *dbinary = NULL;
- DefElem *doids = NULL;
- DefElem *ddelim = NULL;
- DefElem *dnull = NULL;
bool binary = false;
bool oids = false;
- char *delim = "\t";
- char *null_print = "\\N";
+ char *delim = NULL;
+ char *null_print = NULL;
FILE *fp;
Relation rel;
AclMode required_access = (is_from ? ACL_INSERT : ACL_SELECT);
@@ -289,51 +290,53 @@ DoCopy(const CopyStmt *stmt)
{
DefElem *defel = (DefElem *) lfirst(option);
+ /* XXX: Should we bother checking for doubled options? */
+
if (strcmp(defel->defname, "binary") == 0)
{
- if (dbinary)
- /* should this really be an error? */
+ if (binary)
elog(ERROR, "COPY: BINARY option appears more than once");
- dbinary = defel;
+
+ binary = intVal(defel->arg);
}
else if (strcmp(defel->defname, "oids") == 0)
{
- if (doids)
- /* should this really be an error? */
+ if (oids)
elog(ERROR, "COPY: OIDS option appears more than once");
- doids = defel;
+
+ oids = intVal(defel->arg);
}
else if (strcmp(defel->defname, "delimiter") == 0)
{
- if (ddelim)
+ if (delim)
elog(ERROR, "COPY: DELIMITER string may only be defined once in query");
- ddelim = defel;
+
+ delim = strVal(defel->arg);
}
else if (strcmp(defel->defname, "null") == 0)
{
- if (dnull)
+ if (null_print)
elog(ERROR, "COPY: NULL representation may only be defined once in query");
- dnull = defel;
+
+ null_print = strVal(defel->arg);
}
else
elog(ERROR, "COPY: option \"%s\" not recognized",
defel->defname);
}
- if (dbinary)
- binary = intVal(dbinary->arg);
- if (doids)
- oids = intVal(doids->arg);
- if (ddelim)
- delim = strVal(ddelim->arg);
- if (dnull)
- null_print = strVal(dnull->arg);
-
- if (binary && ddelim)
+ if (binary && delim)
elog(ERROR, "You can not specify the DELIMITER in BINARY mode.");
- if (binary && dnull)
+ if (binary && null_print)
elog(ERROR, "You can not specify NULL in BINARY mode.");
+
+ /* Set defaults */
+ if (!delim)
+ delim = "\t";
+
+ if (!null_print)
+ null_print = "\\N";
/*
* Open and lock the relation, using the appropriate lock type.
@@ -364,6 +367,13 @@ DoCopy(const CopyStmt *stmt)
elog(ERROR, "COPY delimiter must be a single character");
/*
+ * Don't allow COPY w/ OIDs to or from a table without them
+ */
+ if (oids && !rel->rd_rel->relhasoids)
+ elog(ERROR, "COPY: table \"%s\" does not have OIDs",
+ RelationGetRelationName(rel));
+
+ /*
* Set up variables to avoid per-attribute overhead.
*/
initStringInfo(&attribute_buf);
@@ -372,22 +382,24 @@ DoCopy(const CopyStmt *stmt)
server_encoding = GetDatabaseEncoding();
#endif
- if( attlist == NIL ){
+ if (attlist == NIL)
+ {
/* get list of attributes in the relation */
TupleDesc desc = RelationGetDescr(rel);
int i;
- for(i = 0; i < desc->natts; ++i){
- Ident* id = makeNode(Ident);
+ for (i = 0; i < desc->natts; ++i)
+ {
+ Ident *id = makeNode(Ident);
id->name = NameStr(desc->attrs[i]->attname);
attlist = lappend(attlist,id);
}
}
- else{
- if( binary ){
+ else
+ {
+ if (binary)
elog(ERROR,"COPY: BINARY format cannot be used with specific column list");
- }
- /* verify that any user-specified attributes exist in the relation */
- CopyAssertAttlist(rel,attlist,is_from);
+
+ CopyCheckAttlist(rel, attlist);
}
if (is_from)
@@ -532,27 +544,28 @@ CopyTo(Relation rel, List *attlist, bool binary, bool oids,
int16 fld_size;
char *string;
Snapshot mySnapshot;
- int copy_attr_count;
- int* attmap;
- int p = 0;
- List* cur;
-
- if (oids && !rel->rd_rel->relhasoids)
- elog(ERROR, "COPY: table %s does not have OIDs",
- RelationGetRelationName(rel));
+ int copy_attr_count;
+ int *attmap;
+ int p = 0;
+ List *cur;
tupDesc = rel->rd_att;
attr_count = rel->rd_att->natts;
attr = rel->rd_att->attrs;
+
copy_attr_count = length(attlist);
+ attmap = (int *) palloc(copy_attr_count * sizeof(int));
+
+ foreach(cur, attlist)
{
- attmap = (int*)palloc(copy_attr_count * sizeof(int));
- foreach(cur,attlist){
- for (i = 0; i < attr_count; i++){
- if( strcmp(strVal(lfirst(cur)),NameStr(attr[i]->attname)) == 0){
- attmap[p++] = i;
- continue;
- }
+ const char *currAtt = strVal(lfirst(cur));
+
+ for (i = 0; i < attr_count; i++)
+ {
+ if (namestrcmp(&attr[i]->attname, currAtt) == 0)
+ {
+ attmap[p++] = i;
+ continue;
}
}
}
@@ -642,7 +655,7 @@ CopyTo(Relation rel, List *attlist, bool binary, bool oids,
Datum origvalue,
value;
bool isnull;
- int mi = attmap[i];
+ int mi = attmap[i];
origvalue = heap_getattr(tuple, mi + 1, tupDesc, &isnull);
@@ -767,19 +780,15 @@ CopyFrom(Relation rel, List *attlist, bool binary, bool oids,
Oid in_func_oid;
Datum *values;
char *nulls;
- bool isnull;
int done = 0;
- char *string;
ResultRelInfo *resultRelInfo;
EState *estate = CreateExecutorState(); /* for ExecConstraints() */
TupleTable tupleTable;
TupleTableSlot *slot;
- Oid loaded_oid = InvalidOid;
- bool skip_tuple = false;
bool file_has_oids;
- int* attmap = NULL;
- int* defmap = NULL;
- Node** defexprs = NULL; /* array of default att expressions */
+ int *attmap = NULL;
+ int *defmap = NULL;
+ Node **defexprs = NULL; /* array of default att expressions */
ExprContext *econtext; /* used for ExecEvalExpr for default atts */
ExprDoneCond isdone;
@@ -810,40 +819,47 @@ CopyFrom(Relation rel, List *attlist, bool binary, bool oids,
slot = ExecAllocTableSlot(tupleTable);
ExecSetSlotDescriptor(slot, tupDesc, false);
-
if (!binary)
{
/*
* pick up the input function and default expression (if any) for
* each attribute in the relation.
*/
- List* cur;
- attmap = (int*)palloc(sizeof(int) * attr_count);
- defmap = (int*)palloc(sizeof(int) * attr_count);
- defexprs = (Node**)palloc(sizeof(Node*) * attr_count);
+ attmap = (int *) palloc(sizeof(int) * attr_count);
+ defmap = (int *) palloc(sizeof(int) * attr_count);
+ defexprs = (Node **) palloc(sizeof(Node *) * attr_count);
in_functions = (FmgrInfo *) palloc(attr_count * sizeof(FmgrInfo));
elements = (Oid *) palloc(attr_count * sizeof(Oid));
for (i = 0; i < attr_count; i++)
{
- int p = 0;
- bool specified = false;
+ List *l;
+ int p = 0;
+ bool specified = false;
+
in_func_oid = (Oid) GetInputFunction(attr[i]->atttypid);
fmgr_info(in_func_oid, &in_functions[i]);
elements[i] = GetTypeElement(attr[i]->atttypid);
- foreach(cur,attlist){
- if( strcmp(strVal(lfirst(cur)),NameStr(attr[i]->attname)) == 0){
+
+ foreach(l, attlist)
+ {
+ if (namestrcmp(&attr[i]->attname, strVal(lfirst(l))) == 0)
+ {
attmap[p] = i;
specified = true;
continue;
}
- ++p;
+ p++;
}
- if( ! specified ){
- /* column not specified, try to get a default */
- defexprs[def_attr_count] = build_column_default(rel,i+1);
- if( defexprs[def_attr_count] != NULL ){
+
+ /* if column not specified, use default value if one exists */
+ if (! specified)
+ {
+ defexprs[def_attr_count] = build_column_default(rel, i + 1);
+
+ if (defexprs[def_attr_count] != NULL)
+ {
defmap[def_attr_count] = i;
- ++def_attr_count;
+ def_attr_count++;
}
}
}
@@ -857,13 +873,11 @@ CopyFrom(Relation rel, List *attlist, bool binary, bool oids,
/* Signature */
CopyGetData(readSig, 12, fp);
- if (CopyGetEof(fp) ||
- memcmp(readSig, BinarySignature, 12) != 0)
+ if (CopyGetEof(fp) || memcmp(readSig, BinarySignature, 12) != 0)
elog(ERROR, "COPY BINARY: file signature not recognized");
/* Integer layout field */
CopyGetData(&tmp, sizeof(int32), fp);
- if (CopyGetEof(fp) ||
- tmp != 0x01020304)
+ if (CopyGetEof(fp) || tmp != 0x01020304)
elog(ERROR, "COPY BINARY: incompatible integer layout");
/* Flags field */
CopyGetData(&tmp, sizeof(int32), fp);
@@ -875,8 +889,7 @@ CopyFrom(Relation rel, List *attlist, bool binary, bool oids,
elog(ERROR, "COPY BINARY: unrecognized critical flags in header");
/* Header extension length */
CopyGetData(&tmp, sizeof(int32), fp);
- if (CopyGetEof(fp) ||
- tmp < 0)
+ if (CopyGetEof(fp) || tmp < 0)
elog(ERROR, "COPY BINARY: bogus file header (missing length)");
/* Skip extension header, if present */
while (tmp-- > 0)
@@ -890,10 +903,6 @@ CopyFrom(Relation rel, List *attlist, bool binary, bool oids,
elements = NULL;
}
- /* Silently drop incoming OIDs if table does not have OIDs */
- if (!rel->rd_rel->relhasoids)
- oids = false;
-
values = (Datum *) palloc(attr_count * sizeof(Datum));
nulls = (char *) palloc(attr_count * sizeof(char));
@@ -904,6 +913,9 @@ CopyFrom(Relation rel, List *attlist, bool binary, bool oids,
while (!done)
{
+ bool skip_tuple;
+ Oid loaded_oid;
+
CHECK_FOR_INTERRUPTS();
copy_lineno++;
@@ -917,16 +929,17 @@ CopyFrom(Relation rel, List *attlist, bool binary, bool oids,
if (!binary)
{
- int newline = 0;
+ CopyReadResult result;
+ char *string;
if (file_has_oids)
{
- string = CopyReadAttribute(fp, &isnull, delim,
- &newline, null_print);
- if (isnull)
+ string = CopyReadAttribute(fp, delim, &result);
+
+ if (result == END_OF_FILE)
+ done = 1;
+ else if (string == NULL || strcmp(string, null_print) == 0)
elog(ERROR, "COPY TEXT: NULL Oid");
- else if (string == NULL)
- done = 1; /* end of file */
else
{
loaded_oid = DatumGetObjectId(DirectFunctionCall1(oidin,
@@ -943,36 +956,45 @@ CopyFrom(Relation rel, List *attlist, bool binary, bool oids,
for (i = 0; i < copy_attr_count && !done; i++)
{
int m = attmap[i];
- string = CopyReadAttribute(fp, &isnull, delim,
- &newline, null_print);
- if( isnull ){
- /* nothing */
+ string = CopyReadAttribute(fp, delim, &result);
+
+ /* If we got an end-of-line before we expected, bail out */
+ if (result == END_OF_LINE && i < (copy_attr_count - 1))
+ elog(ERROR, "COPY TEXT: Missing data for attribute %d", i + 1);
+
+ if (result == END_OF_FILE)
+ done = 1;
+ else if (strcmp(string, null_print) == 0)
+ {
+ /* we read an SQL NULL, no need to do anything */
}
- else if (string == NULL)
- done = 1; /* end of file */
else
{
values[m] = FunctionCall3(&in_functions[m],
- CStringGetDatum(string),
- ObjectIdGetDatum(elements[m]),
- Int32GetDatum(attr[m]->atttypmod));
+ CStringGetDatum(string),
+ ObjectIdGetDatum(elements[m]),
+ Int32GetDatum(attr[m]->atttypmod));
nulls[m] = ' ';
}
}
-
+
+ if (result == NORMAL_ATTR && !done)
+ elog(ERROR, "COPY TEXT: Extra data encountered");
+
/*
* as above, we only try a default lookup if one is
* known to be available
*/
- for (i = 0; i < def_attr_count && !done; i++){
+ for (i = 0; i < def_attr_count && !done; i++)
+ {
bool isnull;
- values[defmap[i]] = ExecEvalExpr(defexprs[i],econtext,&isnull,&isdone);
- if( ! isnull )
- nulls[defmap[i]] = ' ';
+ values[defmap[i]] = ExecEvalExpr(defexprs[i], econtext,
+ &isnull, &isdone);
+
+ if (! isnull)
+ nulls[defmap[i]] = ' ';
}
- if (!done)
- CopyReadNewline(fp, &newline);
}
else
{ /* binary */
@@ -980,8 +1002,7 @@ CopyFrom(Relation rel, List *attlist, bool binary, bool oids,
fld_size;
CopyGetData(&fld_count, sizeof(int16), fp);
- if (CopyGetEof(fp) ||
- fld_count == -1)
+ if (CopyGetEof(fp) || fld_count == -1)
done = 1;
else
{
@@ -1173,36 +1194,25 @@ GetTypeElement(Oid type)
return result;
}
-
-/*
- * Reads input from fp until an end of line is seen.
- */
-
-static void
-CopyReadNewline(FILE *fp, int *newline)
-{
- if (!*newline)
- {
- elog(WARNING, "CopyReadNewline: extra fields ignored");
- while (!CopyGetEof(fp) && (CopyGetChar(fp) != '\n'));
- }
- *newline = 0;
-}
-
/*
* Read the value of a single attribute.
*
- * Result is either a string, or NULL (if EOF or a null attribute).
- * Note that the caller should not pfree the string!
+ * Results are returned in the status indicator, as well as the
+ * return value. If a value was successfully read but there is
+ * more to read before EOL, NORMAL_ATTR is set and the value read
+ * is returned. If a value was read and we hit EOL, END_OF_LINE
+ * is set and the value read is returned. If we hit the EOF,
+ * END_OF_FILE is set and NULL is returned.
+ *
+ * Note: This function does not care about SQL NULL values -- it
+ * is the caller's responsibility to check if the returned string
+ * matches what the user specified for the SQL NULL value.
*
- * *isnull is set true if a null attribute, else false.
- * delim is the column delimiter string (currently always 1 character).
- * *newline remembers whether we've seen a newline ending this tuple.
- * null_print says how NULL values are represented
+ * delim is the column delimiter string.
*/
static char *
-CopyReadAttribute(FILE *fp, bool *isnull, char *delim, int *newline, char *null_print)
+CopyReadAttribute(FILE *fp, const char *delim, CopyReadResult *result)
{
int c;
int delimc = (unsigned char)delim[0];
@@ -1220,23 +1230,20 @@ CopyReadAttribute(FILE *fp, bool *isnull, char *delim, int *newline, char *null_
attribute_buf.len = 0;
attribute_buf.data[0] = '\0';
- /* if last delimiter was a newline return a NULL attribute */
- if (*newline)
- {
- *isnull = (bool) true;
- return NULL;
- }
-
- *isnull = (bool) false; /* set default */
+ /* set default */
+ *result = NORMAL_ATTR;
for (;;)
{
c = CopyGetChar(fp);
if (c == EOF)
- goto endOfFile;
+ {
+ *result = END_OF_FILE;
+ return NULL;
+ }
if (c == '\n')
{
- *newline = 1;
+ *result = END_OF_LINE;
break;
}
if (c == delimc)
@@ -1245,7 +1252,10 @@ CopyReadAttribute(FILE *fp, bool *isnull, char *delim, int *newline, char *null_
{
c = CopyGetChar(fp);
if (c == EOF)
- goto endOfFile;
+ {
+ *result = END_OF_FILE;
+ return NULL;
+ }
switch (c)
{
case '0':
@@ -1274,14 +1284,20 @@ CopyReadAttribute(FILE *fp, bool *isnull, char *delim, int *newline, char *null_
else
{
if (c == EOF)
- goto endOfFile;
+ {
+ *result = END_OF_FILE;
+ return NULL;
+ }
CopyDonePeek(fp, c, false /* put back */ );
}
}
else
{
if (c == EOF)
- goto endOfFile;
+ {
+ *result = END_OF_FILE;
+ return NULL;
+ }
CopyDonePeek(fp, c, false /* put back */ );
}
c = val & 0377;
@@ -1319,7 +1335,8 @@ CopyReadAttribute(FILE *fp, bool *isnull, char *delim, int *newline, char *null_
c = CopyGetChar(fp);
if (c != '\n')
elog(ERROR, "CopyReadAttribute: end of record marker corrupted");
- goto endOfFile;
+ *result = END_OF_FILE;
+ return NULL;
}
}
appendStringInfoCharMacro(&attribute_buf, c);
@@ -1334,7 +1351,10 @@ CopyReadAttribute(FILE *fp, bool *isnull, char *delim, int *newline, char *null_
{
c = CopyGetChar(fp);
if (c == EOF)
- goto endOfFile;
+ {
+ *result = END_OF_FILE;
+ return NULL;
+ }
appendStringInfoCharMacro(&attribute_buf, c);
}
}
@@ -1357,13 +1377,7 @@ CopyReadAttribute(FILE *fp, bool *isnull, char *delim, int *newline, char *null_
}
#endif
- if (strcmp(attribute_buf.data, null_print) == 0)
- *isnull = true;
-
return attribute_buf.data;
-
-endOfFile:
- return NULL;
}
static void
@@ -1454,49 +1468,50 @@ CopyAttributeOut(FILE *fp, char *server_string, char *delim)
}
/*
- * CopyAssertAttlist: elog(ERROR,...) if the specified attlist
+ * CopyCheckAttlist: elog(ERROR,...) if the specified attlist
* is not valid for the Relation
*/
static void
-CopyAssertAttlist(Relation rel, List* attlist, bool from)
+CopyCheckAttlist(Relation rel, List *attlist)
{
- TupleDesc tupDesc;
- List* cur;
- char* illegalattname = NULL;
- int attr_count;
- const char* to_or_from;
-
- if( attlist == NIL )
- return;
+ TupleDesc tupDesc;
+ int attr_count;
+ List *l;
- to_or_from = (from == true ? "FROM" : "TO");
+ if (attlist == NIL)
+ return;
tupDesc = RelationGetDescr(rel);
- Assert(tupDesc != NULL);
-
- /*
- * make sure there aren't more columns specified than are in the table
- */
- attr_count = tupDesc->natts;
- if( attr_count < length(attlist) )
- elog(ERROR,"More columns specified in COPY %s command than in target relation",to_or_from);
-
- /*
- * make sure no columns are specified that don't exist in the table
- */
- foreach(cur,attlist)
- {
- int found = 0;
- int i = 0;
- for(;i<attr_count;++i)
- {
- if( strcmp(strVal(lfirst(cur)),NameStr(tupDesc->attrs[i]->attname)) == 0)
- ++found;
- }
- if( ! found )
- illegalattname = strVal(lfirst(cur));
- }
- if( illegalattname )
- elog(ERROR,"Attribute referenced in COPY %s command does not exist: \"%s.%s\"",to_or_from,RelationGetRelationName(rel),illegalattname);
+ Assert(tupDesc != NULL);
+
+ /*
+ * make sure there aren't more columns specified than are in the table
+ */
+ attr_count = tupDesc->natts;
+ if (attr_count < length(attlist))
+ elog(ERROR, "COPY: Too many columns specified");
+
+ /*
+ * make sure no columns are specified that don't exist in the table
+ */
+ foreach(l, attlist)
+ {
+ char *colName = strVal(lfirst(l));
+ bool found = false;
+ int i;
+
+ for (i = 0; i < attr_count; i++)
+ {
+ if (namestrcmp(&tupDesc->attrs[i]->attname, colName) == 0)
+ {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found)
+ elog(ERROR, "COPY: Specified column \"%s\" does not exist",
+ colName);
+ }
}
diff --git a/src/test/regress/expected/copy2.out b/src/test/regress/expected/copy2.out
index c84edb9864f..e4f806c8a56 100644
--- a/src/test/regress/expected/copy2.out
+++ b/src/test/regress/expected/copy2.out
@@ -23,10 +23,44 @@ CREATE TRIGGER trg_x_after AFTER INSERT ON x
FOR EACH ROW EXECUTE PROCEDURE fn_x_after();
CREATE TRIGGER trg_x_before BEFORE INSERT ON x
FOR EACH ROW EXECUTE PROCEDURE fn_x_before();
-COPY x (a,b,c,d,e) from stdin;
-COPY x (b,d) from stdin;
-COPY x (b,d) from stdin;
-COPY x (a,b,c,d,e) from stdin;
+COPY x (a, b, c, d, e) from stdin;
+COPY x (b, d) from stdin;
+COPY x (b, d) from stdin;
+COPY x (a, b, c, d, e) from stdin;
+-- non-existent column in column list: should fail
+COPY x (xyz) from stdin;
+ERROR: COPY: Specified column "xyz" does not exist
+-- too many columns in column list: should fail
+COPY x (a, b, c, d, e, d, c) from stdin;
+ERROR: COPY: Too many columns specified
+-- missing data: should fail
+COPY x from stdin;
+ERROR: copy: line 1, COPY TEXT: Missing data for attribute 1
+lost synchronization with server, resetting connection
+COPY x from stdin;
+ERROR: copy: line 1, COPY TEXT: Missing data for attribute 4
+lost synchronization with server, resetting connection
+COPY x from stdin;
+ERROR: copy: line 1, COPY TEXT: Missing data for attribute 4
+lost synchronization with server, resetting connection
+-- extra data: should fail
+COPY x from stdin;
+ERROR: copy: line 1, COPY TEXT: Extra data encountered
+lost synchronization with server, resetting connection
+-- various COPY options: delimiters, oids, NULL string
+COPY x (b, c, d, e) from stdin with oids delimiter ',' null 'x';
+-- COPY w/ oids on a table w/o oids should fail
+CREATE TABLE no_oids (
+ a int,
+ b int
+) WITHOUT OIDS;
+INSERT INTO no_oids (a, b) VALUES (5, 10);
+INSERT INTO no_oids (a, b) VALUES (20, 30);
+-- should fail
+COPY no_oids FROM stdin WITH OIDS;
+ERROR: COPY: table "no_oids" does not have OIDs
+COPY no_oids TO stdout WITH OIDS;
+ERROR: COPY: table "no_oids" does not have OIDs
COPY x TO stdout;
10000 21 31 41 before trigger fired
10001 22 32 42 before trigger fired
@@ -34,11 +68,25 @@ COPY x TO stdout;
10003 24 34 44 before trigger fired
10004 25 35 45 before trigger fired
10005 26 36 46 before trigger fired
+6 \N 45 80 before trigger fired
1 1 stuff test_1 after trigger fired
2 2 stuff test_2 after trigger fired
3 3 stuff test_3 after trigger fired
4 4 stuff test_4 after trigger fired
5 5 stuff test_5 after trigger fired
+COPY x (c, e) TO stdout;
+31 before trigger fired
+32 before trigger fired
+33 before trigger fired
+34 before trigger fired
+35 before trigger fired
+36 before trigger fired
+45 before trigger fired
+stuff after trigger fired
+stuff after trigger fired
+stuff after trigger fired
+stuff after trigger fired
+stuff after trigger fired
DROP TABLE x;
DROP FUNCTION fn_x_before();
DROP FUNCTION fn_x_after();
diff --git a/src/test/regress/sql/copy2.sql b/src/test/regress/sql/copy2.sql
index 52d0bee4ed2..b0ccfe67160 100644
--- a/src/test/regress/sql/copy2.sql
+++ b/src/test/regress/sql/copy2.sql
@@ -26,22 +26,22 @@ FOR EACH ROW EXECUTE PROCEDURE fn_x_after();
CREATE TRIGGER trg_x_before BEFORE INSERT ON x
FOR EACH ROW EXECUTE PROCEDURE fn_x_before();
-COPY x (a,b,c,d,e) from stdin;
+COPY x (a, b, c, d, e) from stdin;
10000 21 31 41 51
\.
-COPY x (b,d) from stdin;
+COPY x (b, d) from stdin;
1 test_1
\.
-COPY x (b,d) from stdin;
+COPY x (b, d) from stdin;
2 test_2
3 test_3
4 test_4
5 test_5
\.
-COPY x (a,b,c,d,e) from stdin;
+COPY x (a, b, c, d, e) from stdin;
10001 22 32 42 52
10002 23 33 43 53
10003 24 34 44 54
@@ -49,7 +49,48 @@ COPY x (a,b,c,d,e) from stdin;
10005 26 36 46 56
\.
+-- non-existent column in column list: should fail
+COPY x (xyz) from stdin;
+
+-- too many columns in column list: should fail
+COPY x (a, b, c, d, e, d, c) from stdin;
+
+-- missing data: should fail
+COPY x from stdin;
+
+\.
+COPY x from stdin;
+2000 230 23 23
+\.
+COPY x from stdin;
+2001 231 \N \N
+\.
+
+-- extra data: should fail
+COPY x from stdin;
+2002 232 40 50 60 70 80
+\.
+
+-- various COPY options: delimiters, oids, NULL string
+COPY x (b, c, d, e) from stdin with oids delimiter ',' null 'x';
+500000,x,45,80,90
+\.
+
+-- COPY w/ oids on a table w/o oids should fail
+CREATE TABLE no_oids (
+ a int,
+ b int
+) WITHOUT OIDS;
+
+INSERT INTO no_oids (a, b) VALUES (5, 10);
+INSERT INTO no_oids (a, b) VALUES (20, 30);
+
+-- should fail
+COPY no_oids FROM stdin WITH OIDS;
+COPY no_oids TO stdout WITH OIDS;
+
COPY x TO stdout;
+COPY x (c, e) TO stdout;
DROP TABLE x;
DROP FUNCTION fn_x_before();
DROP FUNCTION fn_x_after();