IMPROVED VERSION APPLIED:

The attached patch completes the following TODO item:

    * Generate failure on short COPY lines rather than pad NULLs

I also restructed a lot of the existing COPY code, did some code
review on the column list patch sent in by Brent Verner a little
while ago, and added some regression tests. I also added an
explicit check (and resultant error) for extra data before
the end-of-line.

Neil Conway
This commit is contained in:
Bruce Momjian 2002-07-30 16:55:06 +00:00
parent 1dedbf2da5
commit 874148fe34
4 changed files with 318 additions and 209 deletions

View File

@ -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 PostgreSQL documentation
--> -->
@ -199,12 +199,12 @@ ERROR: <replaceable>reason</replaceable>
whatever is in the table already). whatever is in the table already).
</para> </para>
<para> <para>
When using the optional column list syntax, <command>COPY TO</command> If a list of columns is specified, <command>COPY</command> will
and <command>COPY FROM</command> will only copy the specified only copy the data in the specified columns to or from the table.
columns' values to/from the table. If a column in the table If there are any columns in the table that are not in the table,
is not in the column list, <command>COPY FROM</command> will insert <command>COPY FROM</command> will insert the default value for
default values for that column if a default value is defined. that column.
</para> </para>
<para> <para>
@ -266,8 +266,8 @@ ERROR: <replaceable>reason</replaceable>
</para> </para>
<para> <para>
<command>COPY FROM</command> neither invokes rules nor acts on column <command>COPY FROM</command> will invoke any triggers or check
defaults. It does invoke triggers and check constraints. constraints. However, it will not invoke rules.
</para> </para>
<para> <para>
@ -330,7 +330,12 @@ ERROR: <replaceable>reason</replaceable>
The attribute values themselves are strings generated by the The attribute values themselves are strings generated by the
output function, or acceptable to the input function, of each output function, or acceptable to the input function, of each
attribute's data type. The specified null-value string is used in 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>
<para> <para>
If OIDS is specified, the OID is read or written as the first column, If OIDS is specified, the OID is read or written as the first column,

View File

@ -7,7 +7,7 @@
* *
* *
* IDENTIFICATION * 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 ISOCTAL(c) (((c) >= '0') && ((c) <= '7'))
#define OCTVALUE(c) ((c) - '0') #define OCTVALUE(c) ((c) - '0')
typedef enum CopyReadResult
{
NORMAL_ATTR,
END_OF_LINE,
END_OF_FILE
} CopyReadResult;
/* non-export function prototypes */ /* non-export function prototypes */
static void CopyTo(Relation rel, List *attlist, bool binary, bool oids, FILE *fp, char *delim, char *null_print); 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 void CopyFrom(Relation rel, List *attlist, bool binary, bool oids, FILE *fp, char *delim, char *null_print);
static Oid GetInputFunction(Oid type); static Oid GetInputFunction(Oid type);
static Oid GetTypeElement(Oid type); static Oid GetTypeElement(Oid type);
static void CopyReadNewline(FILE *fp, int *newline); static char *CopyReadAttribute(FILE *fp, const char *delim, CopyReadResult *result);
static char *CopyReadAttribute(FILE *fp, bool *isnull, char *delim, int *newline, char *null_print);
static void CopyAttributeOut(FILE *fp, char *string, char *delim); 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"; static const char BinarySignature[12] = "PGBCOPY\n\377\r\n\0";
@ -271,14 +276,10 @@ DoCopy(const CopyStmt *stmt)
bool pipe = (stmt->filename == NULL); bool pipe = (stmt->filename == NULL);
List *option; List *option;
List *attlist = stmt->attlist; List *attlist = stmt->attlist;
DefElem *dbinary = NULL;
DefElem *doids = NULL;
DefElem *ddelim = NULL;
DefElem *dnull = NULL;
bool binary = false; bool binary = false;
bool oids = false; bool oids = false;
char *delim = "\t"; char *delim = NULL;
char *null_print = "\\N"; char *null_print = NULL;
FILE *fp; FILE *fp;
Relation rel; Relation rel;
AclMode required_access = (is_from ? ACL_INSERT : ACL_SELECT); AclMode required_access = (is_from ? ACL_INSERT : ACL_SELECT);
@ -289,51 +290,53 @@ DoCopy(const CopyStmt *stmt)
{ {
DefElem *defel = (DefElem *) lfirst(option); DefElem *defel = (DefElem *) lfirst(option);
/* XXX: Should we bother checking for doubled options? */
if (strcmp(defel->defname, "binary") == 0) if (strcmp(defel->defname, "binary") == 0)
{ {
if (dbinary) if (binary)
/* should this really be an error? */
elog(ERROR, "COPY: BINARY option appears more than once"); elog(ERROR, "COPY: BINARY option appears more than once");
dbinary = defel;
binary = intVal(defel->arg);
} }
else if (strcmp(defel->defname, "oids") == 0) else if (strcmp(defel->defname, "oids") == 0)
{ {
if (doids) if (oids)
/* should this really be an error? */
elog(ERROR, "COPY: OIDS option appears more than once"); elog(ERROR, "COPY: OIDS option appears more than once");
doids = defel;
oids = intVal(defel->arg);
} }
else if (strcmp(defel->defname, "delimiter") == 0) else if (strcmp(defel->defname, "delimiter") == 0)
{ {
if (ddelim) if (delim)
elog(ERROR, "COPY: DELIMITER string may only be defined once in query"); 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) else if (strcmp(defel->defname, "null") == 0)
{ {
if (dnull) if (null_print)
elog(ERROR, "COPY: NULL representation may only be defined once in query"); elog(ERROR, "COPY: NULL representation may only be defined once in query");
dnull = defel;
null_print = strVal(defel->arg);
} }
else else
elog(ERROR, "COPY: option \"%s\" not recognized", elog(ERROR, "COPY: option \"%s\" not recognized",
defel->defname); defel->defname);
} }
if (dbinary) if (binary && delim)
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)
elog(ERROR, "You can not specify the DELIMITER in BINARY mode."); 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."); 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. * Open and lock the relation, using the appropriate lock type.
@ -363,6 +366,13 @@ DoCopy(const CopyStmt *stmt)
if (strlen(delim) != 1) if (strlen(delim) != 1)
elog(ERROR, "COPY delimiter must be a single character"); 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. * Set up variables to avoid per-attribute overhead.
*/ */
@ -372,22 +382,24 @@ DoCopy(const CopyStmt *stmt)
server_encoding = GetDatabaseEncoding(); server_encoding = GetDatabaseEncoding();
#endif #endif
if( attlist == NIL ){ if (attlist == NIL)
{
/* get list of attributes in the relation */ /* get list of attributes in the relation */
TupleDesc desc = RelationGetDescr(rel); TupleDesc desc = RelationGetDescr(rel);
int i; int i;
for(i = 0; i < desc->natts; ++i){ for (i = 0; i < desc->natts; ++i)
Ident* id = makeNode(Ident); {
Ident *id = makeNode(Ident);
id->name = NameStr(desc->attrs[i]->attname); id->name = NameStr(desc->attrs[i]->attname);
attlist = lappend(attlist,id); attlist = lappend(attlist,id);
} }
} }
else{ else
if( binary ){ {
if (binary)
elog(ERROR,"COPY: BINARY format cannot be used with specific column list"); elog(ERROR,"COPY: BINARY format cannot be used with specific column list");
}
/* verify that any user-specified attributes exist in the relation */ CopyCheckAttlist(rel, attlist);
CopyAssertAttlist(rel,attlist,is_from);
} }
if (is_from) if (is_from)
@ -532,27 +544,28 @@ CopyTo(Relation rel, List *attlist, bool binary, bool oids,
int16 fld_size; int16 fld_size;
char *string; char *string;
Snapshot mySnapshot; Snapshot mySnapshot;
int copy_attr_count; int copy_attr_count;
int* attmap; int *attmap;
int p = 0; int p = 0;
List* cur; List *cur;
if (oids && !rel->rd_rel->relhasoids)
elog(ERROR, "COPY: table %s does not have OIDs",
RelationGetRelationName(rel));
tupDesc = rel->rd_att; tupDesc = rel->rd_att;
attr_count = rel->rd_att->natts; attr_count = rel->rd_att->natts;
attr = rel->rd_att->attrs; attr = rel->rd_att->attrs;
copy_attr_count = length(attlist); copy_attr_count = length(attlist);
attmap = (int *) palloc(copy_attr_count * sizeof(int));
foreach(cur, attlist)
{ {
attmap = (int*)palloc(copy_attr_count * sizeof(int)); const char *currAtt = strVal(lfirst(cur));
foreach(cur,attlist){
for (i = 0; i < attr_count; i++){ for (i = 0; i < attr_count; i++)
if( strcmp(strVal(lfirst(cur)),NameStr(attr[i]->attname)) == 0){ {
attmap[p++] = i; if (namestrcmp(&attr[i]->attname, currAtt) == 0)
continue; {
} attmap[p++] = i;
continue;
} }
} }
} }
@ -642,7 +655,7 @@ CopyTo(Relation rel, List *attlist, bool binary, bool oids,
Datum origvalue, Datum origvalue,
value; value;
bool isnull; bool isnull;
int mi = attmap[i]; int mi = attmap[i];
origvalue = heap_getattr(tuple, mi + 1, tupDesc, &isnull); 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; Oid in_func_oid;
Datum *values; Datum *values;
char *nulls; char *nulls;
bool isnull;
int done = 0; int done = 0;
char *string;
ResultRelInfo *resultRelInfo; ResultRelInfo *resultRelInfo;
EState *estate = CreateExecutorState(); /* for ExecConstraints() */ EState *estate = CreateExecutorState(); /* for ExecConstraints() */
TupleTable tupleTable; TupleTable tupleTable;
TupleTableSlot *slot; TupleTableSlot *slot;
Oid loaded_oid = InvalidOid;
bool skip_tuple = false;
bool file_has_oids; bool file_has_oids;
int* attmap = NULL; int *attmap = NULL;
int* defmap = NULL; int *defmap = NULL;
Node** defexprs = NULL; /* array of default att expressions */ Node **defexprs = NULL; /* array of default att expressions */
ExprContext *econtext; /* used for ExecEvalExpr for default atts */ ExprContext *econtext; /* used for ExecEvalExpr for default atts */
ExprDoneCond isdone; ExprDoneCond isdone;
@ -810,40 +819,47 @@ CopyFrom(Relation rel, List *attlist, bool binary, bool oids,
slot = ExecAllocTableSlot(tupleTable); slot = ExecAllocTableSlot(tupleTable);
ExecSetSlotDescriptor(slot, tupDesc, false); ExecSetSlotDescriptor(slot, tupDesc, false);
if (!binary) if (!binary)
{ {
/* /*
* pick up the input function and default expression (if any) for * pick up the input function and default expression (if any) for
* each attribute in the relation. * each attribute in the relation.
*/ */
List* cur; attmap = (int *) palloc(sizeof(int) * attr_count);
attmap = (int*)palloc(sizeof(int) * attr_count); defmap = (int *) palloc(sizeof(int) * attr_count);
defmap = (int*)palloc(sizeof(int) * attr_count); defexprs = (Node **) palloc(sizeof(Node *) * attr_count);
defexprs = (Node**)palloc(sizeof(Node*) * attr_count);
in_functions = (FmgrInfo *) palloc(attr_count * sizeof(FmgrInfo)); in_functions = (FmgrInfo *) palloc(attr_count * sizeof(FmgrInfo));
elements = (Oid *) palloc(attr_count * sizeof(Oid)); elements = (Oid *) palloc(attr_count * sizeof(Oid));
for (i = 0; i < attr_count; i++) for (i = 0; i < attr_count; i++)
{ {
int p = 0; List *l;
bool specified = false; int p = 0;
bool specified = false;
in_func_oid = (Oid) GetInputFunction(attr[i]->atttypid); in_func_oid = (Oid) GetInputFunction(attr[i]->atttypid);
fmgr_info(in_func_oid, &in_functions[i]); fmgr_info(in_func_oid, &in_functions[i]);
elements[i] = GetTypeElement(attr[i]->atttypid); 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; attmap[p] = i;
specified = true; specified = true;
continue; continue;
} }
++p; p++;
} }
if( ! specified ){
/* column not specified, try to get a default */ /* if column not specified, use default value if one exists */
defexprs[def_attr_count] = build_column_default(rel,i+1); if (! specified)
if( defexprs[def_attr_count] != NULL ){ {
defexprs[def_attr_count] = build_column_default(rel, i + 1);
if (defexprs[def_attr_count] != NULL)
{
defmap[def_attr_count] = i; 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 */ /* Signature */
CopyGetData(readSig, 12, fp); CopyGetData(readSig, 12, fp);
if (CopyGetEof(fp) || if (CopyGetEof(fp) || memcmp(readSig, BinarySignature, 12) != 0)
memcmp(readSig, BinarySignature, 12) != 0)
elog(ERROR, "COPY BINARY: file signature not recognized"); elog(ERROR, "COPY BINARY: file signature not recognized");
/* Integer layout field */ /* Integer layout field */
CopyGetData(&tmp, sizeof(int32), fp); CopyGetData(&tmp, sizeof(int32), fp);
if (CopyGetEof(fp) || if (CopyGetEof(fp) || tmp != 0x01020304)
tmp != 0x01020304)
elog(ERROR, "COPY BINARY: incompatible integer layout"); elog(ERROR, "COPY BINARY: incompatible integer layout");
/* Flags field */ /* Flags field */
CopyGetData(&tmp, sizeof(int32), fp); 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"); elog(ERROR, "COPY BINARY: unrecognized critical flags in header");
/* Header extension length */ /* Header extension length */
CopyGetData(&tmp, sizeof(int32), fp); CopyGetData(&tmp, sizeof(int32), fp);
if (CopyGetEof(fp) || if (CopyGetEof(fp) || tmp < 0)
tmp < 0)
elog(ERROR, "COPY BINARY: bogus file header (missing length)"); elog(ERROR, "COPY BINARY: bogus file header (missing length)");
/* Skip extension header, if present */ /* Skip extension header, if present */
while (tmp-- > 0) while (tmp-- > 0)
@ -890,10 +903,6 @@ CopyFrom(Relation rel, List *attlist, bool binary, bool oids,
elements = NULL; 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)); values = (Datum *) palloc(attr_count * sizeof(Datum));
nulls = (char *) palloc(attr_count * sizeof(char)); nulls = (char *) palloc(attr_count * sizeof(char));
@ -904,6 +913,9 @@ CopyFrom(Relation rel, List *attlist, bool binary, bool oids,
while (!done) while (!done)
{ {
bool skip_tuple;
Oid loaded_oid;
CHECK_FOR_INTERRUPTS(); CHECK_FOR_INTERRUPTS();
copy_lineno++; copy_lineno++;
@ -917,16 +929,17 @@ CopyFrom(Relation rel, List *attlist, bool binary, bool oids,
if (!binary) if (!binary)
{ {
int newline = 0; CopyReadResult result;
char *string;
if (file_has_oids) if (file_has_oids)
{ {
string = CopyReadAttribute(fp, &isnull, delim, string = CopyReadAttribute(fp, delim, &result);
&newline, null_print);
if (isnull) if (result == END_OF_FILE)
done = 1;
else if (string == NULL || strcmp(string, null_print) == 0)
elog(ERROR, "COPY TEXT: NULL Oid"); elog(ERROR, "COPY TEXT: NULL Oid");
else if (string == NULL)
done = 1; /* end of file */
else else
{ {
loaded_oid = DatumGetObjectId(DirectFunctionCall1(oidin, 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++) for (i = 0; i < copy_attr_count && !done; i++)
{ {
int m = attmap[i]; int m = attmap[i];
string = CopyReadAttribute(fp, &isnull, delim,
&newline, null_print);
if( isnull ){ string = CopyReadAttribute(fp, delim, &result);
/* nothing */
/* 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 else
{ {
values[m] = FunctionCall3(&in_functions[m], values[m] = FunctionCall3(&in_functions[m],
CStringGetDatum(string), CStringGetDatum(string),
ObjectIdGetDatum(elements[m]), ObjectIdGetDatum(elements[m]),
Int32GetDatum(attr[m]->atttypmod)); Int32GetDatum(attr[m]->atttypmod));
nulls[m] = ' '; 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 * as above, we only try a default lookup if one is
* known to be available * known to be available
*/ */
for (i = 0; i < def_attr_count && !done; i++){ for (i = 0; i < def_attr_count && !done; i++)
{
bool isnull; bool isnull;
values[defmap[i]] = ExecEvalExpr(defexprs[i],econtext,&isnull,&isdone); values[defmap[i]] = ExecEvalExpr(defexprs[i], econtext,
if( ! isnull ) &isnull, &isdone);
nulls[defmap[i]] = ' ';
if (! isnull)
nulls[defmap[i]] = ' ';
} }
if (!done)
CopyReadNewline(fp, &newline);
} }
else else
{ /* binary */ { /* binary */
@ -980,8 +1002,7 @@ CopyFrom(Relation rel, List *attlist, bool binary, bool oids,
fld_size; fld_size;
CopyGetData(&fld_count, sizeof(int16), fp); CopyGetData(&fld_count, sizeof(int16), fp);
if (CopyGetEof(fp) || if (CopyGetEof(fp) || fld_count == -1)
fld_count == -1)
done = 1; done = 1;
else else
{ {
@ -1173,36 +1194,25 @@ GetTypeElement(Oid type)
return result; 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. * Read the value of a single attribute.
* *
* Result is either a string, or NULL (if EOF or a null attribute). * Results are returned in the status indicator, as well as the
* Note that the caller should not pfree the string! * 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.
* *
* *isnull is set true if a null attribute, else false. * Note: This function does not care about SQL NULL values -- it
* delim is the column delimiter string (currently always 1 character). * is the caller's responsibility to check if the returned string
* *newline remembers whether we've seen a newline ending this tuple. * matches what the user specified for the SQL NULL value.
* null_print says how NULL values are represented *
* delim is the column delimiter string.
*/ */
static char * 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 c;
int delimc = (unsigned char)delim[0]; 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.len = 0;
attribute_buf.data[0] = '\0'; attribute_buf.data[0] = '\0';
/* if last delimiter was a newline return a NULL attribute */ /* set default */
if (*newline) *result = NORMAL_ATTR;
{
*isnull = (bool) true;
return NULL;
}
*isnull = (bool) false; /* set default */
for (;;) for (;;)
{ {
c = CopyGetChar(fp); c = CopyGetChar(fp);
if (c == EOF) if (c == EOF)
goto endOfFile; {
*result = END_OF_FILE;
return NULL;
}
if (c == '\n') if (c == '\n')
{ {
*newline = 1; *result = END_OF_LINE;
break; break;
} }
if (c == delimc) if (c == delimc)
@ -1245,7 +1252,10 @@ CopyReadAttribute(FILE *fp, bool *isnull, char *delim, int *newline, char *null_
{ {
c = CopyGetChar(fp); c = CopyGetChar(fp);
if (c == EOF) if (c == EOF)
goto endOfFile; {
*result = END_OF_FILE;
return NULL;
}
switch (c) switch (c)
{ {
case '0': case '0':
@ -1274,14 +1284,20 @@ CopyReadAttribute(FILE *fp, bool *isnull, char *delim, int *newline, char *null_
else else
{ {
if (c == EOF) if (c == EOF)
goto endOfFile; {
*result = END_OF_FILE;
return NULL;
}
CopyDonePeek(fp, c, false /* put back */ ); CopyDonePeek(fp, c, false /* put back */ );
} }
} }
else else
{ {
if (c == EOF) if (c == EOF)
goto endOfFile; {
*result = END_OF_FILE;
return NULL;
}
CopyDonePeek(fp, c, false /* put back */ ); CopyDonePeek(fp, c, false /* put back */ );
} }
c = val & 0377; c = val & 0377;
@ -1319,7 +1335,8 @@ CopyReadAttribute(FILE *fp, bool *isnull, char *delim, int *newline, char *null_
c = CopyGetChar(fp); c = CopyGetChar(fp);
if (c != '\n') if (c != '\n')
elog(ERROR, "CopyReadAttribute: end of record marker corrupted"); elog(ERROR, "CopyReadAttribute: end of record marker corrupted");
goto endOfFile; *result = END_OF_FILE;
return NULL;
} }
} }
appendStringInfoCharMacro(&attribute_buf, c); appendStringInfoCharMacro(&attribute_buf, c);
@ -1334,7 +1351,10 @@ CopyReadAttribute(FILE *fp, bool *isnull, char *delim, int *newline, char *null_
{ {
c = CopyGetChar(fp); c = CopyGetChar(fp);
if (c == EOF) if (c == EOF)
goto endOfFile; {
*result = END_OF_FILE;
return NULL;
}
appendStringInfoCharMacro(&attribute_buf, c); appendStringInfoCharMacro(&attribute_buf, c);
} }
} }
@ -1357,13 +1377,7 @@ CopyReadAttribute(FILE *fp, bool *isnull, char *delim, int *newline, char *null_
} }
#endif #endif
if (strcmp(attribute_buf.data, null_print) == 0)
*isnull = true;
return attribute_buf.data; return attribute_buf.data;
endOfFile:
return NULL;
} }
static void 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 * is not valid for the Relation
*/ */
static void static void
CopyAssertAttlist(Relation rel, List* attlist, bool from) CopyCheckAttlist(Relation rel, List *attlist)
{ {
TupleDesc tupDesc; TupleDesc tupDesc;
List* cur; int attr_count;
char* illegalattname = NULL; List *l;
int attr_count;
const char* to_or_from; if (attlist == NIL)
if( attlist == NIL )
return; return;
to_or_from = (from == true ? "FROM" : "TO");
tupDesc = RelationGetDescr(rel); tupDesc = RelationGetDescr(rel);
Assert(tupDesc != NULL); Assert(tupDesc != NULL);
/* /*
* make sure there aren't more columns specified than are in the table * make sure there aren't more columns specified than are in the table
*/ */
attr_count = tupDesc->natts; attr_count = tupDesc->natts;
if( attr_count < length(attlist) ) if (attr_count < length(attlist))
elog(ERROR,"More columns specified in COPY %s command than in target relation",to_or_from); elog(ERROR, "COPY: Too many columns specified");
/* /*
* make sure no columns are specified that don't exist in the table * make sure no columns are specified that don't exist in the table
*/ */
foreach(cur,attlist) foreach(l, attlist)
{ {
int found = 0; char *colName = strVal(lfirst(l));
int i = 0; bool found = false;
for(;i<attr_count;++i) int i;
{
if( strcmp(strVal(lfirst(cur)),NameStr(tupDesc->attrs[i]->attname)) == 0) for (i = 0; i < attr_count; i++)
++found; {
} if (namestrcmp(&tupDesc->attrs[i]->attname, colName) == 0)
if( ! found ) {
illegalattname = strVal(lfirst(cur)); found = true;
} break;
if( illegalattname ) }
elog(ERROR,"Attribute referenced in COPY %s command does not exist: \"%s.%s\"",to_or_from,RelationGetRelationName(rel),illegalattname); }
if (!found)
elog(ERROR, "COPY: Specified column \"%s\" does not exist",
colName);
}
} }

View File

@ -23,10 +23,44 @@ CREATE TRIGGER trg_x_after AFTER INSERT ON x
FOR EACH ROW EXECUTE PROCEDURE fn_x_after(); FOR EACH ROW EXECUTE PROCEDURE fn_x_after();
CREATE TRIGGER trg_x_before BEFORE INSERT ON x CREATE TRIGGER trg_x_before BEFORE INSERT ON x
FOR EACH ROW EXECUTE PROCEDURE fn_x_before(); 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;
COPY x (b,d) from stdin; COPY x (b, d) 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;
-- 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; COPY x TO stdout;
10000 21 31 41 before trigger fired 10000 21 31 41 before trigger fired
10001 22 32 42 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 10003 24 34 44 before trigger fired
10004 25 35 45 before trigger fired 10004 25 35 45 before trigger fired
10005 26 36 46 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 1 1 stuff test_1 after trigger fired
2 2 stuff test_2 after trigger fired 2 2 stuff test_2 after trigger fired
3 3 stuff test_3 after trigger fired 3 3 stuff test_3 after trigger fired
4 4 stuff test_4 after trigger fired 4 4 stuff test_4 after trigger fired
5 5 stuff test_5 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 TABLE x;
DROP FUNCTION fn_x_before(); DROP FUNCTION fn_x_before();
DROP FUNCTION fn_x_after(); DROP FUNCTION fn_x_after();

View File

@ -26,22 +26,22 @@ FOR EACH ROW EXECUTE PROCEDURE fn_x_after();
CREATE TRIGGER trg_x_before BEFORE INSERT ON x CREATE TRIGGER trg_x_before BEFORE INSERT ON x
FOR EACH ROW EXECUTE PROCEDURE fn_x_before(); 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 10000 21 31 41 51
\. \.
COPY x (b,d) from stdin; COPY x (b, d) from stdin;
1 test_1 1 test_1
\. \.
COPY x (b,d) from stdin; COPY x (b, d) from stdin;
2 test_2 2 test_2
3 test_3 3 test_3
4 test_4 4 test_4
5 test_5 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 10001 22 32 42 52
10002 23 33 43 53 10002 23 33 43 53
10003 24 34 44 54 10003 24 34 44 54
@ -49,7 +49,48 @@ COPY x (a,b,c,d,e) from stdin;
10005 26 36 46 56 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 TO stdout;
COPY x (c, e) TO stdout;
DROP TABLE x; DROP TABLE x;
DROP FUNCTION fn_x_before(); DROP FUNCTION fn_x_before();
DROP FUNCTION fn_x_after(); DROP FUNCTION fn_x_after();