/*
 * Filename: msql-import.c
 * Project:  msql-import
 *
 * Function: This program reads the contents of a flat file
 *           and loads it into a Mini SQL table.
 *           The program does not create the MiniSQL table,
 *           it must have been created beforehand.  msql-import
 *           simply sends INSERTs to the database server.
 *
 *           When importing a flat file that was created in
 *           the DOS world, make sure you convert the file
 *           to unix by replacing CR/LF with LF only.
 *
 * Author:   Pascal Forget <pascal@wsc.com>
 *
 * Copyright (C) 1995-1996 Pascal Forget.  All Rights Reserved
 *
 * PASCAL FORGET MAKES NO REPRESENTATIONS ABOUT THE SUITABILITY
 * OF THIS SOFTWARE FOR ANY PURPOSE.  IT IS SUPPLIED "AS IS"
 * WITHOUT EXPRESS OR IMPLIED WARRANTY.
 *
 * Permission to use, copy, modify, and distribute this software
 * and its documentation for any purpose and without fee is
 * hereby granted, provided that the above copyright notice
 * appear in all copies of the software.
 */

/*
** This code has been munged, merged, and generally defaced by David J.
** Hughes <Bambi@Hughes.com.au> for inclusion into the mSQL 2.0 release
** distribution.
**
** I'd like to thank Pascal for his work and for allowing me to include
** it in the mSQL release.
**
** o	Rewrote arg and flag handling to match the other utilities
** o	Added support for non-default config files
** o	Made the verbose output optional
** o	Added support for a user defined escape char (to match msqlexport)
*/

#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <unistd.h>
#include <sys/types.h>
#include "msql.h"


/*
** Definitions and prototypes for "safe string routines"
*/
typedef struct {
    char *buffer;
    unsigned int length;
    unsigned int capacity;
} SafeString;


/* Basic functions */

char ss_character_at_index();
int ss_compare_strings();
SafeString *ss_create_string();
SafeString *ss_create_string_from_file();
SafeString *ss_create_string_with_capacity();
SafeString *ss_create_string_with_c_string();
SafeString *ss_duplicate_string();
int ss_strings_are_equal();
int ss_string_length();
void ss_append_c_string();
void ss_copy_c_string();
void ss_set_string_capacity();
void safe_c_string_free();

/* String values */

char *ss_string_value();
int ss_int_value();
float ss_float_value();
double ss_double_value();

/* Other goodies */

int ss_has_prefix();
int ss_has_case_prefix();/* case insensitive*/
int ss_has_suffix();
SafeString *ss_last_path_component();
SafeString *ss_path_extension();
SafeString *ss_string_by_deleting_last_path_component();
SafeString *ss_string_by_deleting_path_extension();

/* replacement for strdup() */
char *safe_c_string_copy();


/*
** msql-import definitions and prototypes
*/

char *backslashify_special_chars();
int *datatypes();
char *format_delimiter();
char *get_record();
int insert_record();
int row_length();
char *str_parse();
char *safe_c_string_copy();


#define MSQL_IMPORT_VERSION "0.1.2"
#define IMPORT_MAX_FIELDS 255
#define DEBUG 1
#define SAFESTRING_VERSION "1"

#ifndef MAX
#  define MAX(a,b) (a>b)a:b
#endif

int 	sock,
	verbose=0;

SafeString *query;

/*****************************************************************
 * ss_append_character() appends a character to str              *
 *****************************************************************/

void
ss_append_character(str, c)
	SafeString *str;
	char c;
{
    unsigned int length = str[0].length;

    /* Grows by 64 byte blocks, no need to realloc every time */    
    ss_set_string_capacity(str, length + 64);

    str[0].buffer[length] = c;
    str[0].buffer[length+1] = '\0';
    str[0].length++;
}

/*****************************************************************
 * ss_append_string() appends a Safe String to another one       *
 *****************************************************************/

void
ss_append_string(str, other_str)
	SafeString *str, 
	*other_str;
{
    ss_append_c_string(str, other_str[0].buffer);
}

/*****************************************************************
 * ss_append_c_string() appends a c string buffer to str         *
 *****************************************************************/

void
ss_append_c_string(str, cstring)
	SafeString *str; 
	char *cstring;
{
    unsigned int cstring_length;
    
    if ((cstring != NULL) && ((cstring_length = strlen(cstring)) > 0))
    {
	if (str[0].buffer != NULL)
	{
	    ss_set_string_capacity(str, str[0].length + cstring_length);
	}
	else
	{
	    ss_set_string_capacity(str, cstring_length);
	}
	    
	if (str[0].buffer != NULL)
	{
	    strcat(str[0].buffer, cstring);
	}
	else
	{
	    strcpy(str[0].buffer, cstring);
	}

	str[0].length += cstring_length;
    }
}

/*****************************************************************
 * ss_fast_append_c_string_length() appends a c string  to str   *
 * This function is faster than append_c_string_length  because  *
 * it doen't validate the calling arguments.                     *
 *****************************************************************/

void
ss_fast_append_c_string_length(str, cstring, length)
	SafeString *str;
	char *cstring;
	int length;
{
    int i;
    unsigned int old_length;
    
    if (str[0].buffer != NULL)
    {
	old_length = str[0].length;
	ss_set_string_capacity(str, old_length + length);
    }
    else
    {
	ss_set_string_capacity(str, length);
	old_length = 0;
    }
    
    if (str[0].buffer != NULL)
    {

	/* the following 5 lines are faster than strcat() */
	for (i=0; i<length; i++)
	{
	    str[0].buffer[old_length + i] = cstring[i];
	}
	str[0].buffer[old_length + i] = '\0';

    }
    else
    {
	strcpy(str[0].buffer, cstring);
    }

    str[0].length += length;
}

/*******************************************************************
 * ss_append_c_string() appends the first length characters of a c *
 * string buffer to str                                            *
 *******************************************************************/

void
ss_append_c_string_length(str, cstring, length)
	SafeString *str; 
	char *cstring; 
	int length;
{
    int real_length;
    
    if (cstring != NULL)
    {
	real_length = strlen(cstring);
	
	if (length < real_length)
	{
	    real_length = length;
	}
	
	if (str[0].buffer != NULL)
	{
	    ss_set_string_capacity(str, str[0].length + real_length);
	}
	else
	{
	    ss_set_string_capacity(str, real_length);
	}
	    
	if (str[0].buffer != NULL)
	{
	    strncat(str[0].buffer, cstring, length);
	}
	else
	{
	    strncpy(str[0].buffer, cstring, length);
	}
	
	str[0].length += real_length;	
    }
}

/*****************************************************************
 * ss_character_at_index() returns the character at index in str *
 *****************************************************************/

char
ss_character_at_index(str, index)
	SafeString *str; 
	int index;
{
    if ((str != NULL) && (str[0].length > index) && (index > -1))
    {
	return str[0].buffer[index];
    }
    else
    {
	return 0;
    }
}

/*****************************************************************
 * ss_compare_strings() is the equivalent to strcmp()            *
 *****************************************************************/

int
ss_compare_strings(str1, str2)
	SafeString *str1; 
	SafeString *str2;
{
    return strcmp(str1[0].buffer, str2[0].buffer);
}

/******************************************************************
 * ss_copy_string() puts a SafeString's value into another string *
 ******************************************************************/

void
ss_copy_string(to_str, from_str)
	SafeString *to_str, 
		*from_str;
{
    if ((to_str != NULL) && (from_str != NULL))
    {
	ss_copy_c_string(to_str, from_str[0].buffer);
    }
}

/*****************************************************************
 * ss_copy_c_string() copies a c string buffer in str            *
 *****************************************************************/

void
ss_copy_c_string(str, cstring)
	SafeString *str; 
	char *cstring;
{
    unsigned int cstring_length;
    
    if (cstring != NULL)
    {
	cstring_length = strlen(cstring);
	
	ss_set_string_capacity(str, cstring_length);
	strcpy(str[0].buffer, cstring);
	str[0].length = cstring_length;
    }
    else
    {
	str[0].buffer = NULL;
	str[0].length = 0;
    }
}

/*****************************************************************
 * ss_create_string() allocates and initializes a SafeString     *
 *****************************************************************/

SafeString *
ss_create_string()
{
    SafeString *string = (SafeString *)malloc(sizeof(SafeString));
    string[0].buffer = (char *)NULL;
    string[0].length = 0;
    string[0].capacity = 0;
    return string;
}

/*****************************************************************
 * ss_create_string_from_file() creates a SafeString and fills   *
 * it with the contents of the file at filename.                 *
 *****************************************************************/

SafeString *
ss_create_string_from_file(filename)
	char *filename;
{
    FILE *fd;
    int filesize;
    char *string;
    SafeString *result;

    if (filename == NULL) return NULL;
    
    if ((fd = fopen(filename, "rw")) == NULL)
    {
	perror("error opening file.");
	return NULL;
    }

    fseek(fd, 0L, SEEK_END);

    filesize = ftell(fd);

    fseek(fd, 0L, SEEK_SET);

    string = (char *)malloc(filesize + 1);
    
    fread(string, filesize, 1, fd);

    string[filesize] = '\0';

    fclose(fd);

    result = ss_create_string_with_c_string (string);

    safe_c_string_free(string);
    
    return result;
}

/*****************************************************************
 * ss_create_string_with_capacity() creates a string and         *
 * allocates a buffer for it                                     *
 *****************************************************************/

SafeString *
ss_create_string_with_capacity(capacity)
	unsigned int capacity;
{
    SafeString *string = ss_create_string();
    ss_set_string_capacity(string, capacity);
    return string;
}

/******************************************************************
 * ss_create_string_with_c_string() creates and sets a SafeString *
 ******************************************************************/

SafeString *
ss_create_string_with_c_string(cstring)
	char *cstring;
{
    SafeString *string = ss_create_string();
    ss_append_c_string(string, cstring);
    return string;
}

/*****************************************************************
 * ss_destroy_string() frees a SafeString and its buffer         *
 *****************************************************************/

void
ss_destroy_string(str)
	SafeString *str;
{
    if (str[0].buffer != NULL)
    {
	free(str[0].buffer);
	str[0].buffer = NULL;
    }
    free(str);
}

/******************************************************************
 * ss_duplicate_string() creates an identical copy of a SafeString *
 ******************************************************************/

SafeString *
ss_duplicate_string(str)
	SafeString *str;
{
    SafeString *string = ss_create_string();
    ss_append_c_string(string, str[0].buffer);
    return string;
}

/*****************************************************************
 * ss_insert_c_string_at_index() inserts a c string to an        *
 * existing SafeString at index.                                 *
 *****************************************************************/

void
ss_insert_c_string_at_index(str, cstring, index)
	SafeString *str;
	char *cstring; 
	int index;
{
    int cstring_length;
    int new_length;
    int i;
#if 0
    char *tmp;
    SafeString *new;
    char *ptr;
#endif
    
    if ((str == NULL) || (cstring == NULL)) return;

    cstring_length = strlen (cstring);

    new_length = str[0].length + cstring_length;

#if 1  /* slower but portable */
    ss_set_string_capacity (str, new_length);

    /* Make space for the cstring */
    i = new_length;

    while (i-- > index)
    {
	str[0].buffer[i] = str[0].buffer[i - cstring_length];
    }

    /* put the cstring in place */
    for (i=0; i<cstring_length; i++)
    {
	str[0].buffer[index+i] = cstring[i];
    }
#else /* faster but bcopy not portable */

    tmp = malloc (new_length + 1);
    
    bcopy (str[0].buffer, tmp, index);
    tmp[index] = '\0';
    strcat (tmp, cstring);

    ptr = str[0].buffer;
    ptr += index;
    strcat (tmp, ptr);

    ss_set_string_value (str, tmp);
    safe_c_string_free(tmp);
#endif

    str[0].length += cstring_length;
}

/*****************************************************************
 * ss_remove_count_at_index() removes count characters at the    *
 * specified offset from the beginning of the string.            *
 *****************************************************************/

void
ss_remove_count_at_index(str, count, index)
	SafeString *str; 
	int count; 
	int index;
{
    int i, len;

    if ((str != NULL) && (count > -1) && (index > -1))
    {
	len = ss_string_length(str);

	if ((index + count) > len)
	{
	    count = len;
	}

	for (i=index; i<(len-index); i++)
	{
	    str[0].buffer[i] = str[0].buffer[i+1];
	}

	str[0].buffer[len-count] = '\0';
	str[0].length -= count;
    }
}

/*******************************************************************
 * ss_save_string_to_file() writes the SafeString to an ASCII file *
 *******************************************************************/

void
ss_save_string_to_file(str, filename)
	SafeString *str; 
	char *filename;
{
    FILE *fd;

    fd = fopen(filename, "w+");

    if (fd == NULL)
    {
	perror ("error opening file");
	return;
    }
    
    fwrite(str[0].buffer, str[0].length, 1, fd);
    fclose(fd);
}

/*****************************************************************
 * ss_string_length() returns the number of characters in str    *
 * forces an update of the length of the string object           *
 *****************************************************************/

int
ss_string_length(str)
	SafeString *str;
{
    if ((str != NULL) && (str[0].buffer != NULL))
    {
	str[0].length = strlen(str[0].buffer);
	return str[0].length;
    }
    else
    {
	str[0].length = 0;
	return 0;
    }
}

/*****************************************************************
 * ss_strings_are_equal() returns 1 if the strings are the same  *
 *****************************************************************/

int
ss_strings_are_equal(str1, str2)
	SafeString *str1, 
		*str2;
{
    /* Trivial case */
    if (str1 == str2) return 1;

    if (strcmp(str1[0].buffer, str2[0].buffer) == 0)
    {
	return 1;
    }
    else
    {
	return 0;
    }
}

/*****************************************************************
 * ss_set_string_capacity() allocates a bigger buffer if needed  *
 *****************************************************************/

void
ss_set_string_capacity(str, cap)
	SafeString *str; 
	unsigned int cap;
{
    if (str[0].capacity < cap)
    {
	if (str[0].buffer != NULL)
	{
	    str[0].buffer = (char *)realloc(str[0].buffer, cap+1);
	}
	else
	{
	    str[0].buffer = (char *)malloc(cap+1);
	    str[0].buffer[0] = '\0';
	    str[0].length = 0;
	}
	str[0].capacity = cap;
    }
}



/* String values */

char *
ss_string_value(str)
	SafeString *str;
{
    return str[0].buffer;
}

int
ss_int_value(str)
	SafeString *str;
{
    if (str[0].buffer != NULL)
    {
	return atoi(str[0].buffer);
    }
    else
    {
	return 0;
    }
}

float
ss_float_value(str)
	SafeString *str;
{
    if (str[0].buffer != NULL)
    {
	return atof(str[0].buffer);
    }
    else
    {
	return 0;
    }
}

double
ss_double_value(str)
	SafeString *str;
{
    if (str[0].buffer != NULL)
    {
	return atof(str[0].buffer);
    }
    else
    {
	return 0;
    }
}

void
ss_set_string_value(str, new_value)
	SafeString *str; 
	char *new_value;
{
    ss_copy_c_string(str, new_value);
}

void
ss_set_int_value(str, new_value)
	SafeString *str; 
	int new_value;
{
    char buf[64];

    sprintf(buf, "%i", new_value);

    ss_copy_c_string(str, buf);
}

void
ss_set_float_value(str, new_value)
	SafeString *str; 
	float new_value;
{
    char buf[64];

    sprintf(buf, "%f", new_value);

    ss_copy_c_string(str, buf);
}


void
ss_set_double_value(str, new_value)
	SafeString *str; 
	double new_value;
{
    char buf[64];

    sprintf(buf, "%f", new_value);

    ss_copy_c_string(str, buf);
}




/* Other goodies */

void
ss_capitalize_string(str) /* "new york city" -> "New York City" */
	SafeString *str; 
{
    int i, str_len;
    char *buf;

    if (str != NULL)
    {
	str_len = ss_string_length(str);
	
	if (str_len > 0)
	{
	    buf = str[0].buffer;
	    
	    for (i=0; i<str_len; i++)
	    {
		if (((i==0) || (buf[i-1] == ' ')) && (islower(buf[i])))
		{
		    buf[i] = (int)buf[i] - 'a' + 'A';
		}
	    }
	}
    }
}

int
ss_has_case_prefix(str, prefix) /* case insensitive */
	SafeString *str, 
	*prefix;
{
    if ((str == NULL) || (prefix == NULL))
    {
	return 0;
    } else
    {
	if (strncasecmp(ss_string_value(prefix),
			ss_string_value(str),
			ss_string_length(str)) == 0)
	{
	    return 1;
	}
	else
	{
	    return 0;
	}
    }
}

int
ss_has_prefix(str, prefix)
	SafeString *str, 
		*prefix;
{
    if ((str == NULL) || (prefix == NULL))
    {
	return 0;
    }
    else
    {
	if (strncmp(ss_string_value(prefix),
		    ss_string_value(str),
		    ss_string_length(str)) == 0)
	{
	    return 1;
	}
	else
	{
	    return 0;
	}
    }
}

int
ss_has_suffix(str, suffix)
	SafeString *str, 
		*suffix;
{
    char *extStr;
    char *string;
    int string_len;
    int suffix_len;
    
    if ((str == NULL) || (suffix == NULL))
    {
	return 0;
    }
    else
    {
	extStr = ss_string_value(suffix);
	string = ss_string_value(str);
	string_len = ss_string_length(str);
	suffix_len = ss_string_length(suffix);
	
	return (strcmp(string + string_len - suffix_len, extStr) == 0);
    }
}

SafeString *
ss_last_path_component(str)
	SafeString *str;
{
    char *myInternalBuffer = ss_string_value(str);
    char *buffer = NULL;
    int length = ss_string_length(str);
    SafeString *lastPathComponent;
    char *lastString;

    if (myInternalBuffer != NULL)
    {
	buffer = (char *)malloc(length + 1);
	strcpy(buffer, myInternalBuffer);
    }
    
    if ((buffer == NULL) ||
	(length < 1) ||
	((length == 1) && (buffer[0] == '/')))
    {
	return ss_create_string_with_c_string("");
    }
    else
    {	
	/* get rid of a trailing slash if there is one */
	if (buffer[length-1] == '/')
	{
	    buffer[length-1] = '\0';
	}
    
	/* find the last slash in the string buffer */
	lastString = strrchr(buffer, '/');

	/* if the string does not contain any slash, return it as is */
	if (lastString == NULL)
	{
	    safe_c_string_free(buffer);
	    return ss_duplicate_string(str);
	}
	else if (strlen(lastString) > 1)
	{
	    lastPathComponent = ss_create_string_with_c_string(lastString);

	    /* Remove leading slash if there is one */
	    if (lastString[0] == '/') {
		ss_remove_count_at_index(lastPathComponent, 1, 0);
	    }

	    lastString = NULL;
	    safe_c_string_free(buffer);
	    return lastPathComponent;
	}
    }    

    safe_c_string_free(buffer);
    
    return ss_create_string_with_c_string("");
}

SafeString *
ss_path_extension(str)
	SafeString *str;
{
    SafeString *last = ss_last_path_component(str);
    char *buffer = ss_string_value(last);
    char *extension;
    int length;
    SafeString *result;

    if ((buffer == NULL) ||
	((length = strlen(buffer)) < 1) ||
	((length == 1) && (buffer[0] == '.')) ||
	(buffer[length-1] == '.') ||
	((extension = strrchr(buffer, '.')) == NULL))
    {
	ss_destroy_string(last);
	return ss_create_string_with_c_string("");
    }
    else
    {
	result = ss_create_string_with_c_string(extension+1);
	ss_destroy_string(last);
	return result;
    }
}

void
ss_remove_trailing_zeroes(str)
	SafeString *str;
{
    int len = ss_string_length(str);
    int pos;
    char *strBuf;
    
    if (len > 0)
    {
	pos = len - 1;

	strBuf = ss_string_value(str);
	
	while ((pos >= 0) && ((char)(*(strBuf+pos)) == '0'))
	{
	    pos--;
	}

	if (pos < (len-1))
	{
	    ss_remove_count_at_index(str, len-pos+1, pos+1);
	}
    }
}



/*****************************************************************
 * _fast_c_string_length_compare() is a substitute for strncmp() *
 * that is more than twice as fast.  It just tells you if the    *
 * two c strings are equal or not, and it doesn't validate the   *
 * arguments.                                                    *
 *****************************************************************/
int
ss_fast_c_string_length_compare(s1, s2, length)
	char *s1, *s2; 
	int length;
{
    int i;

    /* Trivial equality */
    if (s1 == s2) return 0;
    
    for (i=0; i<length; i++)
    {
	if (s1[i] != s2[i])
	{
	    return 1;
	}
    }

    return 0; /* equality */
}

/******************************************************************
 * ss_replace_substring() replaces all occurences of old with new *
 ******************************************************************/

void
ss_replace_substring(str, old, new)
	SafeString *str; 
	char *old, *new;
{
    char first = old[0];
    char *ptr;
    SafeString *copy;
    int old_length, new_length;
    unsigned int characters_left_to_process;
    unsigned int buf_length;
    
    if (old == NULL)
    {
	return;
    }

    old_length = strlen (old);
    new_length = 0;

    if (new != NULL)
    {
	new_length = strlen(new);
    }

    copy = ss_create_string_with_c_string(str[0].buffer);

    characters_left_to_process = copy[0].length;
    
    ss_set_string_value (str, "");    

    ptr = copy[0].buffer;
    
    while (ptr[0] != '\0')
    {
	/* advance to the next occurence of the first character of 'old' */
	while ((ptr[0] != first) && (ptr[0] != '\0'))
	{
	    /* Append one character to str */	    
	    buf_length = str[0].length++;
	    str[0].buffer[buf_length] = ptr[0];
	    str[0].buffer[buf_length+1] = '\0';

	    characters_left_to_process--;
	    ptr++;
	}

	if (characters_left_to_process >= old_length)
	{
	    /* Check if the token found is equal to 'old' */

	    /* if (!strncmp (old, ptr, old_length)) */
	    if (ss_fast_c_string_length_compare(old, ptr, old_length) == 0)
	    {
		if (new_length > 0)
		{
		    ss_fast_append_c_string_length (str, new, new_length);
		}
		
		ptr += old_length;
		characters_left_to_process -= old_length;
	    }
	    else
	    {
		/* Append one character to str */
		buf_length = str[0].length++;
		str[0].buffer[buf_length] = ptr[0];
		str[0].buffer[buf_length+1] = '\0';

		characters_left_to_process--;
		ptr++;
	    }
	    
	} else {
	    /* Append one character to str */
	    buf_length = str[0].length++;
	    str[0].buffer[buf_length] = ptr[0];
	    str[0].buffer[buf_length+1] = '\0';

	    /* add the characters left to process */
	    ss_append_c_string (str, ptr);

	    characters_left_to_process--;
	    ptr++;
	    break;
	}
    }
    
    ss_destroy_string(copy);
}

SafeString *
ss_string_by_deleting_last_path_component(str)
	SafeString *str;
{
    char *str_buf;
    int i, len;
    SafeString *result;

    len = ss_string_length(str);

    str_buf = (char *)malloc(len + 1);
    strcpy(str_buf, ss_string_value(str));
    
    if (len > 1)
    {
	str_buf[len-1] = '\0';
    
	for (i = len-2; i > 0; i--)
	{
	    if (str_buf[i] == '/')
	    {
		str_buf[i] = '\0';
		break;
	    }
	    str_buf[i] = '\0';
	}
    }

    if ((str_buf[0] != '/') && (str_buf[0] != '\0') && (str_buf[1] == '\0'))
    {
	safe_c_string_free(str_buf);
	return ss_create_string_with_c_string("");
    }

    result = ss_create_string_with_c_string(str_buf);
    safe_c_string_free(str_buf);
    return result;
}

SafeString *
ss_string_by_deleting_path_extension(str)
	SafeString *str;
{
    char *str_buf;
    char *ptr;
    int len;

    /* Work on a copy of the buffer */
    
    str_buf = (char *)malloc(ss_string_length(str) + 1);
    strcpy(str_buf, ss_string_value(str));

    if (str_buf != NULL)
    {
	if (ptr = strrchr(str_buf, '.'))
	{
	    ptr[0] = '\0';
	}
	
	if (((len = strlen(str_buf)) > 1) && (str_buf[len - 1] == '/'))
	{
	    str_buf[len-1] = '\0';
	}
	
	return ss_create_string_with_c_string(str_buf);
	
    }
    else
    {
	return NULL;
    }    
}


/* Trim leading and trailing blanks */

void
ss_trim_string(str)
	SafeString *str;
{
    int i, j, len;
    char *buf;
    char *ptr;
    
    if ((len = ss_string_length(str)) < 1)
    {
	return;
    }

    /* Work with a copy of the string buffer */
    
    buf = (char *)malloc(len + 1);
    strcpy(buf, ss_string_value(str));
    
    if (buf != NULL)
    {
	ptr = buf + strlen(buf) - 1;

	while (*ptr == ' ')
	{
	    *ptr = '\0';
	    ptr--;
	}

        /* get position of rightmost blank */
	for(i=0; i<len; i++)
	{
	    if (buf[i] != ' ')
	    {
		break;
	    }
	}

	if (i)
	{
	    /* move string to the left to eliminate leading blanks */
	    for (j=i; j<len; j++)
	    {
		buf[j-i] = buf[j];
	    }
	    buf[len-i] = '\0';
	}

	ss_copy_c_string(str, buf);
	safe_c_string_free(buf);
    }
}

/*****************************************************************
 * _safe_c_string_copy() makes a copy of a string and returns    *
 * the new string.                                               *
 *                                                               *
 * This function replaces strdup() because:                      *
 *      Not all Unixes implement strdup                          *
 *      Some strdup implementations crash on a NULL argument     *
 *****************************************************************/

char *
safe_c_string_copy(s)
	char *s;
{
    char *s1;
    
    if (s == NULL)
    {
	return(NULL);
    }
    
    s1 = (char *)malloc(strlen(s) + 1);
    
    if (s1 == NULL)
    {
	return(NULL);
    }
    
    (void)strcpy(s1,s);
    
    return(s1);
}

void
safe_c_string_free(s)
	char *s;
{
    if (s != NULL)
    {
	free(s);
    }
}



int sock;

SafeString *query;

/**********************************************************************/
/* File Id:                     strparse                              */
/* Author:                      Stan Milam.                           */
/* Date Written:                20-Feb-1995.                          */
/* Description:                                                       */
/*     The str_parse() function is used to extract fields from de-    */
/*     limited ASCII records. It is designed to deal with empty fields*/
/*     in a logical manner.                                           */
/*                                                                    */
/* Arguments:                                                         */
/*     char **str       - The address of a pointer which in turn      */
/*                        points to the string being parsed. The      */
/*                        actual pointer is modified with each call to*/
/*                        point to the beginning of the next field.   */
/*     char *delimiters - The address of the string containing the    */
/*                        characters used to delimit the fields within*/
/*                        the record.                                 */
/*                                                                    */
/* Return Value:                                                      */
/*     A pointer of type char which points to the current field in the*/
/*     parsed string.  If an empty field is encountered the address   */
/*     is that of an empty string (i.e. "" ). When there are no more  */
/*     fields in the record a NULL pointer value is returned.         */
/*                                                                    */
/**********************************************************************/

char *
str_parse( str, delimiters )
	char 	**str,
		*delimiters;
{
    char *head, *tail, *rv;

    if ( *str == NULL || **str == '\0' )
    {
        rv = NULL;
    }
    else if ( delimiters == NULL || *delimiters == '\0' )
    {
        rv = NULL;
    }
    else
    {
        rv = head = *str;
        if (( tail = strpbrk( head, delimiters ) ) == NULL)
            *str = head + strlen(head);
        else {
            *tail = '\0';
            *str = tail + 1;
        }
    }
    return rv;
}


char *
str_esc_parse( str, delimiters, esc )
	char 	**str,
		*delimiters,
		esc;
{
    	char *head, *tail, *rv, *cp, *cp2;
	int	needEscape = 0;

    	if ( *str == NULL || **str == '\0' )
    	{
        	rv = NULL;
    	}
    	else if ( delimiters == NULL || *delimiters == '\0' )
    	{
        	rv = NULL;
    	}
    	else
    	{
        	rv = head = *str;
		cp = head;
		while((tail = strpbrk( cp, delimiters )))
		{
			if (tail > head)
			{
				if (*(tail - 1) != esc)
					break;
			}
			needEscape++;
			cp = tail + 1;
		}
        	if (tail == NULL)
            		*str = head + strlen(head);
        	else 
		{
            		*tail = '\0';
            		*str = tail + 1;
        	}
    	}
	if (needEscape)
	{
		cp=cp2=rv;
		while(*cp)
		{
			if (*cp == esc)
			{
				cp++;
			}
			*cp2++ = *cp++;
		}
		*cp2 = 0;
	}
    	return rv;
}

/*****************************************************************
 * _abort_import() closes the client, then exits the program     *
 *****************************************************************/

void
abort_import(exitCode)
	int exitCode;
{
    if (sock > -1)
    {
	msqlClose(sock);
    }
    exit(exitCode);
}

/*****************************************************************
 * alarm() prints an error message then calls abort_import()     *
 *****************************************************************/

void
alarm_msql()
{
    fprintf(stderr, "msql-import error: %s\n", msqlErrMsg);
    abort_import(-1);
}

void
alarm_msg(message)
	char *message;
{
    fprintf(stderr, "%s\n", message);
    abort_import(-1);
}

/*****************************************************************
 * Put a backslash in front of single quote, parentheses,        *
 * backslashes, and other special characters in the string. This *
 * is needed for inserting strings containing these characters   *
 * in a relational database.                                     *
 *****************************************************************/

#define BACKSLASH_CHAR 92

char *
backslashify_special_chars(s)
	char *s;
{
    char *str, *ptr;
    int i, j, index, len;

    static char special_chars[] ={ '\'', '\"', 0}; 
    int current = 0;
    
    if ((!s) ||
	(strlen(s) == 0) ||
	((strchr(s, '\'') == NULL) &&
	 (strchr(s, ')') == NULL) &&
	 (strchr(s, '\\') == NULL) &&
	 (strchr(s, '(') == NULL)))
    {
	return safe_c_string_copy(s);
    }

    str = safe_c_string_copy(s);
    
    /*
     * Process the backslash character first.  We can't use strchr
     * on strings looking for a backslash
     */

    i = 0;
    
    while (str[i] != '\0')
    {
	if (str[i] == BACKSLASH_CHAR)
	{
	    len = strlen(str) + 1;
	    str = (char *)realloc(str, len+2);

	    /* move the string to the right */
	    
	    j = len-1;

	    while (j>i)
	    {
		str[j+2] = str[j--];
	    }
	    str[len+1] = '\0';

	    /* insert 2 backslashes in the gap */

	    str[i] = BACKSLASH_CHAR;
	    str[i+1] = BACKSLASH_CHAR;
	    
	    i+=3; /* this is not a mistake */
	}
	
	if (str[i] != '\0')
	{
	    i++;
	}
    }
    
    /* Do the other special characters */
    
#if 1
    while(special_chars[current])
    {
	index = -2;

	while ((ptr = strchr(str+index+2, special_chars[current])) != NULL)
	{
	    len = strlen(str);
	    index = ptr-str;
	    str = (char *)realloc(str, len+2);
	    
	    for (i=len; i>=index; i--)
	    {
		str[i+1] = str[i];
	    }
	    
	    str[index] = BACKSLASH_CHAR;
	}
	current++;
    }

#else /* the following chunk of code is buggy */

    while(special_chars[current])
    {
	index = -2;

	while ((ptr = strchr(str+index+2, special_chars[current])) != NULL)
	{
	    len = strlen(str)+1;
	    index = ptr-str;
	    str = (char *)realloc(str, len+1);
	    
	    for (i=len+1; i>=index; i--)
	    {
		str[i+1] = str[i];
	    }
	    
	    str[index] = BACKSLASH_CHAR;
	}
	current++;
    }
#endif
    return str;
}

/*****************************************************************
 * _datatypes() returns an array of the field datatypes.         *
 *                                                               *
 * msql-import uses this information to determine whether or     *
 * not to enclose the value to be inserted between quotes, and   *
 * msql-import will validate the data depending on the datatype. *
 *                                                               *
 * Algorithm:                                                    *
 *                                                               *
 *   FOR EACH field IN fields                                    *
 *       find its definition and add its type to the result      *
 *****************************************************************/

int *
datatypes(table, fields)
	char *table, *fields;
{
    int fieldCount = 0;
    char *tableCopy = safe_c_string_copy(table);
    int *types = (int *)malloc((IMPORT_MAX_FIELDS+1) * sizeof(int));
    m_result *result = msqlListFields(sock, tableCopy);
    m_field *field;
    char *token;
    char *fieldsCopy;
    char **ptr;
    int done = 0;
    int found = 0;
    static char *buf[] = {"unknown", "int", "char", "real", "ident", "null"};
    char **str = buf;
    
    fieldsCopy = safe_c_string_copy(fields);
    ptr = &fieldsCopy;

    free(tableCopy);

    if (fieldsCopy != NULL)
    {
	while (!done)
	{
	    if ((token = str_parse(ptr, ",")) != NULL)
	    {
		msqlFieldSeek(result, 0);
		found = 0;
		
		while((!found) && (field = msqlFetchField(result)))
		{
	            if (field->type > LAST_REAL_TYPE)
			continue;
		    if (strcmp(field->name, token) != 0)
			continue;
		    printf("Column: %s ", field->name);
		    switch(field->type)
		    {
			case INT_TYPE:
				printf(" Type: int\n");
				break;
			case CHAR_TYPE:
				printf(" Type: char\n");
				break;
			case REAL_TYPE:
				printf(" Type: real\n");
				break;
			case TEXT_TYPE:
				printf(" Type: text\n");
				break;
			default:
				printf("\n\nERROR : Unknown Type!\n\n");
				exit(1);
		    }
		    types[fieldCount++ +1] = field->type;
		    found = 1;
		}
		
		if (!found)
		{
		    alarm_msg("Field definition not found. Exiting.");
		}
	    }
	    else
	    {
		done = 1;
	    }
	}
    }
    else
    {
	msqlFieldSeek(result, 0);

	while ((field = msqlFetchField(result)) != NULL)
	{
	    if (field->type > LAST_REAL_TYPE)
		continue;
	    printf("Column: %s\t", field->name);
	    switch(field->type)
	    {
		case INT_TYPE:
			printf(" Type: int\n");
			break;
		case CHAR_TYPE:
			printf(" Type: char\n");
			break;
		case REAL_TYPE:
			printf(" Type: real\n");
			break;
		case TEXT_TYPE:
			printf(" Type: text\n");
			break;
		default:
			printf("\n\nERROR : Unknown Type!\n\n");
			exit(1);
	    }

	    types[fieldCount++ + 1] = field->type;
	}
    }
    
    msqlFreeResult(result);

#if 0
    if (fieldsCopy!= NULL)
    {
	free(fieldsCopy);
    }
#endif

    /* Put the number of fields in the first position in the array */
    
    types[0] = fieldCount;
    
    printf("\n");
    return types;
}

/*****************************************************************
 * _get_record reads one row from the data file and returns it   *
 *****************************************************************/

#define GET_REC_BLOCKSIZE 8192

char *
get_record(fp, record_delimiter, record)
	FILE *fp; 
	char record_delimiter; 
	SafeString *record;
{
    char c;
    int i = 0;

    ss_set_string_capacity(record, GET_REC_BLOCKSIZE);
    ss_copy_c_string(record, "");
    
    for ( ; ; )
    {
	if (((c = getc(fp)) == EOF) || (c == record_delimiter))
	{
	    break;
	}
	else
	{
	    /* add the block to the record string */
	    ss_append_character(record, c);
	    i++;
	}
    }

    if (i)
    {
	return (char *)record[0].buffer;
    } else
    {
	return NULL;
    }
}

/*****************************************************************
 * _import_file opens the flat file, reads it one record at a    *
 * time, and imports it into the specified Mini SQL table        *
 *****************************************************************/

void
import_file(table,fieldDel,rowDel,fields, esc)
	char 	*table,
	    	*fieldDel,
	    	*rowDel,
		*fields,
		*esc;
{
    FILE *fp;
    int recordCount = 0; /* Number of records read from the flat file */
    int *data_types = datatypes(table, fields);
    int recs = 0; /* Number of records successfully inserted */
    SafeString *record;
    
    fp = stdin;
    record = ss_create_string();
    query = ss_create_string();
    ss_set_string_capacity(query, GET_REC_BLOCKSIZE);
    
    while (get_record(fp, rowDel[0], record) != NULL)
    {
	recs += insert_record(table,
			      record[0].buffer,
			      fields,
			      data_types,
			      recordCount++,
			      fieldDel,
			      esc);
    }

    safe_c_string_free(fieldDel);
    safe_c_string_free(rowDel);
    safe_c_string_free(table);
    ss_destroy_string(record);
    ss_destroy_string(query);
    
    if (recs > 1)
    {
	fprintf(stdout, "%i rows successfully imported.  ", recs);
    }
    else
    {
	if (recs == 1)
	{
	    fprintf(stdout, "1 row was successfully imported.  ");
	}
	else
	{
	    fprintf(stdout, "No row was successfully imported.  ");
	}
    }

    if (recordCount-recs > 1)
    {
	fprintf(stdout, "%i rows were rejected.\n\n", recordCount-recs);
    }
    else
    {
	if (recordCount-recs == 1)
	{
	    fprintf(stdout, "1 row was rejected.\n\n");
	}
	else
	{
	    fprintf(stdout, "No row was rejected.\n\n");
	}
    }
}

/*****************************************************************
 * _insert_record sends the INSERT statement to the msql server  *
 * Returns 1 if record was inserted successfully, 0 otherwise.   *
 *****************************************************************/

int
insert_record(table, record, fields, types, recordCount, fieldDel,esc)
	char *table,
	      *record,
	      *fields;
	int *types,
	     recordCount;
	char *fieldDel,
	    *esc;
{
    char **ptr = &record;
    char *token;
    char *value;
    char delimiter[2];
    int done = 0;
    int fieldNumber = 1;
    int fieldsCount = types[0]; /* The number of values to be inserted */
    
    sprintf(delimiter, "%s", fieldDel);

    ss_copy_c_string(query, "INSERT INTO ");
    ss_append_c_string(query, table);
    
    if ((fields == NULL) || (strlen(fields) < 2))
    {
	ss_append_c_string(query, " VALUES(");
    }
    else
    {
	ss_append_c_string(query, " (");
	ss_append_c_string(query, fields);
 	ss_append_c_string(query, ") VALUES(");
    }
    
    if((record != NULL) && (token = str_esc_parse(ptr,delimiter,*esc)))
    {
	switch(types[1])
	{
	  case CHAR_TYPE:
	  case TEXT_TYPE:
	    value = backslashify_special_chars(token);
	    ss_append_character(query, '\'');
	    ss_append_c_string(query, value);
	    ss_append_c_string(query,"',");
	    
	    if (value != NULL)
	    {
		free(value);
	    }
	    
	    break;

	  case NULL_TYPE:
	    ss_append_c_string(query, "NULL,");
	    break;

	  default:
	    ss_append_c_string(query, token);
	    ss_append_character(query,',');
	}
    }
    else
    {
	return 0;
    }

    while (!done)
    {
	if ((token = str_esc_parse(ptr, delimiter, *esc)) != NULL)
	{
	    switch(types[fieldNumber++ + 1])
	    {
	      case CHAR_TYPE:        
	      case TEXT_TYPE:        
		value = backslashify_special_chars(token);
		ss_append_character(query, '\'');
		ss_append_c_string(query, value);
		ss_append_c_string(query,"',");

		safe_c_string_free(value);

		break;
		
	      case NULL_TYPE:
		ss_append_c_string(query, "NULL,");
		break;
		
	      default:
		ss_append_c_string(query, token);
		ss_append_character(query, ',');
	    }
	}
	else
	{
	    done = 1;
	}
    }

    /*
     * Add NULL values if the number of values read in the record
     * is lower than the number of field specified in the command
     * line or if the number of values in the record is lower than
     * the number of fields in the table if the field names weren't
     * specified on the command line
     */
    
    while (fieldNumber < fieldsCount)
    {
	ss_append_c_string(query, "NULL,");
	fieldNumber++;
    }

    /* Remove the last comma and the record delimiter ('\n') */
    query[0].buffer[strlen(query[0].buffer)-1] = '\0';
    query[0].length--;
    
    ss_append_character(query, ')');
    if (verbose)
    	printf("Query : %s\n\n", query[0].buffer);

    if (msqlQuery(sock, query[0].buffer) == -1)
    {
	fprintf(stderr, "msql-import: could not import record %i: %s\n",
		recordCount, msqlErrMsg);
	fprintf(stdout, "Query : %s\n\n", query[0].buffer);
	return 0;
    }
    
    return 1;
}

/*****************************************************************
 *  row_length verifies that table_name exists in the database,  *
 *  and returns the row length in bytes (including control bytes)*
 *****************************************************************/

int
row_length(table_name)
	char *table_name;
{
    m_result *res;
    m_field *curField;
    int len = 1;
    char *tableCopy = safe_c_string_copy(table_name);
    
    /* Verify that the table_name argument is valid */

    if (table_name == NULL)
    {
	fprintf(stderr, 
	"msql-import in row_length(), invalid table_name (NULL).  Exiting.\n");
	exit(1);
    }
    
    res = msqlListFields(sock,tableCopy);

    free(tableCopy);
    
    if (!res)
    {
	fprintf(stderr, 
		"msql-import error : Unable to get the fields in table %s.\n", 
		table_name);
	fprintf(stderr, "Exiting.\n");
	exit(1);
    }
    
    while((curField = msqlFetchField(res)))
    {
	len += curField->length + 1;
    }

    msqlFreeResult(res);

    if (len<3)
    {
	fprintf(stderr, 
		"msql-import: error getting table definition. Exiting.\n");
	abort_import(1);
    }
    
    return len;
}

char *
format_delimiter(str)
	char *str;
{
    unsigned char nl = (unsigned char)10;
    unsigned char tab = (unsigned char)9;
    char *result = (char *)NULL;
    
    if (!str)
    {
	return (char *)NULL;
    }
    
    if (strcmp(str, "\\n") == 0)
    {
	result = malloc(2);
	result[0] = nl;
	result[1] = '\0';
    }
    else
    {
	if (strcmp(str, "\\t") == 0)
	{
	    result = malloc(2);
	    result[0] = tab;
	    result[1] = '\0';
	}
	else
	{
	    result = (char *)malloc(strlen(str) + 1);
	    strcpy(result, str);
	}
    }
    return result;
}

void
printHelp()
{
    static char text[] =
    {
"  msql-import loads the contents of an ASCII delimited flat file \
into an\n  existing MiniSQL table.  It automatically performs the \
type conversions, and\n  validates the data.\n\n  msql-import is \
invoked as follows:\n\n    msql-import -h host -d database -t table \
-c column_delimiter \\\n\
    -r record_delimiter -i input_datafile \
-f \"[field [,field...]]\"\n\n\
               host: hostname of the msql server\n\
           database: the Mini SQL database name\n\
              table: the table in which to load the data\n\
   column_delimiter: the character used to delimit fields within a\
 record\n\
   record_delimiter: the character used to delimit records\n\
     input_datafile: contains the data to be imported\n\
             fields: import the data in those only (optional)\n\n\
  Example:\n\n    msql-import -h zeus -d db -t table -c \\t \
-r \\n -i /tmp/file \\\n \
    -f \"client_id,name,address\"\n\n\
  If the fields are not specified, then all fields in the table \
will be filled\n  with the data contained in the flat file, in \
order of appearance.\n\n\
  msql-import was written by Pascal Forget <pascal@wsc.com>.\n\n"
    };
	
    fprintf(stdout, "\n  msql-import %s help:\n\n", MSQL_IMPORT_VERSION);
    fprintf(stdout, text);
}

void
printUsage()
{
    	printf(
	     "\nusage: msqlimport [-v] [-h host] [-f conf] [-s C] [-e C] [-c cols] db table\n");
	printf("\n\tImports an ASCII export file.\n\n");
	printf("\t-v\t\tRun in verbose mode\n");
	printf("\t-h\t\tmSQL Server Host\n");
	printf("\t-s Char\t\tUse the character Char as the separation character\n");
	printf("\t\t\tDefault is a comma.\n");
	printf("\t-e Char\t\tUse the specifed Char as the escape character\n");
	printf("\t\t\tDefault is \\\n");
	printf("\t-c col,col...\tSpecify a list of columns for the imported data\n");
    exit (0);
}

void
printVersion()
{
    fprintf(stdout, "%s\n", MSQL_IMPORT_VERSION);
}

/*****************************************************************
 * _main() connects to the msql server, then imports the data    *
 *****************************************************************/

void
main(argc, argv)
	int 	argc; 
	char 	*argv[];
{
    	int 	len;
    	char 	*hostname = NULL;
    	char 	*rec_delimiter = "\n";
    	char 	*col_delimiter = NULL;
    	char 	*esc_char = NULL;
    	char 	*database = NULL;
    	char 	*table = NULL;
    	char 	*fields = NULL;
    	char 	*confFile = NULL;
    	int  	c,
		errFlag = 0;
        extern  char *optarg;
        extern  int optind;


        /*
        ** Check out the args
        */
        while((c=getopt(argc,argv,"h:f:s:e:c:v"))!= -1)
        {
                switch(c)
                {
                        case 'h':
                                if (hostname)
                                        errFlag++;
                                else
                                        hostname = optarg;
                                break;

                        case 'f':
                                if (confFile)
                                        errFlag++;
                                else
                                        confFile = optarg;
                                break;

                        case 's':
                                if (col_delimiter)
                                        errFlag++;
                                else
                                        col_delimiter = optarg;
                                break;

                        case 'e':
                                if (esc_char)
                                        errFlag++;
                                else
                                        esc_char = optarg;
                                break;

                        case 'c':
                                if (fields)
                                        errFlag++;
                                else
                                        fields = optarg;
                                break;

                        case 'v':
                                if (verbose)
                                        errFlag++;
                                else
                                        verbose++;
                                break;
		}
	}
	if (errFlag)
		printUsage();

	if (argc != optind + 2)
		printUsage();

	database = argv[optind++];
	table = argv[optind++];

        /*
        ** If we have a config file override the default config
        */
        if (confFile)
        {
                msqlLoadConfigFile(confFile);
        }


    	/* 
	** Print the arguments that were interpreted 
	*/
	if (!col_delimiter)
		col_delimiter = (char *)strdup(",");
	if (!esc_char)
		esc_char = (char *)strdup("\\");
   
	printf("\nImporting into '%s:%s' using :-\n\n",database,table); 
    	printf ("hostname         =  %s\n", hostname? hostname : "localhost");
	printf ("column delimiter =  %s\n", col_delimiter);
	printf ("escape character =  %s\n", esc_char);
	printf ("columns          =  %s\n", fields?fields:"all columns");
    	printf("\n");

    	/* 
	** Connect to the msql server 
	*/

	sock = msqlConnect(hostname);
    	if (sock == -1)
    	{
		fprintf(stderr, 
			"msql-import: error connecting to host. Exiting.\n");
		abort_import(1);
    	}
    
    	if (msqlSelectDB(sock, database) == -1)
    	{
		fprintf(stderr, 
			"msql-import: error opening database. Exiting.\n");
		abort_import(1);
    	}
    
    	/* ssk test if table exists */
    	len = row_length( table );
   	 
    	import_file(table,
		format_delimiter(col_delimiter),
		format_delimiter(rec_delimiter),
		fields,
		esc_char);

    	abort_import(0); /* close socket and quit, we're done. */
}
