mirror of
https://github.com/postgres/postgres.git
synced 2025-11-12 05:01:15 +03:00
varchar length. Cleans up code so attlen is always length. Removed varchar() hack added earlier. Will fix bug in selecting varchar() fields, and varchar() can be variable length.
1190 lines
26 KiB
C
1190 lines
26 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* copy.c--
|
|
*
|
|
* Copyright (c) 1994, Regents of the University of California
|
|
*
|
|
*
|
|
* IDENTIFICATION
|
|
* $Header: /cvsroot/pgsql/src/backend/commands/copy.c,v 1.39 1998/01/16 23:19:40 momjian Exp $
|
|
*
|
|
*-------------------------------------------------------------------------
|
|
*/
|
|
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
#include <postgres.h>
|
|
|
|
#include <access/heapam.h>
|
|
#include <tcop/dest.h>
|
|
#include <fmgr.h>
|
|
#include <miscadmin.h>
|
|
#include <utils/builtins.h>
|
|
#include <utils/acl.h>
|
|
#include <sys/stat.h>
|
|
#include <catalog/pg_index.h>
|
|
#include <utils/syscache.h>
|
|
#include <utils/memutils.h>
|
|
#include <executor/executor.h>
|
|
#include <access/transam.h>
|
|
#include <catalog/index.h>
|
|
#include <access/genam.h>
|
|
#include <catalog/pg_type.h>
|
|
#include <catalog/catname.h>
|
|
#include <catalog/pg_user.h>
|
|
#include <commands/copy.h>
|
|
#include "commands/trigger.h"
|
|
#include <storage/fd.h>
|
|
|
|
#define ISOCTAL(c) (((c) >= '0') && ((c) <= '7'))
|
|
#define VALUE(c) ((c) - '0')
|
|
|
|
|
|
/* non-export function prototypes */
|
|
static void CopyTo(Relation rel, bool binary, bool oids, FILE *fp, char *delim);
|
|
static void CopyFrom(Relation rel, bool binary, bool oids, FILE *fp, char *delim);
|
|
static Oid GetOutputFunction(Oid type);
|
|
static Oid GetTypeElement(Oid type);
|
|
static Oid GetInputFunction(Oid type);
|
|
static Oid IsTypeByVal(Oid type);
|
|
static void
|
|
GetIndexRelations(Oid main_relation_oid,
|
|
int *n_indices,
|
|
Relation **index_rels);
|
|
|
|
#ifdef COPY_PATCH
|
|
static void CopyReadNewline(FILE *fp, int *newline);
|
|
static char *CopyReadAttribute(FILE *fp, bool *isnull, char *delim, int *newline);
|
|
|
|
#else
|
|
static char *CopyReadAttribute(FILE *fp, bool *isnull, char *delim);
|
|
|
|
#endif
|
|
static void CopyAttributeOut(FILE *fp, char *string, char *delim);
|
|
static int CountTuples(Relation relation);
|
|
|
|
extern FILE *Pfout,
|
|
*Pfin;
|
|
|
|
static int lineno;
|
|
|
|
/*
|
|
* DoCopy executes a the SQL COPY statement.
|
|
*/
|
|
|
|
void
|
|
DoCopy(char *relname, bool binary, bool oids, bool from, bool pipe,
|
|
char *filename, char *delim)
|
|
{
|
|
/*----------------------------------------------------------------------------
|
|
Either unload or reload contents of class <relname>, depending on <from>.
|
|
|
|
If <pipe> is false, transfer is between the class and the file named
|
|
<filename>. Otherwise, transfer is between the class and our regular
|
|
input/output stream. The latter could be either stdin/stdout or a
|
|
socket, depending on whether we're running under Postmaster control.
|
|
|
|
Iff <binary>, unload or reload in the binary format, as opposed to the
|
|
more wasteful but more robust and portable text format.
|
|
|
|
If in the text format, delimit columns with delimiter <delim>.
|
|
|
|
When loading in the text format from an input stream (as opposed to
|
|
a file), recognize a "." on a line by itself as EOF. Also recognize
|
|
a stream EOF. When unloading in the text format to an output stream,
|
|
write a "." on a line by itself at the end of the data.
|
|
|
|
Iff <oids>, unload or reload the format that includes OID information.
|
|
|
|
Do not allow a Postgres user without superuser privilege to read from
|
|
or write to a file.
|
|
|
|
Do not allow the copy if user doesn't have proper permission to access
|
|
the class.
|
|
----------------------------------------------------------------------------*/
|
|
|
|
FILE *fp;
|
|
Relation rel;
|
|
extern char *UserName; /* defined in global.c */
|
|
const AclMode required_access = from ? ACL_WR : ACL_RD;
|
|
int result;
|
|
|
|
rel = heap_openr(relname);
|
|
if (rel == NULL)
|
|
elog(ERROR, "COPY command failed. Class %s "
|
|
"does not exist.", relname);
|
|
|
|
result = pg_aclcheck(relname, UserName, required_access);
|
|
if (result != ACLCHECK_OK)
|
|
elog(ERROR, "%s: %s", relname, aclcheck_error_strings[result]);
|
|
/* Above should not return */
|
|
else if (!superuser() && !pipe)
|
|
elog(ERROR, "You must have Postgres superuser privilege to do a COPY "
|
|
"directly to or from a file. Anyone can COPY to stdout or "
|
|
"from stdin. Psql's \\copy command also works for anyone.");
|
|
/* Above should not return. */
|
|
else
|
|
{
|
|
if (from)
|
|
{ /* copy from file to database */
|
|
if (rel->rd_rel->relkind == RELKIND_SEQUENCE)
|
|
elog(ERROR, "You can't change sequence relation %s", relname);
|
|
if (pipe)
|
|
{
|
|
if (IsUnderPostmaster)
|
|
{
|
|
ReceiveCopyBegin();
|
|
fp = Pfin;
|
|
}
|
|
else
|
|
fp = stdin;
|
|
}
|
|
else
|
|
{
|
|
fp = AllocateFile(filename, "r");
|
|
if (fp == NULL)
|
|
elog(ERROR, "COPY command, running in backend with "
|
|
"effective uid %d, could not open file '%s' for "
|
|
"reading. Errno = %s (%d).",
|
|
geteuid(), filename, strerror(errno), errno);
|
|
/* Above should not return */
|
|
}
|
|
CopyFrom(rel, binary, oids, fp, delim);
|
|
}
|
|
else
|
|
{ /* copy from database to file */
|
|
if (pipe)
|
|
{
|
|
if (IsUnderPostmaster)
|
|
{
|
|
SendCopyBegin();
|
|
fp = Pfout;
|
|
}
|
|
else
|
|
fp = stdout;
|
|
}
|
|
else
|
|
{
|
|
mode_t oumask; /* Pre-existing umask value */
|
|
|
|
oumask = umask((mode_t) 0);
|
|
fp = AllocateFile(filename, "w");
|
|
umask(oumask);
|
|
if (fp == NULL)
|
|
elog(ERROR, "COPY command, running in backend with "
|
|
"effective uid %d, could not open file '%s' for "
|
|
"writing. Errno = %s (%d).",
|
|
geteuid(), filename, strerror(errno), errno);
|
|
/* Above should not return */
|
|
}
|
|
CopyTo(rel, binary, oids, fp, delim);
|
|
}
|
|
if (!pipe)
|
|
FreeFile(fp);
|
|
else if (!from && !binary)
|
|
{
|
|
fputs("\\.\n", fp);
|
|
if (IsUnderPostmaster)
|
|
fflush(Pfout);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
CopyTo(Relation rel, bool binary, bool oids, FILE *fp, char *delim)
|
|
{
|
|
HeapTuple tuple;
|
|
HeapScanDesc scandesc;
|
|
|
|
int32 attr_count,
|
|
i;
|
|
AttributeTupleForm *attr;
|
|
FmgrInfo *out_functions;
|
|
Oid out_func_oid;
|
|
Oid *elements;
|
|
Datum value;
|
|
bool isnull; /* The attribute we are copying is null */
|
|
char *nulls;
|
|
|
|
/*
|
|
* <nulls> is a (dynamically allocated) array with one character per
|
|
* attribute in the instance being copied. nulls[I-1] is 'n' if
|
|
* Attribute Number I is null, and ' ' otherwise.
|
|
*
|
|
* <nulls> is meaningful only if we are doing a binary copy.
|
|
*/
|
|
char *string;
|
|
int32 ntuples;
|
|
TupleDesc tupDesc;
|
|
|
|
scandesc = heap_beginscan(rel, 0, false, 0, NULL);
|
|
|
|
attr_count = rel->rd_att->natts;
|
|
attr = rel->rd_att->attrs;
|
|
tupDesc = rel->rd_att;
|
|
|
|
if (!binary)
|
|
{
|
|
out_functions = (FmgrInfo *) palloc(attr_count * sizeof(FmgrInfo));
|
|
elements = (Oid *) palloc(attr_count * sizeof(Oid));
|
|
for (i = 0; i < attr_count; i++)
|
|
{
|
|
out_func_oid = (Oid) GetOutputFunction(attr[i]->atttypid);
|
|
fmgr_info(out_func_oid, &out_functions[i]);
|
|
elements[i] = GetTypeElement(attr[i]->atttypid);
|
|
}
|
|
nulls = NULL; /* meaningless, but compiler doesn't know
|
|
* that */
|
|
}
|
|
else
|
|
{
|
|
elements = NULL;
|
|
out_functions = NULL;
|
|
nulls = (char *) palloc(attr_count);
|
|
for (i = 0; i < attr_count; i++)
|
|
nulls[i] = ' ';
|
|
|
|
/* XXX expensive */
|
|
|
|
ntuples = CountTuples(rel);
|
|
fwrite(&ntuples, sizeof(int32), 1, fp);
|
|
}
|
|
|
|
for (tuple = heap_getnext(scandesc, 0, NULL);
|
|
tuple != NULL;
|
|
tuple = heap_getnext(scandesc, 0, NULL))
|
|
{
|
|
|
|
if (oids && !binary)
|
|
{
|
|
fputs(oidout(tuple->t_oid), fp);
|
|
fputc(delim[0], fp);
|
|
}
|
|
|
|
for (i = 0; i < attr_count; i++)
|
|
{
|
|
value = heap_getattr(tuple, InvalidBuffer, i + 1, tupDesc, &isnull);
|
|
if (!binary)
|
|
{
|
|
if (!isnull)
|
|
{
|
|
string = (char *) (*fmgr_faddr(&out_functions[i])) (value, elements[i]);
|
|
CopyAttributeOut(fp, string, delim);
|
|
pfree(string);
|
|
}
|
|
else
|
|
fputs("\\N", fp); /* null indicator */
|
|
|
|
if (i == attr_count - 1)
|
|
{
|
|
fputc('\n', fp);
|
|
}
|
|
else
|
|
{
|
|
|
|
/*
|
|
* when copying out, only use the first char of the
|
|
* delim string
|
|
*/
|
|
fputc(delim[0], fp);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
|
|
/*
|
|
* only interesting thing heap_getattr tells us in this
|
|
* case is if we have a null attribute or not.
|
|
*/
|
|
if (isnull)
|
|
nulls[i] = 'n';
|
|
}
|
|
}
|
|
|
|
if (binary)
|
|
{
|
|
int32 null_ct = 0,
|
|
length;
|
|
|
|
for (i = 0; i < attr_count; i++)
|
|
{
|
|
if (nulls[i] == 'n')
|
|
null_ct++;
|
|
}
|
|
|
|
length = tuple->t_len - tuple->t_hoff;
|
|
fwrite(&length, sizeof(int32), 1, fp);
|
|
if (oids)
|
|
fwrite((char *) &tuple->t_oid, sizeof(int32), 1, fp);
|
|
|
|
fwrite(&null_ct, sizeof(int32), 1, fp);
|
|
if (null_ct > 0)
|
|
{
|
|
for (i = 0; i < attr_count; i++)
|
|
{
|
|
if (nulls[i] == 'n')
|
|
{
|
|
fwrite(&i, sizeof(int32), 1, fp);
|
|
nulls[i] = ' ';
|
|
}
|
|
}
|
|
}
|
|
fwrite((char *) tuple + tuple->t_hoff, length, 1, fp);
|
|
}
|
|
}
|
|
|
|
heap_endscan(scandesc);
|
|
if (binary)
|
|
{
|
|
pfree(nulls);
|
|
}
|
|
else
|
|
{
|
|
pfree(out_functions);
|
|
pfree(elements);
|
|
}
|
|
|
|
heap_close(rel);
|
|
}
|
|
|
|
static void
|
|
CopyFrom(Relation rel, bool binary, bool oids, FILE *fp, char *delim)
|
|
{
|
|
HeapTuple tuple;
|
|
AttrNumber attr_count;
|
|
AttributeTupleForm *attr;
|
|
FmgrInfo *in_functions;
|
|
int i;
|
|
Oid in_func_oid;
|
|
Datum *values;
|
|
char *nulls,
|
|
*index_nulls;
|
|
bool *byval;
|
|
bool isnull;
|
|
bool has_index;
|
|
int done = 0;
|
|
char *string = NULL,
|
|
*ptr;
|
|
Relation *index_rels;
|
|
int32 len,
|
|
null_ct,
|
|
null_id;
|
|
int32 ntuples,
|
|
tuples_read = 0;
|
|
bool reading_to_eof = true;
|
|
Oid *elements;
|
|
FuncIndexInfo *finfo,
|
|
**finfoP = NULL;
|
|
TupleDesc *itupdescArr;
|
|
HeapTuple pgIndexTup;
|
|
IndexTupleForm *pgIndexP = NULL;
|
|
int *indexNatts = NULL;
|
|
char *predString;
|
|
Node **indexPred = NULL;
|
|
TupleDesc rtupdesc;
|
|
ExprContext *econtext = NULL;
|
|
|
|
#ifndef OMIT_PARTIAL_INDEX
|
|
TupleTable tupleTable;
|
|
TupleTableSlot *slot = NULL;
|
|
|
|
#endif
|
|
int natts;
|
|
AttrNumber *attnumP;
|
|
Datum *idatum;
|
|
int n_indices;
|
|
InsertIndexResult indexRes;
|
|
TupleDesc tupDesc;
|
|
Oid loaded_oid;
|
|
bool skip_tuple = false;
|
|
|
|
tupDesc = RelationGetTupleDescriptor(rel);
|
|
attr = tupDesc->attrs;
|
|
attr_count = tupDesc->natts;
|
|
|
|
has_index = false;
|
|
|
|
/*
|
|
* This may be a scalar or a functional index. We initialize all
|
|
* kinds of arrays here to avoid doing extra work at every tuple copy.
|
|
*/
|
|
|
|
if (rel->rd_rel->relhasindex)
|
|
{
|
|
GetIndexRelations(rel->rd_id, &n_indices, &index_rels);
|
|
if (n_indices > 0)
|
|
{
|
|
has_index = true;
|
|
itupdescArr =
|
|
(TupleDesc *) palloc(n_indices * sizeof(TupleDesc));
|
|
pgIndexP =
|
|
(IndexTupleForm *) palloc(n_indices * sizeof(IndexTupleForm));
|
|
indexNatts = (int *) palloc(n_indices * sizeof(int));
|
|
finfo = (FuncIndexInfo *) palloc(n_indices * sizeof(FuncIndexInfo));
|
|
finfoP = (FuncIndexInfo **) palloc(n_indices * sizeof(FuncIndexInfo *));
|
|
indexPred = (Node **) palloc(n_indices * sizeof(Node *));
|
|
econtext = NULL;
|
|
for (i = 0; i < n_indices; i++)
|
|
{
|
|
itupdescArr[i] = RelationGetTupleDescriptor(index_rels[i]);
|
|
pgIndexTup =
|
|
SearchSysCacheTuple(INDEXRELID,
|
|
ObjectIdGetDatum(index_rels[i]->rd_id),
|
|
0, 0, 0);
|
|
Assert(pgIndexTup);
|
|
pgIndexP[i] = (IndexTupleForm) GETSTRUCT(pgIndexTup);
|
|
for (attnumP = &(pgIndexP[i]->indkey[0]), natts = 0;
|
|
*attnumP != InvalidAttrNumber;
|
|
attnumP++, natts++);
|
|
if (pgIndexP[i]->indproc != InvalidOid)
|
|
{
|
|
FIgetnArgs(&finfo[i]) = natts;
|
|
natts = 1;
|
|
FIgetProcOid(&finfo[i]) = pgIndexP[i]->indproc;
|
|
*(FIgetname(&finfo[i])) = '\0';
|
|
finfoP[i] = &finfo[i];
|
|
}
|
|
else
|
|
finfoP[i] = (FuncIndexInfo *) NULL;
|
|
indexNatts[i] = natts;
|
|
if (VARSIZE(&pgIndexP[i]->indpred) != 0)
|
|
{
|
|
predString = fmgr(F_TEXTOUT, &pgIndexP[i]->indpred);
|
|
indexPred[i] = stringToNode(predString);
|
|
pfree(predString);
|
|
/* make dummy ExprContext for use by ExecQual */
|
|
if (econtext == NULL)
|
|
{
|
|
#ifndef OMIT_PARTIAL_INDEX
|
|
tupleTable = ExecCreateTupleTable(1);
|
|
slot = ExecAllocTableSlot(tupleTable);
|
|
econtext = makeNode(ExprContext);
|
|
econtext->ecxt_scantuple = slot;
|
|
rtupdesc = RelationGetTupleDescriptor(rel);
|
|
slot->ttc_tupleDescriptor = rtupdesc;
|
|
|
|
/*
|
|
* There's no buffer associated with heap tuples
|
|
* here, so I set the slot's buffer to NULL.
|
|
* Currently, it appears that the only way a
|
|
* buffer could be needed would be if the partial
|
|
* index predicate referred to the "lock" system
|
|
* attribute. If it did, then heap_getattr would
|
|
* call HeapTupleGetRuleLock, which uses the
|
|
* buffer's descriptor to get the relation id.
|
|
* Rather than try to fix this, I'll just disallow
|
|
* partial indexes on "lock", which wouldn't be
|
|
* useful anyway. --Nels, Nov '92
|
|
*/
|
|
/* SetSlotBuffer(slot, (Buffer) NULL); */
|
|
/* SetSlotShouldFree(slot, false); */
|
|
slot->ttc_buffer = (Buffer) NULL;
|
|
slot->ttc_shouldFree = false;
|
|
#endif /* OMIT_PARTIAL_INDEX */
|
|
}
|
|
}
|
|
else
|
|
{
|
|
indexPred[i] = NULL;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!binary)
|
|
{
|
|
in_functions = (FmgrInfo *) palloc(attr_count * sizeof(FmgrInfo));
|
|
elements = (Oid *) palloc(attr_count * sizeof(Oid));
|
|
for (i = 0; i < attr_count; i++)
|
|
{
|
|
in_func_oid = (Oid) GetInputFunction(attr[i]->atttypid);
|
|
fmgr_info(in_func_oid, &in_functions[i]);
|
|
elements[i] = GetTypeElement(attr[i]->atttypid);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
in_functions = NULL;
|
|
elements = NULL;
|
|
fread(&ntuples, sizeof(int32), 1, fp);
|
|
if (ntuples != 0)
|
|
reading_to_eof = false;
|
|
}
|
|
|
|
values = (Datum *) palloc(sizeof(Datum) * attr_count);
|
|
nulls = (char *) palloc(attr_count);
|
|
index_nulls = (char *) palloc(attr_count);
|
|
idatum = (Datum *) palloc(sizeof(Datum) * attr_count);
|
|
byval = (bool *) palloc(attr_count * sizeof(bool));
|
|
|
|
for (i = 0; i < attr_count; i++)
|
|
{
|
|
nulls[i] = ' ';
|
|
index_nulls[i] = ' ';
|
|
byval[i] = (bool) IsTypeByVal(attr[i]->atttypid);
|
|
}
|
|
|
|
lineno = 0;
|
|
while (!done)
|
|
{
|
|
if (!binary)
|
|
{
|
|
#ifdef COPY_PATCH
|
|
int newline = 0;
|
|
|
|
#endif
|
|
lineno++;
|
|
if (oids)
|
|
{
|
|
#ifdef COPY_PATCH
|
|
string = CopyReadAttribute(fp, &isnull, delim, &newline);
|
|
#else
|
|
string = CopyReadAttribute(fp, &isnull, delim);
|
|
#endif
|
|
if (string == NULL)
|
|
done = 1;
|
|
else
|
|
{
|
|
loaded_oid = oidin(string);
|
|
if (loaded_oid < BootstrapObjectIdData)
|
|
elog(ERROR, "COPY TEXT: Invalid Oid");
|
|
}
|
|
}
|
|
for (i = 0; i < attr_count && !done; i++)
|
|
{
|
|
#ifdef COPY_PATCH
|
|
string = CopyReadAttribute(fp, &isnull, delim, &newline);
|
|
#else
|
|
string = CopyReadAttribute(fp, &isnull, delim);
|
|
#endif
|
|
if (isnull)
|
|
{
|
|
values[i] = PointerGetDatum(NULL);
|
|
nulls[i] = 'n';
|
|
}
|
|
else if (string == NULL)
|
|
{
|
|
done = 1;
|
|
}
|
|
else
|
|
{
|
|
values[i] =
|
|
(Datum) (*fmgr_faddr(&in_functions[i])) (string,
|
|
elements[i],
|
|
attr[i]->atttypmod);
|
|
|
|
/*
|
|
* Sanity check - by reference attributes cannot
|
|
* return NULL
|
|
*/
|
|
if (!PointerIsValid(values[i]) &&
|
|
!(rel->rd_att->attrs[i]->attbyval))
|
|
{
|
|
elog(ERROR, "copy from line %d: Bad file format",lineno);
|
|
}
|
|
}
|
|
}
|
|
#ifdef COPY_PATCH
|
|
if (!done)
|
|
{
|
|
CopyReadNewline(fp, &newline);
|
|
}
|
|
#endif
|
|
}
|
|
else
|
|
{ /* binary */
|
|
fread(&len, sizeof(int32), 1, fp);
|
|
if (feof(fp))
|
|
{
|
|
done = 1;
|
|
}
|
|
else
|
|
{
|
|
if (oids)
|
|
{
|
|
fread(&loaded_oid, sizeof(int32), 1, fp);
|
|
if (loaded_oid < BootstrapObjectIdData)
|
|
elog(ERROR, "COPY BINARY: Invalid Oid");
|
|
}
|
|
fread(&null_ct, sizeof(int32), 1, fp);
|
|
if (null_ct > 0)
|
|
{
|
|
for (i = 0; i < null_ct; i++)
|
|
{
|
|
fread(&null_id, sizeof(int32), 1, fp);
|
|
nulls[null_id] = 'n';
|
|
}
|
|
}
|
|
|
|
string = (char *) palloc(len);
|
|
fread(string, len, 1, fp);
|
|
|
|
ptr = string;
|
|
|
|
for (i = 0; i < attr_count; i++)
|
|
{
|
|
if (byval[i] && nulls[i] != 'n')
|
|
{
|
|
|
|
switch (attr[i]->attlen)
|
|
{
|
|
case sizeof(char):
|
|
values[i] = (Datum) *(unsigned char *) ptr;
|
|
ptr += sizeof(char);
|
|
break;
|
|
case sizeof(short):
|
|
ptr = (char *) SHORTALIGN(ptr);
|
|
values[i] = (Datum) *(unsigned short *) ptr;
|
|
ptr += sizeof(short);
|
|
break;
|
|
case sizeof(int32):
|
|
ptr = (char *) INTALIGN(ptr);
|
|
values[i] = (Datum) *(uint32 *) ptr;
|
|
ptr += sizeof(int32);
|
|
break;
|
|
default:
|
|
elog(ERROR, "COPY BINARY: impossible size!");
|
|
break;
|
|
}
|
|
}
|
|
else if (nulls[i] != 'n')
|
|
{
|
|
switch (attr[i]->attlen)
|
|
{
|
|
case -1:
|
|
if (attr[i]->attalign == 'd')
|
|
ptr = (char *) DOUBLEALIGN(ptr);
|
|
else
|
|
ptr = (char *) INTALIGN(ptr);
|
|
values[i] = (Datum) ptr;
|
|
ptr += *(uint32 *) ptr;
|
|
break;
|
|
case sizeof(char):
|
|
values[i] = (Datum) ptr;
|
|
ptr += attr[i]->attlen;
|
|
break;
|
|
case sizeof(short):
|
|
ptr = (char *) SHORTALIGN(ptr);
|
|
values[i] = (Datum) ptr;
|
|
ptr += attr[i]->attlen;
|
|
break;
|
|
case sizeof(int32):
|
|
ptr = (char *) INTALIGN(ptr);
|
|
values[i] = (Datum) ptr;
|
|
ptr += attr[i]->attlen;
|
|
break;
|
|
default:
|
|
if (attr[i]->attalign == 'd')
|
|
ptr = (char *) DOUBLEALIGN(ptr);
|
|
else
|
|
ptr = (char *) LONGALIGN(ptr);
|
|
values[i] = (Datum) ptr;
|
|
ptr += attr[i]->attlen;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (done)
|
|
continue;
|
|
|
|
/*
|
|
* Does it have any sence ? - vadim 12/14/96
|
|
*
|
|
* tupDesc = CreateTupleDesc(attr_count, attr);
|
|
*/
|
|
tuple = heap_formtuple(tupDesc, values, nulls);
|
|
if (oids)
|
|
tuple->t_oid = loaded_oid;
|
|
|
|
skip_tuple = false;
|
|
/* BEFORE ROW INSERT Triggers */
|
|
if (rel->trigdesc &&
|
|
rel->trigdesc->n_before_row[TRIGGER_EVENT_INSERT] > 0)
|
|
{
|
|
HeapTuple newtuple;
|
|
|
|
newtuple = ExecBRInsertTriggers(rel, tuple);
|
|
|
|
if (newtuple == NULL) /* "do nothing" */
|
|
skip_tuple = true;
|
|
else if (newtuple != tuple) /* modified by Trigger(s) */
|
|
{
|
|
pfree(tuple);
|
|
tuple = newtuple;
|
|
}
|
|
}
|
|
|
|
if (!skip_tuple)
|
|
{
|
|
/* ----------------
|
|
* Check the constraints of a tuple
|
|
* ----------------
|
|
*/
|
|
|
|
if (rel->rd_att->constr)
|
|
{
|
|
HeapTuple newtuple;
|
|
|
|
newtuple = ExecConstraints("CopyFrom", rel, tuple);
|
|
|
|
if (newtuple != tuple)
|
|
{
|
|
pfree(tuple);
|
|
tuple = newtuple;
|
|
}
|
|
}
|
|
|
|
heap_insert(rel, tuple);
|
|
|
|
if (has_index)
|
|
{
|
|
for (i = 0; i < n_indices; i++)
|
|
{
|
|
if (indexPred[i] != NULL)
|
|
{
|
|
#ifndef OMIT_PARTIAL_INDEX
|
|
|
|
/*
|
|
* if tuple doesn't satisfy predicate, don't
|
|
* update index
|
|
*/
|
|
slot->val = tuple;
|
|
/* SetSlotContents(slot, tuple); */
|
|
if (ExecQual((List *) indexPred[i], econtext) == false)
|
|
continue;
|
|
#endif /* OMIT_PARTIAL_INDEX */
|
|
}
|
|
FormIndexDatum(indexNatts[i],
|
|
(AttrNumber *) &(pgIndexP[i]->indkey[0]),
|
|
tuple,
|
|
tupDesc,
|
|
InvalidBuffer,
|
|
idatum,
|
|
index_nulls,
|
|
finfoP[i]);
|
|
indexRes = index_insert(index_rels[i], idatum, index_nulls,
|
|
&(tuple->t_ctid), rel);
|
|
if (indexRes)
|
|
pfree(indexRes);
|
|
}
|
|
}
|
|
/* AFTER ROW INSERT Triggers */
|
|
if (rel->trigdesc &&
|
|
rel->trigdesc->n_after_row[TRIGGER_EVENT_INSERT] > 0)
|
|
ExecARInsertTriggers(rel, tuple);
|
|
}
|
|
|
|
if (binary)
|
|
pfree(string);
|
|
|
|
for (i = 0; i < attr_count; i++)
|
|
{
|
|
if (!byval[i] && nulls[i] != 'n')
|
|
{
|
|
if (!binary)
|
|
pfree((void *) values[i]);
|
|
}
|
|
else if (nulls[i] == 'n')
|
|
{
|
|
nulls[i] = ' ';
|
|
}
|
|
}
|
|
|
|
pfree(tuple);
|
|
tuples_read++;
|
|
|
|
if (!reading_to_eof && ntuples == tuples_read)
|
|
done = true;
|
|
}
|
|
pfree(values);
|
|
if (!binary)
|
|
pfree(in_functions);
|
|
pfree(nulls);
|
|
pfree(byval);
|
|
heap_close(rel);
|
|
}
|
|
|
|
|
|
|
|
static Oid
|
|
GetOutputFunction(Oid type)
|
|
{
|
|
HeapTuple typeTuple;
|
|
|
|
typeTuple = SearchSysCacheTuple(TYPOID,
|
|
ObjectIdGetDatum(type),
|
|
0, 0, 0);
|
|
|
|
if (HeapTupleIsValid(typeTuple))
|
|
return ((int) ((TypeTupleForm) GETSTRUCT(typeTuple))->typoutput);
|
|
|
|
elog(ERROR, "GetOutputFunction: Cache lookup of type %d failed", type);
|
|
return (InvalidOid);
|
|
}
|
|
|
|
static Oid
|
|
GetTypeElement(Oid type)
|
|
{
|
|
HeapTuple typeTuple;
|
|
|
|
typeTuple = SearchSysCacheTuple(TYPOID,
|
|
ObjectIdGetDatum(type),
|
|
0, 0, 0);
|
|
|
|
|
|
if (HeapTupleIsValid(typeTuple))
|
|
return ((int) ((TypeTupleForm) GETSTRUCT(typeTuple))->typelem);
|
|
|
|
elog(ERROR, "GetOutputFunction: Cache lookup of type %d failed", type);
|
|
return (InvalidOid);
|
|
}
|
|
|
|
static Oid
|
|
GetInputFunction(Oid type)
|
|
{
|
|
HeapTuple typeTuple;
|
|
|
|
typeTuple = SearchSysCacheTuple(TYPOID,
|
|
ObjectIdGetDatum(type),
|
|
0, 0, 0);
|
|
|
|
if (HeapTupleIsValid(typeTuple))
|
|
return ((int) ((TypeTupleForm) GETSTRUCT(typeTuple))->typinput);
|
|
|
|
elog(ERROR, "GetInputFunction: Cache lookup of type %d failed", type);
|
|
return (InvalidOid);
|
|
}
|
|
|
|
static Oid
|
|
IsTypeByVal(Oid type)
|
|
{
|
|
HeapTuple typeTuple;
|
|
|
|
typeTuple = SearchSysCacheTuple(TYPOID,
|
|
ObjectIdGetDatum(type),
|
|
0, 0, 0);
|
|
|
|
if (HeapTupleIsValid(typeTuple))
|
|
return ((int) ((TypeTupleForm) GETSTRUCT(typeTuple))->typbyval);
|
|
|
|
elog(ERROR, "GetInputFunction: Cache lookup of type %d failed", type);
|
|
|
|
return (InvalidOid);
|
|
}
|
|
|
|
/*
|
|
* Given the OID of a relation, return an array of index relation descriptors
|
|
* and the number of index relations. These relation descriptors are open
|
|
* using heap_open().
|
|
*
|
|
* Space for the array itself is palloc'ed.
|
|
*/
|
|
|
|
typedef struct rel_list
|
|
{
|
|
Oid index_rel_oid;
|
|
struct rel_list *next;
|
|
} RelationList;
|
|
|
|
static void
|
|
GetIndexRelations(Oid main_relation_oid,
|
|
int *n_indices,
|
|
Relation **index_rels)
|
|
{
|
|
RelationList *head,
|
|
*scan;
|
|
Relation pg_index_rel;
|
|
HeapScanDesc scandesc;
|
|
Oid index_relation_oid;
|
|
HeapTuple tuple;
|
|
TupleDesc tupDesc;
|
|
int i;
|
|
bool isnull;
|
|
|
|
pg_index_rel = heap_openr(IndexRelationName);
|
|
scandesc = heap_beginscan(pg_index_rel, 0, false, 0, NULL);
|
|
tupDesc = RelationGetTupleDescriptor(pg_index_rel);
|
|
|
|
*n_indices = 0;
|
|
|
|
head = (RelationList *) palloc(sizeof(RelationList));
|
|
scan = head;
|
|
head->next = NULL;
|
|
|
|
for (tuple = heap_getnext(scandesc, 0, NULL);
|
|
tuple != NULL;
|
|
tuple = heap_getnext(scandesc, 0, NULL))
|
|
{
|
|
|
|
index_relation_oid =
|
|
(Oid) DatumGetInt32(heap_getattr(tuple, InvalidBuffer, 2,
|
|
tupDesc, &isnull));
|
|
if (index_relation_oid == main_relation_oid)
|
|
{
|
|
scan->index_rel_oid =
|
|
(Oid) DatumGetInt32(heap_getattr(tuple, InvalidBuffer,
|
|
Anum_pg_index_indexrelid,
|
|
tupDesc, &isnull));
|
|
(*n_indices)++;
|
|
scan->next = (RelationList *) palloc(sizeof(RelationList));
|
|
scan = scan->next;
|
|
}
|
|
}
|
|
|
|
heap_endscan(scandesc);
|
|
heap_close(pg_index_rel);
|
|
|
|
/* We cannot trust to relhasindex of the main_relation now, so... */
|
|
if (*n_indices == 0)
|
|
return;
|
|
|
|
*index_rels = (Relation *) palloc(*n_indices * sizeof(Relation));
|
|
|
|
for (i = 0, scan = head; i < *n_indices; i++, scan = scan->next)
|
|
{
|
|
(*index_rels)[i] = index_open(scan->index_rel_oid);
|
|
}
|
|
|
|
for (i = 0, scan = head; i < *n_indices + 1; i++)
|
|
{
|
|
scan = head->next;
|
|
pfree(head);
|
|
head = scan;
|
|
}
|
|
}
|
|
|
|
#define EXT_ATTLEN 5*8192
|
|
|
|
/*
|
|
returns 1 is c is in s
|
|
*/
|
|
static bool
|
|
inString(char c, char *s)
|
|
{
|
|
int i;
|
|
|
|
if (s)
|
|
{
|
|
i = 0;
|
|
while (s[i] != '\0')
|
|
{
|
|
if (s[i] == c)
|
|
return 1;
|
|
i++;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
#ifdef COPY_PATCH
|
|
/*
|
|
* Reads input from fp until an end of line is seen.
|
|
*/
|
|
|
|
void
|
|
CopyReadNewline(FILE *fp, int *newline)
|
|
{
|
|
if (!*newline)
|
|
{
|
|
elog(NOTICE, "CopyReadNewline: line %d - extra fields ignored", lineno);
|
|
while (!feof(fp) && (getc(fp) != '\n'));
|
|
}
|
|
*newline = 0;
|
|
}
|
|
|
|
#endif
|
|
|
|
/*
|
|
* Reads input from fp until eof is seen. If we are reading from standard
|
|
* input, AND we see a dot on a line by itself (a dot followed immediately
|
|
* by a newline), we exit as if we saw eof. This is so that copy pipelines
|
|
* can be used as standard input.
|
|
*/
|
|
|
|
static char *
|
|
#ifdef COPY_PATCH
|
|
CopyReadAttribute(FILE *fp, bool *isnull, char *delim, int *newline)
|
|
#else
|
|
CopyReadAttribute(FILE *fp, bool *isnull, char *delim)
|
|
#endif
|
|
{
|
|
static char attribute[EXT_ATTLEN];
|
|
char c;
|
|
int done = 0;
|
|
int i = 0;
|
|
|
|
#ifdef COPY_PATCH
|
|
/* if last delimiter was a newline return a NULL attribute */
|
|
if (*newline)
|
|
{
|
|
*isnull = (bool) true;
|
|
return (NULL);
|
|
}
|
|
#endif
|
|
|
|
*isnull = (bool) false; /* set default */
|
|
if (feof(fp))
|
|
return (NULL);
|
|
|
|
while (!done)
|
|
{
|
|
c = getc(fp);
|
|
|
|
if (feof(fp))
|
|
return (NULL);
|
|
else if (c == '\\')
|
|
{
|
|
c = getc(fp);
|
|
if (feof(fp))
|
|
return (NULL);
|
|
switch (c)
|
|
{
|
|
case '0':
|
|
case '1':
|
|
case '2':
|
|
case '3':
|
|
case '4':
|
|
case '5':
|
|
case '6':
|
|
case '7':
|
|
{
|
|
int val;
|
|
|
|
val = VALUE(c);
|
|
c = getc(fp);
|
|
if (ISOCTAL(c))
|
|
{
|
|
val = (val << 3) + VALUE(c);
|
|
c = getc(fp);
|
|
if (ISOCTAL(c))
|
|
{
|
|
val = (val << 3) + VALUE(c);
|
|
}
|
|
else
|
|
{
|
|
if (feof(fp))
|
|
return (NULL);
|
|
ungetc(c, fp);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (feof(fp))
|
|
return (NULL);
|
|
ungetc(c, fp);
|
|
}
|
|
c = val & 0377;
|
|
}
|
|
break;
|
|
case 'b':
|
|
c = '\b';
|
|
break;
|
|
case 'f':
|
|
c = '\f';
|
|
break;
|
|
case 'n':
|
|
c = '\n';
|
|
break;
|
|
case 'r':
|
|
c = '\r';
|
|
break;
|
|
case 't':
|
|
c = '\t';
|
|
break;
|
|
case 'v':
|
|
c = '\v';
|
|
break;
|
|
case 'N':
|
|
attribute[0] = '\0'; /* just to be safe */
|
|
*isnull = (bool) true;
|
|
break;
|
|
case '.':
|
|
c = getc(fp);
|
|
if (c != '\n')
|
|
elog(ERROR, "CopyReadAttribute - end of record marker corrupted");
|
|
return (NULL);
|
|
break;
|
|
}
|
|
}
|
|
else if (inString(c, delim) || c == '\n')
|
|
{
|
|
#ifdef COPY_PATCH
|
|
if (c == '\n')
|
|
{
|
|
*newline = 1;
|
|
}
|
|
#endif
|
|
done = 1;
|
|
}
|
|
if (!done)
|
|
attribute[i++] = c;
|
|
if (i == EXT_ATTLEN - 1)
|
|
elog(ERROR, "CopyReadAttribute - attribute length too long");
|
|
}
|
|
attribute[i] = '\0';
|
|
return (&attribute[0]);
|
|
}
|
|
|
|
static void
|
|
CopyAttributeOut(FILE *fp, char *string, char *delim)
|
|
{
|
|
char c;
|
|
int is_array = false;
|
|
int len = strlen(string);
|
|
|
|
/* XXX - This is a kludge, we should check the data type */
|
|
if (len && (string[0] == '{') && (string[len - 1] == '}'))
|
|
is_array = true;
|
|
|
|
for (; (c = *string) != '\0'; string++)
|
|
{
|
|
if (c == delim[0] || c == '\n' ||
|
|
(c == '\\' && !is_array))
|
|
fputc('\\', fp);
|
|
else if (c == '\\' && is_array)
|
|
if (*(string + 1) == '\\')
|
|
{
|
|
/* translate \\ to \\\\ */
|
|
fputc('\\', fp);
|
|
fputc('\\', fp);
|
|
fputc('\\', fp);
|
|
string++;
|
|
}
|
|
else if (*(string + 1) == '"')
|
|
{
|
|
/* translate \" to \\\" */
|
|
fputc('\\', fp);
|
|
fputc('\\', fp);
|
|
}
|
|
fputc(*string, fp);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Returns the number of tuples in a relation. Unfortunately, currently
|
|
* must do a scan of the entire relation to determine this.
|
|
*
|
|
* relation is expected to be an open relation descriptor.
|
|
*/
|
|
static int
|
|
CountTuples(Relation relation)
|
|
{
|
|
HeapScanDesc scandesc;
|
|
HeapTuple tuple;
|
|
|
|
int i;
|
|
|
|
scandesc = heap_beginscan(relation, 0, false, 0, NULL);
|
|
|
|
for (tuple = heap_getnext(scandesc, 0, NULL), i = 0;
|
|
tuple != NULL;
|
|
tuple = heap_getnext(scandesc, 0, NULL), i++)
|
|
;
|
|
heap_endscan(scandesc);
|
|
return (i);
|
|
}
|