/*
** msqlimport.c
**
** The basis of this software was originally written by Pascal Forget,
** a long-time member and contributor in the mSQL community.  This code
** been rewritten from scratch to look more like the other mSQL code
** and to imrpove reliability.
**
*/

#include <stdio.h>
#include <fcntl.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <errno.h>
#include <time.h>

#ifdef WIN32
#  include <winsock.h>
#endif

#include <common/portability.h>
#include "msql_priv.h"
#include "msql.h"



/* Exit codes */

#define EX_USAGE 1
#define EX_MSQLERR 2
#define EX_CONSCHECK 3

char	usage[] = "\n\n\
usage: 	msqlimport [-h host] [-v] [-s Char] [-e Char] [-q Char] database table\n\n\
	Imports a formatted ASCII file from the standard input.\n\n\
	-v		Verbose\n\
	-s Char		Use the character Char as the separation character\n\
			Default is a comma.\n\
	-q Char		Use the specifed Char as the quote character\n\
			Default is none\n\
	-e Char		Use the specifed Char as the escape character\n\
			Default is \\\n\n";

int	verbose = 0;
int	sock = -1;
int	lineNo = 0;
int	recNo = 0;
char	buffer[5 * 1024];



/*
** DBerror -- prints mSQL error message and exits the program.
*/

void DBerror()
{
	if (lineNo > 0)
		fprintf(stderr, "\nmSQL error at line %d: %s\n", 
			lineNo, msqlErrMsg);
	else
		fprintf(stderr, "\nmSQL error: %s\n", msqlErrMsg);
	if ( sock != -1 )
		msqlClose(sock);
	exit(EX_MSQLERR);
}



void fatal(msg)
	char	*msg;
{
	fprintf(stderr, "Fatal error at line %d : %s\n\n",lineNo, msg);
	exit(1);
}


char *escapeCharField(str)
	char	*str;
{
	register char	*cp,
			*cp2,
			*tmp;
	int	numQuotes;
	static	char	buffer[5 * 1024]="";

	if (!str)
		return(buffer);
	cp = str;
	numQuotes = 0;
	while((cp = (char *)index(cp,'\'')))
	{
		numQuotes++;
		cp++;
	}
	cp = str;
	while((cp = (char *)index(cp,'\\')))
	{
		numQuotes++;
		cp++;
	}

	if (numQuotes)
	{
		cp2 = buffer;
		cp = str;
		while(*cp)
		{
			if (*cp == '\'' || *cp == '\\')
				*cp2++='\\';
			*cp2++ = *cp++;
		}
		*cp2 = '\0';
		return(buffer);
	}
	else
	{
		return(str);
	}
}


/*
** dbConnect -- connects to the host and selects DB.
**            Also checks whether the tablename is a valid table name.
*/

void dbConnect(host,database)
	char	*host,
		*database;
{
	if (verbose)
	{
		fprintf(stderr, "Connecting to %s...\n", 
			host ? host : "localhost");
	}
	sock = msqlConnect(host);
	if ( sock == -1 )
		DBerror();
	if ( verbose )
		fprintf(stderr, "Selecting data base %s...\n", database);
	if ( msqlSelectDB(sock, database) == -1 )
		DBerror();
}



/*
** dbDisconnect -- disconnects from the host.
*/

void dbDisconnect(host)
	char	*host;
{
	if (verbose)
	{
		fprintf(stderr, "Disconnecting from %s...\n", 
			host ? host : "localhost");
	}
	msqlClose(sock);
}




int getFieldDefs(table,fDefs)
	char	*table;
	int	*fDefs;
{
	int	numFields;
	m_result *res;
	m_field	*curField;

	printf("\nImporting into table '%s'\n",table);
	res = msqlListFields(sock,table);
	if (!res)
	{
		DBerror();
	}
	numFields = 0;
	curField = msqlFetchField(res);
	while(curField)
	{
		if (curField->type > LAST_REAL_TYPE)
			break;
		fDefs[numFields] = curField->type;
		numFields++;
		printf("Field %d is '%s' of type %s\n",numFields,
			curField->name, msqlTypeNames[curField->type]);
		curField = msqlFetchField(res);
	}
	printf("\n");
	msqlFreeResult(res);
	return(numFields);
}



char *generateQuery(table, numFields, fDefs, fVals)
	char	*table;
	int	numFields,
		*fDefs;
	char	**fVals;
{
	static	char qBuf[5 * 1024];
	int	count;

	snprintf(qBuf,sizeof(qBuf),"insert into %s values ( ", table);
	count = 0;
	while(count < numFields)
	{
		switch(fDefs[count])
		{
			case CHAR_TYPE:
			case TEXT_TYPE:
			case DATE_TYPE:
			case TIME_TYPE:
				strcat(qBuf,"'");
				strcat(qBuf,escapeCharField(fVals[count]));
				strcat(qBuf,"'");
				break;

			default:
				if (*(fVals[count]) == 0)
					strcat(qBuf,"NULL");
				else
					strcat(qBuf,fVals[count]);
				break;
		}
		count++;
		if (count < numFields)
			strcat(qBuf,", ");
	}
	strcat(qBuf,")");
	return(qBuf);
}

splitLine(numFields, fVals, line, esc, sep, quote)
	int	numFields;
	char	*fVals[],
		*line,
		esc,
		sep,
		quote;
{
	int 	count = 0,
		qState = 0;
	char	*cp1, *cp2;

	cp1 = cp2 = line;
	while(*cp2)
	{
		if (quote)
		{
			if (qState == 0 && *cp2 == quote)
			{
				cp2++;
				qState=1;
				continue;
			}
			if (*cp2 == quote && quote == esc && qState == 1)
			{
				if (*(cp2+1) == esc)
				{
					cp2++;
					cp2++;
					continue;
				}
			}
			if (*cp2 == quote)
			{
				cp2++;
				qState = 2;
				continue;
			}
		}

		if (*cp2 == esc)
		{
			cp2++;
			if (*cp2 == 0)
			{
				fatal("Invalid character escape");
			}
			cp2++;
			continue;
		}
		if (*cp2 == sep)
		{
			if (qState == 1)
			{
				cp2++;
				continue;
			}
			if (qState == 2)
			{
				*(cp2-1) = 0;
				cp1++;
			}
			*cp2 = 0;
			fVals[count] = cp1;
			count++;
			qState = 0;
			cp1 = cp2 = cp2+1;
			continue;
		}
		cp2++;
	}
	if (qState == 2)
	{
		*(cp2-1) = 0;
		cp1++;
	}
	*cp2 = 0;
	fVals[count] = cp1;
	count++;
	if (count != numFields)
	{
		fatal("Invalid number of data fields for specified table");
	}
}



unescapeData(numFields,fVals,esc)
	int	numFields;
	char	**fVals,
		esc;
{
	char	*cp1,
		*cp2;
	int	count;

	count = 0;
	while(count < numFields)
	{
		cp1 = cp2 = fVals[count];
		while(*cp2)
		{
			if (*cp2 == esc)
			{
				cp2++;
			}
			*cp1 = *cp2;
			if (*cp2 == 0)
				break;
			cp1++;
			cp2++;
		}
		*cp1 = 0;
		count++;
	}
}



processInput(table, numFields,fDefs,esc,sep, quote)
	char	*table;
	int	numFields,
		*fDefs;
	char	esc,
		sep,
		quote;
{
	char	line[5 * 1024],
		*fVals[MAX_FIELDS];
	int	count;

	fgets(line,sizeof(line), stdin);
	while(!feof(stdin))
	{
		write(fileno(stdout),".",1);
		lineNo++;
		line[strlen(line) - 1] = 0;
		if (*line == 0)
		{
			fgets(line,sizeof(line), stdin);
			continue;
		}
		splitLine(numFields, fVals, line, esc, sep, quote);
		unescapeData(numFields,fVals,esc);

		if (msqlQuery(sock,
			generateQuery(table, numFields, fDefs, fVals)) < 0)
		{
			DBerror();
		}
		recNo++;
		fgets(line,sizeof(line), stdin);
	}
}


int main(argc, argv)
	int	argc;
	char	*argv[];
{
	int	c,
		errFlag = 0,
		fDefs[MAX_FIELDS],
		numFields;
	char	*host = NULL,
		*database = NULL,
		*table = NULL,
		sep=0,
		esc=0,
		quote=0;
	extern	char *optarg;
	extern	int optind;

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

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

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

			case 'q':
				if (quote)
					errFlag++;
				else
					quote = *optarg;
				break;

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

			case '?':
				errFlag++;
		}
	}
	if (errFlag)
	{
		fprintf(stderr,usage);
		exit(EX_USAGE);
	}

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

	if (!database || !table)
	{
		fprintf(stderr,usage);
		exit(EX_USAGE);
	}
	

	if (!sep)
		sep = ',';
	if (!esc)
		esc = '\\';

	dbConnect(host,database);
	numFields = getFieldDefs(table,fDefs);
	processInput(table,numFields,fDefs,esc,sep,quote);
	printf("\n\nDone.  %d records processed.\n\n",recNo);
	dbDisconnect(host);
	exit(0);
}
