[OPEN-ILS-DEV] Holdings Import Program
Travis Schafer
tschafer at carsoncity.k12.mi.us
Wed Nov 14 05:36:55 EST 2007
Skipped content of type multipart/alternative-------------- next part --------------
//
// create_holdings.c
//
// Carson City - Crystal Area Schools
// Technology Department
// 115 E Main
// Carson City, MI 48811
// (989) 584-3138
//
// Given an XML MARC file, this program creates holdings information
// for records whose bibliographic data is already present in the Evergreen
// database (based on TCN).
//
// This program DOES NOT pull the "holding libary" from the the MARC XML
// file. Instead, this option is set on the command line by specifying the
// actor.org_unit.id value for the owning and circulating libraries. This
// is because the program was originally developed for K-12 migrations from
// systems that were "islands," and in many cases didn't have a holdings
// tag that indicated the owning or circulating library. Serveral other
// items of information that would be pulled from MARC during conversion
// from a "sane" system are simalarly defined as command line options. Again,
// the incumbant K-12 systems aren't sane.
//
// However, it would be trivial to modify this program to extract additional
// information from a holdings tag. Note also that several functions are
// declared and defined, but not used... they are present for debugging.
//
// This program is poorly written. It probably leaks memory... it actually causes
// permanent damage to RAM. It's been known to cause server farms to burst
// into flames. It stole money from my sock drawer. You've been warned...
//
// There are two tables involved in this enterprise. The relavent fields
// (the ones we will be filling out) are as follows (FK indicates a foreign
// key):
//
// asset.call_number
// creator : FK - User who created this entry (actor.usr.id)
// editor : FK - User who last edited this record (actor.usr.id)
// record : FK - Biblio Data for copy (biblio.record_entry.id)
// owning_lib : FK - Owning library (actor.org_unit.id)
// label : The call number!
//
//
// asset.copy
// circ_lib : FK - Circulating Library (actor.org_unit.id)
// creator : FK - User who created this entry (actor.usr.id)
// call_number : FK - Item Call Number (asset.call_number.id)
// editor : FK - User who last edited this record (actor.usr.id)
// status : FK - Item Status (config.copy_status.id)
// location : FK - Location (ie, Stacks) of copy (asset.copy_location.id)
// loan_duration : Required, but not an FK... '2' is popular
// fine_level : Required, but not an FK... '2' is popular
// price : Item Price
// barcode : Not suprisingly, the item barcode
//
// So, basically, we extract the Barcode, Price, Call Number, and TCN of
// each record in the MARC File. Then, we use the TCN to find the value for
// asset.call_number.record, create a call number record, then create a copy
// record.
//
// We get editor, owning_lib, circ_lib, creator, status, location, etc from
// the command line...
//
// Or at least that's the plan
//
//
// Copyright 2007 Travis Schafer
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 3
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
// Initial Program:
// 2007-11-07 T.Schafer
//
// Changes:
//
// 2007-11-13 T.Schafer - 1) Initialized variable in wrong spot. This meant
// that if any copy was found to have an existing
// barcode, no subsequent entires would be inserted
// to the database (basically, we didn't reset and
// "exists" flag at the top of a loop)
//
// 2007-11-14 T.Schafer 2) Inserted GPL/Copyright Notice
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <libpq-fe.h>
#include <libxml/parser.h>
#include <libxml/tree.h>
#define _GNU_SOURCE
#include <getopt.h>
#define TRUE 1
#define FALSE 0
#define MAXFIELD 512 // Maximum field length in copy struct (used to
// construct a buffer for escaping of strings).
#define MAXSTMT 4096 // Maximum length of an SQL statement
#define STATUSINTVL 250 // On database update, give status every STATUSINTVL
// records.
// Structure for copy records
struct copy
{
char *tcn; // TCN of biblio to which copy is associated
char *call; // Copy Call Number
char *bcode; // Copy Barcode
char *price; // Copy Price
struct copy *next; // Next copy in a linked list
};
// Initialize a linked list of copies (ie, create front guard)
//
// Arguments: (void)
//
// Returns: Pointer to new list (ie, pointer to front guard)
struct copy *copy_initlist( void );
// Free a list of copies
//
// Arguments: struct copy *copylist - Copy list to free
// Returns: (void)
void copy_freelist( struct copy *copylist );
// Insert a copy at the end of a list
//
// Arguments: struct copy *copylist - List into which new record is inserted
// char *tcn - TCN of new record
// char *call - Call Number of new record
// char *bcode - Barcode of new record
// char *price - Price of new record
//
// Returns: (void)
void copy_insert( struct copy *copylist, char *tcn, char *call, char *bcode,
char *price );
// Link copy lists lista and listb
//
// Note: This function literally links the linked lists... so neither list
// may be freed after a call to this function. However, the list B start guard
// is freed, so all references to listb become invalid.
//
// Arguments: struct copy* lista - First List
// struct copy* listb - Second List
//
// Returns: (void)
void copy_linklists( struct copy *lista, struct copy *listb );
// Print the contents of a list of copies
//
// Arguments: FILE* ofd - File Descriptor to which list
// is printed
// struct copy *copylist - Copy list to print
// Returns: (void)
void copy_printlist( FILE* ofd, struct copy *copylist );
// Count the number of elements in a linked list of copies
//
// Arguments: struct copy *copylist - List to count
// Returns: unsigned - Number of copies in list
unsigned copy_countlist( struct copy *copylist );
// Return a linked list of all holdings found in a MARC XML document
//
// Arguments: PGconn *conn - Needed for escaping string for SQL
// (ie, get DB localization info)
// xmlNode *collection - Collection node from XML Marc doc
// char *tcnfield - Field that holds item TCN
// char *holdfield - (sub)Field that has holdings data
// char *bcodefield - (sub)Field that has barcode
// char *calltag - (sub)Field that price
//
// Returns: Linked list of all holdings found in document
struct copy *process_doc( PGconn *conn, xmlNode *collection, char *tcnfield,
char *holdfield, char *bcodetag, char *calltag,
char *pricetag );
// Given a record, return a linked list of all holdings found within that
// record
//
// Arguments: PGconn *conn - DB Connection for escaping strings
// xmlNode *record - Record to process
// char *tcnfield - Tag of TCN Field
// char *holdfield - Tag of holdings field
// char *bcodetag - Tag of barcode subfield
// char *calltag - Tag of call number subfield
// char *pricetag - Tag of price subfield
//
// Returns: struct copy* - Linked list of copies found in record,
// or NULL if no copies are found
struct copy *process_record( PGconn *conn, xmlNode *record, char *tcnfield,
char *holdfield, char *bcodetag, char *calltag,
char *pricetag );
// Return the content of a given subfield (ie, child of xmlNode *field)
// where the subfield has "property" set to "value"
//
// Note: Caller must free returned string, as appropriate
//
// Arguments: xmlNode *field - Field whose subfields are searched
// char *property - Propert to search
// char *value - Property is set to this value for match
//
// Returns: Text of subfield, or a zero length string if nothing is found
char *get_subfield_contents( xmlNode *field, char *property, char *value );
// Remove any character that is not a digit or a '.'
//
// Arguments: char *price - Price string to be cleaned
//
// Outputs: char *price - Cleaned price string
// Returns: (void)
void clean_price( char *price );
// Given a linked list of copies, update the Evergreen database...
//
// Arguments: PGconn *conn - Connection to Evergreen DB
// struct copy *copylist - List of copies for which records must
// be created
// unsigned egcreator - Database ID of Creating User
//
// Returns: (void)
void update_database( PGconn *conn, struct copy *copylist, unsigned egcreator,
unsigned egeditor, unsigned egstatus,
unsigned egownlib, unsigned egcirclib,
unsigned eglocation );
// Print the statement, error message contained in a statement result (if any),
// and halt execution immediately.
//
// Arguments: FILE *ofd - Descriptor to which error is printed
// char *stmt - Statement associated with result
// PGresult *sres - Statement Result
//
// Returns: (void)
void database_printerr( FILE *ofd, char *stmt, PGresult *sres );
int main( int argc, char **argv )
{
// Current Command Line Option
int currOpt;
// Count of Command Line Options Process
unsigned optCount;
// Name of XML MARC file
char *xfname;
// Database Host
char *dbhost;
// Database Name
char *dbname;
// Database User
char *dbuser;
// Database Password
char *dbpass;
// Evergreen Creator ID
unsigned egcreator;
// Evergreen Editor ID
unsigned egeditor;
// Evergreen Status ID
unsigned egstatus;
// Evergreen Owning Lib ID
unsigned egownlib;
// Evergreen Cirgulating Lib ID
unsigned egcirclib;
// Evergreen Location ID
unsigned eglocation;
// Tag of TCN Field
char *tcnfield;
// Tag of Holdings Field
char *holdfield;
// Tag of Barcode Field
char *bcodetag;
// Tag of Call Number Subfield
char *calltag;
// Tag of Price Subfield
char *pricetag;
// Linked list of copies
struct copy *copylist;
// XML Document
xmlDocPtr doc;
// Connection to database
PGconn *conn;
optCount=0;
while( TRUE )
{
static struct option options [] =
{
{ "xmlfile", required_argument, 0, 'x' },
{ "dbhost", required_argument, 0, 's' },
{ "dbname", required_argument, 0, 'd' },
{ "dbuser", required_argument, 0, 'u' },
{ "dbpass", required_argument, 0, 'w' },
{ "egcreator", required_argument, 0, 'r' },
{ "egeditor", required_argument, 0, 'e' },
{ "egownlib", required_argument, 0, 'o' },
{ "egcirclib", required_argument, 0, 'i' },
{ "egstatus", required_argument, 0, 'a' },
{ "eglocation", required_argument, 0, 'l' },
{ "tcnfield", required_argument, 0, 't' },
{ "holdfield", required_argument, 0, 'h' },
{ "bcodetag", required_argument, 0, 'b' },
{ "calltag", required_argument, 0, 'c' },
{ "pricetag", required_argument, 0, 'p' },
{ 0, 0, 0, 0 }
};
int option_index = 0;
currOpt = getopt_long_only( argc, argv, "", options, &option_index );
if( currOpt == -1 )
{
break;
}
switch( currOpt )
{
case 'x' :
{
xfname = strdup( optarg );
optCount++;
break;
}
case 's' :
{
dbhost = strdup( optarg );
optCount++;
break;
}
case 'd' :
{
dbname = strdup( optarg );
optCount++;
break;
}
case 'u' :
{
dbuser = strdup( optarg );
optCount++;
break;
}
case 'w' :
{
dbpass = strdup( optarg );
optCount++;
break;
}
case 'r' :
{
sscanf( optarg, "%d", &egcreator );
optCount++;
break;
}
case 'e' :
{
sscanf( optarg, "%d", &egeditor );
optCount++;
break;
}
case 'a' :
{
sscanf( optarg, "%d", &egstatus );
optCount++;
break;
}
case 'o' :
{
sscanf( optarg, "%d", &egownlib );
optCount++;
break;
}
case 'i' :
{
sscanf( optarg, "%d", &egcirclib );
optCount++;
break;
}
case 'l' :
{
sscanf( optarg, "%d", &eglocation );
optCount++;
break;
}
case 't' :
{
tcnfield = strdup( optarg );
optCount++;
break;
}
case 'h' :
{
holdfield = strdup( optarg );
optCount++;
break;
}
case 'b' :
{
bcodetag = strdup( optarg );
optCount++;
break;
}
case 'c' :
{
calltag = strdup( optarg );
optCount++;
break;
}
case 'p' :
{
pricetag = strdup( optarg );
optCount++;
break;
}
case '?':
{
// Error printed by getopt()
break;
}
}
}
if( optCount != 16 )
{
fprintf( stderr, "%s: Given an XML MARC file, create holding\n"
"records in Evergreen.\n\n"
"Arguments:\n"
"--xmlfile : XML MARC File\n"
"--dbhost : Evergreen Database Host\n"
"--dbname : Evergreen Database Name\n"
"--dbuser : Evergreen Database User\n"
"--dbpass : Evergreen Database Password\n"
"--egcreator : Primary Key of Evergreen Record Creator\n"
" (ie, record is marked as created by this user\n"
"--egeditor : Primary Key of Evergreen Editing User\n"
"--egstatus : Evergreen Status to set on creation\n"
"--egownlib : Primary Key of Owning Library\n"
"--egcirclib : Primary Key of Circulating Library\n"
"--eglocation : Primary Key of Copy Location (ie, stacks)\n"
"--tcnfield : Tag of TCN Field (001 is common)\n"
"--holdfield : Tag of holdings field (852 is common)\n"
"--bcodetag : Tag of barcode subfield\n"
"--calltag : Tag of callnumber subfield\n"
"--pricetag : Tag of item price subfield\n",
argv[ 0 ] );
exit( 1 );
}
// Attempt to parse XML File
if( !(doc = xmlReadFile( xfname, NULL, 0 ) ) )
{
// Error printed by xmlReadFile
exit( 1 );
}
// Attempt DB Connection
conn = PQsetdbLogin( dbhost, NULL, NULL, NULL, dbname, dbuser, dbpass );
if( PQstatus( conn ) == CONNECTION_BAD )
{
fprintf( stderr, "%s: Can not connet to database!\n"
"Error is :%s\n\n"
"***Exiting***\n",
argv[0], PQerrorMessage( conn ) );
PQfinish( conn );
exit( 1 );
}
// Extract linked list of copy structures from XML doc tree
copylist = process_doc( conn, xmlDocGetRootElement( doc ), tcnfield,
holdfield, bcodetag, calltag, pricetag );
xmlFreeDoc( doc );
xmlCleanupParser();
// Update database to reflect linked list of copies
update_database( conn, copylist, egcreator, egeditor, egstatus, egownlib,
egcirclib, eglocation );
copy_freelist( copylist );
PQfinish( conn );
free( xfname );
free( dbhost );
free( dbname );
free( dbuser );
free( dbpass );
free( tcnfield );
free( holdfield );
free( bcodetag );
free( calltag );
free( pricetag );
return 0;
}
struct copy *copy_initlist( void )
{
// List to return
struct copy *retList;
retList = (struct copy*)malloc( sizeof( struct copy ) );
retList->next = 0;
return retList;
}
void copy_freelist( struct copy *copylist )
{
// Pointer to current copy in list
struct copy *curr;
// Pointer to next copy in list
struct copy *next;
// Advance past guard
curr = copylist->next;
while( curr )
{
next = curr->next;
free( curr->tcn );
free( curr->call );
free( curr->bcode );
free( curr->price );
free( curr );
curr = next;
}
// Free the guard
free( copylist );
return;
}
void copy_insert( struct copy *copylist, char *tcn, char *call, char *bcode,
char *price )
{
// Current Copy in List
struct copy* curr;
curr = copylist;
// Find the end of the list
while( curr )
{
if( !curr->next )
{
curr->next = (struct copy*)malloc( sizeof( struct copy ) );
curr = curr->next;
curr->tcn = strdup( tcn );
curr->call = strdup( call );
curr->bcode = strdup( bcode );
curr->price = strdup( price );
curr->next = 0;
}
curr = curr->next;
}
return;
}
void copy_linklists( struct copy *lista, struct copy *listb )
{
// Current position in list a
struct copy *curr;
// Advance curr to the last record
for( curr = lista; curr->next; curr=curr->next );
// Okay, curr is pointing to the end record in list A. Link the lists.
curr->next = listb->next;
// Dont forget to free the guard.
free( listb );
return;
}
void copy_printlist( FILE* ofd, struct copy *copylist )
{
// Current item in copylist
struct copy* curr;
for( curr = copylist->next; curr; curr=curr->next )
{
fprintf( ofd, "\n*** Copy Record ***\n" );
fprintf( ofd, "TCN: %s\n", curr->tcn );
fprintf( ofd, "Call: %s\n", curr->call );
fprintf( ofd, "Barcode: %s\n", curr->bcode );
fprintf( ofd, "Price: %s\n", curr->price );
}
return;
}
unsigned copy_countlist( struct copy *copylist )
{
// Current copy in list
struct copy *curr;
// Count of copies
unsigned count=0;
for( curr = copylist->next; curr; curr=curr->next )
{
count++;
}
return count;
}
struct copy *process_doc( PGconn *conn, xmlNode *collection, char *tcnfield,
char *holdfield, char *bcodetag, char *calltag,
char *pricetag )
{
// Current Record in XML Tree
xmlNode *currRec = NULL;
// Linked list of holdings found...
struct copy *retList;
retList = copy_initlist();
for( currRec = collection->children; currRec; currRec=currRec->next )
{
copy_linklists( retList,
process_record( conn, currRec, tcnfield, holdfield,
bcodetag, calltag, pricetag ) );
}
return retList;
}
struct copy *process_record( PGconn *conn, xmlNode *record, char *tcnfield,
char *holdfield, char *bcodetag, char *calltag,
char *pricetag )
{
// Pointer to current field
xmlNode *currField = NULL;
// Tag of current Field
xmlChar *currTag;
// Current TCN
char *currTcn;
// Current Call Number
char *currCall;
// Current Barcode
char *currBcode;
// Current Price
char *currPrice;
// Copy list to return
struct copy *retList;
// Did we find good copy records?
unsigned goodRecs = TRUE;
// Initialize return list
retList = copy_initlist();
// Get the TCN
currTcn = get_subfield_contents( record, "tag", tcnfield );
if( !strlen( currTcn ) )
{
goodRecs = FALSE;
}
// Now we need to find any holdings tags and get the necessary subfields.
// There is really no reason to do this if we didn't get a TCN.
if( goodRecs )
{
for( currField=record->children; currField; currField=currField->next )
{
currTag = xmlGetProp( currField, (xmlChar*)"tag" );
if( !xmlStrcmp( currTag, (xmlChar*)holdfield ) )
{
currCall = get_subfield_contents( currField, "code", calltag );
currBcode = get_subfield_contents( currField, "code", bcodetag );
currPrice = get_subfield_contents( currField, "code", pricetag );
// Okay, call number and barcode are required... we can live
// without price. Therefore, if we have both currCall and
// currBcode, we should enter a new copy in our copylist to
// return
if( strlen( currCall ) && strlen( currBcode ) )
{
// Buffer for escaping of strings
char buff[MAXFIELD+1];
// If we didn't get a price, default to '0.00'. Otherwise,
// clean up existing price
if( !strlen( currPrice ) )
{
free( currPrice );
currPrice = strdup( "0.00" );
}
else
{
clean_price( currPrice );
}
// We really need to escape these, as well as the TCN,
// before we insert into the struct... since the struct
// is later used for SQL purposes.
PQescapeStringConn( conn, buff, currTcn, strlen(currTcn),
NULL );
free( currTcn );
currTcn = strdup( buff );
PQescapeStringConn( conn, buff, currCall, strlen(currCall),
NULL );
free( currCall );
currCall = strdup( buff );
PQescapeStringConn( conn, buff, currBcode, strlen(currBcode),
NULL );
free( currBcode );
currBcode = strdup( buff );
PQescapeStringConn( conn, buff, currPrice, strlen(currPrice),
NULL );
free( currPrice );
currPrice = strdup( buff );
copy_insert( retList, currTcn, currCall, currBcode,
currPrice );
free( currCall );
free( currBcode );
free( currPrice );
}
}
xmlFree( currTag );
}
}
free( currTcn );
return retList;
}
char *get_subfield_contents( xmlNode *field, char *property, char *value )
{
// Current Subfield
xmlNode *currSub = NULL;
// Current Tag
xmlChar *currTag;
// String to return
char *retStr;
// Did we find the subfield?
unsigned found = FALSE;
for( currSub = field->children; currSub && !found; currSub = currSub->next )
{
currTag = xmlGetProp( currSub, (xmlChar*)property );
if( !xmlStrcmp( currTag, (xmlChar*)value ) )
{
found = TRUE;
retStr = (char*)xmlNodeListGetString( currSub->doc,
currSub->children,
1 );
}
xmlFree( currTag );
}
if( !found )
{
retStr = (char*)malloc( sizeof( char ) );
*retStr = '\0';
}
return retStr;
}
void clean_price( char *price )
{
// Buffer
char *buff;
// Current position in input/output string (we're a side-effect)
char *currPos;
// Generic counter
unsigned i;
// Initialize buffer and set current position in output string
buff = strdup( price );
currPos = price;
*price = '\0';
for( i = 0; i < strlen( buff ); i++ )
{
if( isdigit( buff[i] ) || (buff[i] == '.' ) )
{
*currPos = buff[i];
currPos += sizeof( char );
}
}
*currPos = '\0';
free( buff );
}
void update_database( PGconn *conn, struct copy *copylist, unsigned egcreator,
unsigned egeditor, unsigned egstatus,
unsigned egownlib, unsigned egcirclib,
unsigned eglocation )
{
// SQL statement Buffer
char stmt[MAXSTMT+1];
// Result of most recently submitted statement
PGresult *sres;
// Current Copy
struct copy *curr;
// This could take a while. Keep track of the number of records that
// we've processed. Give a status update every once in a while.
unsigned processed = 0;
// Advance past guard
curr = copylist->next;
// Start Transaction
sres = PQexec( conn, "BEGIN;" );
database_printerr( stderr, stmt, sres );
PQclear( sres );
while( curr )
{
// Does copy already exist, based on barcode?
unsigned exists = FALSE;
// See if a copy record already exists in the database, based on barcode
sprintf( stmt, "SELECT id FROM asset.copy WHERE barcode = '%s';",
curr->bcode );
sres = PQexec( conn, stmt );
database_printerr( stderr, stmt, sres );
if( PQntuples( sres ) )
{
fprintf( stdout, "Barcode already exists: Bcode %s\tTCN %s\n",
curr->bcode, curr->tcn );
exists = TRUE;
}
PQclear( sres );
// Get biblio.record_entry key for this record
sprintf( stmt, "SELECT id FROM biblio.record_entry WHERE "
"tcn_value = '%s';", curr->tcn );
sres = PQexec( conn, stmt );
database_printerr( stderr, stmt, sres );
if( PQntuples( sres ) && !exists )
{
// Primary key of existing bibliographic record entry
unsigned biblioKey;
// Primary key of new/existing call number record
unsigned callKey;
// We have a bibliographic record entry. Get the key
sscanf( PQgetvalue( sres, 0, 0 ), "%d", &biblioKey );
PQclear( sres );
// This is worth a short discussion. The "right" way to do this
// is to figure out how many records we are going to insert
// ahead of time, and reserve space in the primary key sequence.
// However, there is a damned fine chance that we'll end up
// inserting far fewer records than are present in our list.
// To compensate for that, we could check all of the records
// ahead of time, to make sure that there is a matching bibliographic
// entry in the database... but then we aren't saving any queries..
// Okay, the record, owning_lib, label tuple must be unique in
// asset.call_number. This implies that asset.call_number is the
// "volume" that appears in the user interface. SO, we need to see
// if an appropriate asset.call_number record exists. If it does,
// we use that... if it doesn't, we create one
sprintf( stmt, "SELECT id FROM asset.call_number WHERE "
"record = %d AND owning_lib = %d AND label = '%s';",
biblioKey, egownlib, curr->call );
sres = PQexec( conn, stmt );
database_printerr( stderr, stmt, sres );
if( PQntuples( sres ) )
{
// Existing asset.call_number
sscanf( PQgetvalue( sres, 0, 0 ), "%d", &callKey );
PQclear( sres );
}
else
{
// New asset.call_number
sprintf( stmt, "SELECT nextval( 'asset.call_number_id_seq' );" );
sres = PQexec( conn, stmt );
database_printerr( stderr, stmt, sres );
sscanf( PQgetvalue( sres, 0, 0 ), "%d", &callKey );
PQclear( sres );
sprintf( stmt, "INSERT INTO asset.call_number "
"( id, creator, editor, record, "
" owning_lib, label ) "
"VALUES "
"( %d, %d, %d, %d, "
" %d, '%s' );",
callKey, egcreator, egeditor, biblioKey,
egownlib, curr->call );
sres = PQexec( conn, stmt );
database_printerr( stderr, stmt, sres );
PQclear( sres );
}
// Create the asset.copy entry
sprintf( stmt, "INSERT INTO asset.copy "
"( circ_lib, creator, call_number, "
" editor, status, location, "
" loan_duration, fine_level, price, "
" barcode ) "
"VALUES "
"( %d, %d, %d, "
" %d, %d, %d, "
" %d, %d, '%s', "
" %s );",
egcirclib, egcreator, callKey,
egeditor, egstatus, eglocation,
2, 2, curr->price,
curr->bcode );
sres = PQexec( conn, stmt );
database_printerr( stderr, stmt, sres );
PQclear( sres );
}
else if( !exists )
{
fprintf( stdout, "TCN Not Found: %s\n", curr->tcn );
PQclear( sres );
}
curr = curr->next;
processed++;
if( !(processed % STATUSINTVL ) )
{
fprintf( stderr, "Processed: %d records\n", processed );
}
}
// End Transaction
sres = PQexec( conn, "COMMIT;" );
database_printerr( stderr, stmt, sres );
PQclear( sres );
return;
}
void database_printerr( FILE *ofd, char *stmt, PGresult *sres )
{
// Pointer to error message within sres
char *err;
err = PQresultErrorMessage( sres );
if( strlen( err ) )
{
fprintf( stderr, "\n\n\n***Begin SQL Error**\n\n" );
fprintf( stderr, "Statement: %s\n", stmt );
fprintf( stderr, "%s\n", err );
fprintf( stderr, "IMPORT ABORTED. TRANSACTION ROLLED BACK.\n" );
fprintf( stderr, "DATABASE REMAINS UNCHANGED.\n" );
exit( 1 );
}
return;
}
More information about the Open-ils-dev
mailing list