[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