/*
 * Code to test ODBC implementation.
 *  - David Fraser, Abelon Systems 2003.
 */

#include "common.h"

#ifdef DEBUG
# define AB_FUNCT(x)  do { printf x; printf("\n"); } while(0)
# define AB_PRINT(x)  do { printf x; printf("\n"); } while(0)
#else
# define AB_FUNCT(x)
# define AB_PRINT(x)
#endif
#define AB_ERROR(x)   do { printf("ERROR: "); printf x; printf("\n"); } while(0)

typedef bool DbTestFn(void);

static bool RunTests(void);

typedef struct
{
	DbTestFn *testFn;
	const char *description;
} DbTestEntry;

/*
 * Test that makes a parameterized ODBC query using SQLPrepare and SQLExecute
 */
static bool
TestRawODBCPreparedQuery(void)
{
	SQLTCHAR *queryString;
	SQLLEN lenOrInd = 0;
	SQLSMALLINT supplierId = 4;
	int count;

	AB_FUNCT(("TestRawODBCPreparedQuery (in)"));

	/* INIT */

	odbc_connect();

	/* MAKE QUERY */

	odbc_command("CREATE TABLE #Products ("
		"ProductID int NOT NULL ,"
		"ProductName varchar (40) ,"
		"SupplierID int NULL ,"
		"CategoryID int NULL ,"
		"QuantityPerUnit varchar (20)  ,"
		"UnitPrice money NULL ,"
		"UnitsInStock smallint NULL ,"
		"UnitsOnOrder smallint NULL ,"
		"ReorderLevel smallint NULL ,"
		"Discontinued bit NOT NULL "
		") "
		"INSERT INTO #Products(ProductID,ProductName,SupplierID,CategoryID,QuantityPerUnit,UnitPrice,UnitsInStock,UnitsOnOrder,ReorderLevel,Discontinued) VALUES(9,'Mishi Kobe Niku',4,6,'18 - 500 g pkgs.',97.00,29,0,0,1) "
		"INSERT INTO #Products(ProductID,ProductName,SupplierID,CategoryID,QuantityPerUnit,UnitPrice,UnitsInStock,UnitsOnOrder,ReorderLevel,Discontinued) VALUES(10,'Ikura',4,8,'12 - 200 ml jars',31.00,31,0,0,0) "
		"INSERT INTO #Products(ProductID,ProductName,SupplierID,CategoryID,QuantityPerUnit,UnitPrice,UnitsInStock,UnitsOnOrder,ReorderLevel,Discontinued) VALUES(74,'Longlife Tofu',4,7,'5 kg pkg.',10.00,4,20,5,0) "
		"INSERT INTO #Products(ProductID,ProductName,SupplierID,CategoryID,QuantityPerUnit,UnitPrice,UnitsInStock,UnitsOnOrder,ReorderLevel,Discontinued) VALUES(11,'Queso Cabrales',5,4,'1 kg pkg.',21.00,22,30,30,0) "
		"INSERT INTO #Products(ProductID,ProductName,SupplierID,CategoryID,QuantityPerUnit,UnitPrice,UnitsInStock,UnitsOnOrder,ReorderLevel,Discontinued) VALUES(12,'Queso Manchego La Pastora',5,4,'10 - 500 g pkgs.',38.00,86,0,0,0)");
	while (SQLMoreResults(odbc_stmt) == SQL_SUCCESS)
		continue;

	queryString = T("SELECT * FROM #Products WHERE SupplierID = ?");

	CHKBindParameter(1, SQL_PARAM_INPUT, SQL_C_SSHORT, SQL_INTEGER, 0, 0, &supplierId, 0, &lenOrInd, "S");

	CHKPrepare(queryString, SQL_NTS, "S");

	CHKExecute("S");

	count = 0;

	while (SQLFetch(odbc_stmt) == SQL_SUCCESS) {
		count++;
	}
	AB_PRINT(("Got %d rows", count));

	if (count != 3) {
		/*
		 * OK - so 3 is a magic number - it's the number of rows matching
		 * this query from the MS sample Northwind database and is a constant.
		 */
		AB_ERROR(("Expected %d rows - but got %d rows", 3, count));
		AB_FUNCT(("TestRawODBCPreparedQuery (out): error"));
		return false;
	}

	/* CLOSEDOWN */

	odbc_disconnect();

	AB_FUNCT(("TestRawODBCPreparedQuery (out): ok"));
	return true;
}

/*
 * Test that makes a parameterized ODBC query using SQLExecDirect.
 */
static bool
TestRawODBCDirectQuery(void)
{
	SQLLEN lenOrInd = 0;
	SQLSMALLINT supplierId = 1;
	int count;

	AB_FUNCT(("TestRawODBCDirectQuery (in)"));

	/* INIT */

	odbc_connect();

	/* MAKE QUERY */

	odbc_command("CREATE TABLE #Products ("
		"ProductID int NOT NULL ,"
		"ProductName varchar (40) ,"
		"SupplierID int NULL ,"
		"CategoryID int NULL ,"
		"QuantityPerUnit varchar (20)  ,"
		"UnitPrice money NULL ,"
		"UnitsInStock smallint NULL ,"
		"UnitsOnOrder smallint NULL ,"
		"ReorderLevel smallint NULL ,"
		"Discontinued bit NOT NULL "
		") "
		"INSERT INTO #Products(ProductID,ProductName,SupplierID,CategoryID,QuantityPerUnit,UnitPrice,UnitsInStock,UnitsOnOrder,ReorderLevel,Discontinued) VALUES(1,'Chai',1,1,'10 boxes x 20 bags',18.00,39,0,10,0) "
		"INSERT INTO #Products(ProductID,ProductName,SupplierID,CategoryID,QuantityPerUnit,UnitPrice,UnitsInStock,UnitsOnOrder,ReorderLevel,Discontinued) VALUES(2,'Chang',1,1,'24 - 12 oz bottles',19.00,17,40,25,0) "
		"INSERT INTO #Products(ProductID,ProductName,SupplierID,CategoryID,QuantityPerUnit,UnitPrice,UnitsInStock,UnitsOnOrder,ReorderLevel,Discontinued) VALUES(3,'Aniseed Syrup',1,2,'12 - 550 ml bottles',10.00,13,70,25,0) "
		"INSERT INTO #Products(ProductID,ProductName,SupplierID,CategoryID,QuantityPerUnit,UnitPrice,UnitsInStock,UnitsOnOrder,ReorderLevel,Discontinued) VALUES(4,'Chef Anton''s Cajun Seasoning',2,2,'48 - 6 oz jars',22.00,53,0,0,0) "
		"INSERT INTO #Products(ProductID,ProductName,SupplierID,CategoryID,QuantityPerUnit,UnitPrice,UnitsInStock,UnitsOnOrder,ReorderLevel,Discontinued) VALUES(5,'Chef Anton''s Gumbo Mix',2,2,'36 boxes',21.35,0,0,0,1) ");
	while (SQLMoreResults(odbc_stmt) == SQL_SUCCESS)
		continue;

	CHKBindParameter(1, SQL_PARAM_INPUT, SQL_C_SSHORT, SQL_INTEGER, 0, 0, &supplierId, 0, &lenOrInd, "S");

	CHKExecDirect(T("SELECT * FROM #Products WHERE SupplierID = ?"), SQL_NTS, "S");

	count = 0;

	while (SQLFetch(odbc_stmt) == SQL_SUCCESS) {
		count++;
	}
	AB_PRINT(("Got %d rows", count));

	if (count != 3) {
		/*
		 * OK - so 3 is a magic number - it's the number of rows matching
		 * this query from the MS sample Northwind database and is a constant.
		 */
		AB_ERROR(("Expected %d rows - but got %d rows", 3, count));
		AB_FUNCT(("TestRawODBCDirectQuery (out): error"));
		return false;
	}

	/* CLOSEDOWN */

	odbc_disconnect();

	AB_FUNCT(("TestRawODBCDirectQuery (out): ok"));
	return true;
}

/*
 * Test that show what works and what doesn't for the poorly
 * documented GUID.
 */
static bool
TestRawODBCGuid(void)
{
	SQLRETURN status;

	const char *queryString;
	SQLLEN lenOrInd;
	SQLSMALLINT age;
	char guid[40];
	SQLCHAR name[20];

	SQLGUID sqlguid;

	AB_FUNCT(("TestRawODBCGuid (in)"));

	odbc_connect();
	
	if (!odbc_db_is_microsoft()) {
		odbc_disconnect();
		return true;
	}

	AB_PRINT(("Creating #pet table"));

	queryString = "CREATE TABLE #pet (name VARCHAR(20), owner VARCHAR(20), "
	       "species VARCHAR(20), sex CHAR(1), age INTEGER, " "guid UNIQUEIDENTIFIER DEFAULT NEWID() ); ";
	CHKExecDirect(T(queryString), SQL_NTS, "SNo");

	odbc_command_with_result(odbc_stmt, "DROP PROC GetGUIDRows");

	AB_PRINT(("Creating stored proc GetGUIDRows"));

	queryString = "CREATE PROCEDURE GetGUIDRows (@guidpar uniqueidentifier) AS \
                SELECT name, guid FROM #pet WHERE guid = @guidpar";
	CHKExecDirect(T(queryString), SQL_NTS, "SNo");

	AB_PRINT(("Insert row 1"));

	queryString = "INSERT INTO #pet( name, owner, species, sex, age ) \
                         VALUES ( 'Fang', 'Mike', 'dog', 'm', 12 );";
	CHKExecDirect(T(queryString), SQL_NTS, "S");

	AB_PRINT(("Insert row 2"));

	/*
	 * Ok - new row with explicit GUID, but parameterised age.
	 */
	queryString = "INSERT INTO #pet( name, owner, species, sex, age, guid ) \
                         VALUES ( 'Splash', 'Dan', 'fish', 'm', ?, \
                         '12345678-1234-1234-1234-123456789012' );";

	lenOrInd = 0;
	age = 3;
	CHKBindParameter(1, SQL_PARAM_INPUT, SQL_C_SSHORT, SQL_INTEGER, 0, 0, &age, 0, &lenOrInd, "S");

	CHKExecDirect(T(queryString), SQL_NTS, "S");
	CHKFreeStmt(SQL_CLOSE, "S");

	AB_PRINT(("Insert row 3"));
	/*
	 * Ok - new row with parameterised GUID.
	 */
	queryString = "INSERT INTO #pet( name, owner, species, sex, age, guid ) \
                         VALUES ( 'Woof', 'Tom', 'cat', 'f', 2, ? );";

	lenOrInd = SQL_NTS;
	strcpy(guid, "87654321-4321-4321-4321-123456789abc");

	CHKBindParameter(1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_GUID, 0, 0, guid, 0, &lenOrInd, "S");
	CHKExecDirect(T(queryString), SQL_NTS, "S");

	AB_PRINT(("Insert row 4"));
	/*
	 * Ok - new row with parameterised GUID.
	 */
	queryString = "INSERT INTO #pet( name, owner, species, sex, age, guid ) \
                         VALUES ( 'Spike', 'Diane', 'pig', 'f', 4, ? );";

	lenOrInd = SQL_NTS;
	strcpy(guid, "1234abcd-abcd-abcd-abcd-123456789abc");

	CHKBindParameter(1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, 36, 0, guid, 0, &lenOrInd, "S");
	CHKExecDirect(T(queryString), SQL_NTS, "S");

	AB_PRINT(("Insert row 5"));
	/*
	 * Ok - new row with parameterised GUID.
	 */
	queryString = "INSERT INTO #pet( name, owner, species, sex, age, guid ) \
                         VALUES ( 'Fluffy', 'Sam', 'dragon', 'm', 16, ? );";

	sqlguid.Data1 = 0xaabbccdd;
	sqlguid.Data2 = 0xeeff;
	sqlguid.Data3 = 0x1122;
	sqlguid.Data4[0] = 0x11;
	sqlguid.Data4[1] = 0x22;
	sqlguid.Data4[2] = 0x33;
	sqlguid.Data4[3] = 0x44;
	sqlguid.Data4[4] = 0x55;
	sqlguid.Data4[5] = 0x66;
	sqlguid.Data4[6] = 0x77;
	sqlguid.Data4[7] = 0x88;

	lenOrInd = 16;
	strcpy(guid, "1234abcd-abcd-abcd-abcd-123456789abc");

	CHKBindParameter(1, SQL_PARAM_INPUT, SQL_C_GUID, SQL_GUID, 16, 0, &sqlguid, 16, &lenOrInd, "S");
	status = SQLExecDirect(odbc_stmt, T(queryString), SQL_NTS);
	if (status != SQL_SUCCESS) {
		AB_ERROR(("Insert row 5 failed"));
		AB_ERROR(("Sadly this was expected in *nix ODBC. Carry on."));
	}

	/*
	 * Now retrieve rows - especially GUID column values.
	 */
	AB_PRINT(("retrieving name and guid"));
	queryString = "SELECT name, guid FROM #pet";
	CHKExecDirect(T(queryString), SQL_NTS, "S");
	while (SQLFetch(odbc_stmt) == SQL_SUCCESS) {
		CHKGetData(1, SQL_CHAR, name, 20, 0, "S");
		CHKGetData(2, SQL_CHAR, guid, 37, 0, "S");

		AB_PRINT(("name: %-10s guid: %s", name, guid));
	}

	/*
	 * Realloc cursor handle - (Windows ODBC considers it an invalid cursor
	 * state if we try SELECT again).
	 */
	odbc_reset_statement();


	/*
	 * Now retrieve rows - especially GUID column values.
	 */

	AB_PRINT(("retrieving name and guid again"));
	queryString = "SELECT name, guid FROM #pet";
	CHKExecDirect(T(queryString), SQL_NTS, "S");
	while (CHKFetch("SNo") == SQL_SUCCESS) {
		CHKGetData(1, SQL_CHAR, name, 20, 0, "S");
		CHKGetData(2, SQL_GUID, &sqlguid, 16, 0, "S");

		AB_PRINT(("%-10s %08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X",
			  name,
			  (int) (sqlguid.Data1), sqlguid.Data2,
			  sqlguid.Data3, sqlguid.Data4[0], sqlguid.Data4[1],
			  sqlguid.Data4[2], sqlguid.Data4[3], sqlguid.Data4[4],
			  sqlguid.Data4[5], sqlguid.Data4[6], sqlguid.Data4[7]));
	}

	/*
	 * Realloc cursor handle - (Windows ODBC considers it an invalid cursor
	 * state if we try SELECT again).
	 */
	odbc_reset_statement();

	/*
	 * Now retrieve rows via stored procedure passing GUID as param.
	 */
	AB_PRINT(("retrieving name and guid"));

	queryString = "{call GetGUIDRows(?)}";
	lenOrInd = SQL_NTS;
	strcpy(guid, "87654321-4321-4321-4321-123456789abc");

	CHKBindParameter(1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_GUID, 0, 0, guid, 0, &lenOrInd, "S");
	CHKExecDirect(T(queryString), SQL_NTS, "S");
	while (SQLFetch(odbc_stmt) == SQL_SUCCESS) {
		CHKGetData(1, SQL_CHAR, name, 20, 0, "S");
		CHKGetData(2, SQL_CHAR, guid, 37, 0, "S");

		AB_PRINT(("%-10s %s", name, guid));
	}

	/*
	 * Realloc cursor handle - (Windows ODBC considers it an invalid cursor
	 * state after a previous SELECT has occurred).
	 */
	odbc_reset_statement();

	/* cleanup */
	odbc_command_with_result(odbc_stmt, "DROP PROC GetGUIDRows");

	/* CLOSEDOWN */

	odbc_disconnect();

	AB_FUNCT(("TestRawODBCGuid (out): ok"));
	return true;
}

/**
 * Array of tests.
 */
static const DbTestEntry tests[] = {
	/* 1 */ {TestRawODBCDirectQuery, "Raw ODBC direct query"},
	/* 2 */ {TestRawODBCPreparedQuery, "Raw ODBC prepared query"},
	/* 3 */ {TestRawODBCGuid, "Raw ODBC GUID"},
	/* end */ {NULL, NULL}
};

/**
 * Code to iterate through all tests to run.
 *
 * \return
 *      true if all tests pass, false if any tests fail.
 */
static bool
RunTests(void)
{
	unsigned int i;
	unsigned int passes = 0;
	unsigned int fails = 0;

	i = 0;
	while (tests[i].testFn) {
		printf("Running test %2d: %s... ", i + 1, tests[i].description);
		fflush(stdout);
		if (tests[i].testFn()) {
			printf("pass\n");
			passes++;
		} else {
			printf("fail\n");
			fails++;
		}
		i++;
		ODBC_FREE();
	}

	if (fails == 0) {
		printf("\nAll %d tests passed.\n\n", passes);
	} else {
		printf("\nTest passes: %d, test fails: %d\n\n", passes, fails);
	}

	/* Return true if there are no failures */
	return (fails == 0);
}

int
main(void)
{
	odbc_use_version3 = true;

	if (RunTests())
		return 0;	/* Success */
	return 1;	/* Error code */
}
