/*
 * Test reading data with SQLGetData
 */
#include "common.h"
#include <assert.h>

/**
 * Test various errors from SQLGetData
 * @param data    string to return from server
 * @param c_type  destination SQL C type
 * @param state   expected SQL state
 */
static void
test_err(const char *data, int c_type, const char *state)
{
	char sql[128];
	SQLLEN ind;
	const unsigned int buf_size = 128;
	char *buf = (char *) malloc(buf_size);

	sprintf(sql, "SELECT '%s'", data);
	odbc_command(sql);
	SQLFetch(odbc_stmt);
	CHKGetData(1, c_type, buf, buf_size, &ind, "E");
	free(buf);
	odbc_read_error();
	if (strcmp(odbc_sqlstate, state) != 0) {
		fprintf(stderr, "Unexpected sql state returned\n");
		odbc_disconnect();
		exit(1);
	}
	odbc_reset_statement();
}

static int type;

static inline int
compute_len_char(void)
{
	return type == SQL_C_CHAR ? 1 : (int) sizeof(SQLWCHAR);
}

#define lc compute_len_char()

static int
mycmp(const char *s1, const char *s2)
{
	size_t i, l;

	l = strlen(s2);
	for (i = 0; i <= l; ++i) {
		SQLWCHAR wc = (type == SQL_C_CHAR) ? s1[i] : ((const SQLWCHAR *) s1)[i];

		if (s2[i] != wc)
			return 1;
	}
	return 0;
}

/**
 * Test returning a string and splitting into multiple parts using SQLGetData
 * @param n_flag  Either "" or "N" to test Nxxx or xxx types (ie NVARCHAR/VARCHAR).
 */
static void
test_split(const char *n_flag)
{
#define CheckLen(x) do { \
	if (len != (x)) { \
		fprintf(stderr, "Wrong len %ld at line %d expected %d\n", (long int) len, __LINE__, (x)); \
		exit(1); \
	} \
	} while(0)

	char *sql;
	char *buf = NULL;
	const char *collate = "";
	SQLLEN len;

	if (odbc_db_is_microsoft())
		collate = " COLLATE Latin1_General_CI_AS";
	sql = odbc_buf_asprintf(&odbc_buf, "SELECT CONVERT(%sTEXT,'Prova'%s + REPLICATE('x',500))%s", n_flag, collate, collate);
	odbc_command(sql);

	CHKFetch("S");

	/* these 2 tests test an old severe BUG in FreeTDS */
	buf = (char *) ODBC_GET(1);
	CHKGetData(1, type, buf, 0, &len, "I");
	if (len != SQL_NO_TOTAL)
		CheckLen(505*lc);
	CHKGetData(1, type, buf, 0, &len, "I");
	if (len != SQL_NO_TOTAL)
		CheckLen(505*lc);
	buf = (char *) ODBC_GET(3*lc);
	CHKGetData(1, type, buf, 3 * lc, &len, "I");
	if (len != SQL_NO_TOTAL)
		CheckLen(505*lc);
	if (mycmp(buf, "Pr") != 0) {
		printf("Wrong data result 1\n");
		exit(1);
	}

	buf = (char *) ODBC_GET(16*lc);
	CHKGetData(1, type, buf, 16 * lc, &len, "I");
	if (len != SQL_NO_TOTAL)
		CheckLen(503*lc);
	if (mycmp(buf, "ovaxxxxxxxxxxxx") != 0) {
		printf("Wrong data result 2 res = '%s'\n", buf);
		exit(1);
	}

	buf = (char *) ODBC_GET(256*lc);
	CHKGetData(1, type, buf, 256 * lc, &len, "I");
	if (len != SQL_NO_TOTAL)
		CheckLen(488*lc);
	CHKGetData(1, type, buf, 256 * lc, &len, "S");
	CheckLen(233*lc);
	CHKGetData(1, type, buf, 256 * lc, &len, "No");

	odbc_reset_statement();

	/* test with varchar, not blob but variable */
	sql = odbc_buf_asprintf(&odbc_buf, "SELECT CONVERT(%sVARCHAR(100), 'Other test')", n_flag);
	odbc_command(sql);

	CHKFetch("S");

	buf = (char *) ODBC_GET(7*lc);
	CHKGetData(1, type, buf, 7 * lc, NULL, "I");
	if (mycmp(buf, "Other ") != 0) {
		printf("Wrong data result 1\n");
		exit(1);
	}

	buf = (char *) ODBC_GET(5*lc);
	CHKGetData(1, type, buf, 20, NULL, "S");
	if (mycmp(buf, "test") != 0) {
		printf("Wrong data result 2 res = '%s'\n", buf);
		exit(1);
	}
	ODBC_FREE();

	odbc_reset_statement();
}

TEST_MAIN()
{
	char buf[32];
	SQLINTEGER int_buf;
	SQLLEN len;
	SQLRETURN rc;
	TIMESTAMP_STRUCT tss;

	odbc_connect();

	type = SQL_C_CHAR;
	test_split("");

	type = SQL_C_WCHAR;
	test_split("");

	if (odbc_db_is_microsoft() && odbc_db_version_int() >= 0x07000000u) {
		type = SQL_C_CHAR;
		test_split("N");

		type = SQL_C_WCHAR;
		test_split("N");
	}

	/* test with fixed length */
	odbc_command("SELECT CONVERT(INT, 12345)");

	CHKFetch("S");

	int_buf = 0xdeadbeef;
	CHKGetData(1, SQL_C_SLONG, &int_buf, 0, NULL, "S");
	if (int_buf != 12345) {
		printf("Wrong data result\n");
		exit(1);
	}

	CHKGetData(1, SQL_C_SLONG, &int_buf, 0, NULL, "No");
	if (int_buf != 12345) {
		printf("Wrong data result 2 res = %d\n", (int) int_buf);
		exit(1);
	}

	odbc_reset_statement();

	/* test with numeric */
	odbc_command("SELECT CONVERT(NUMERIC(18,5), 1850000000000)");

	CHKFetch("S");

	memset(buf, 'x', sizeof(buf));
	CHKGetData(1, SQL_C_CHAR, buf, 14, NULL, "S");
	buf[sizeof(buf)-1] = 0;
	if (strcmp(buf, "1850000000000") != 0) {
		printf("Wrong data result: %s\n", buf);
		exit(1);
	}

	/* should give NO DATA */
	CHKGetData(1, SQL_C_CHAR, buf, 14, NULL, "No");
	buf[sizeof(buf)-1] = 0;
	if (strcmp(buf, "1850000000000") != 0) {
		printf("Wrong data result 3 res = %s\n", buf);
		exit(1);
	}

	odbc_reset_statement();


	/* test int to truncated string */
	odbc_command("SELECT CONVERT(INTEGER, 12345)");
	CHKFetch("S");

	/* error 22003 */
	memset(buf, 'x', sizeof(buf));
	CHKGetData(1, SQL_C_CHAR, buf, 4, NULL, "E");
#ifdef ENABLE_DEVELOPING
	buf[4] = 0;
	if (strcmp(buf, "xxxx") != 0) {
		fprintf(stderr, "Wrong buffer result buf = %s\n", buf);
		exit(1);
	}
#endif
	odbc_read_error();
	if (strcmp(odbc_sqlstate, "22003") != 0) {
		fprintf(stderr, "Unexpected sql state %s returned\n", odbc_sqlstate);
		odbc_disconnect();
		exit(1);
	}
	CHKGetData(1, SQL_C_CHAR, buf, 2, NULL, "No");
	odbc_reset_statement();

	/* test unique identifier to truncated string */
	rc = odbc_command2("SELECT CONVERT(UNIQUEIDENTIFIER, 'AA7DF450-F119-11CD-8465-00AA00425D90')", "SENo");
	if (rc != SQL_ERROR) {
		CHKFetch("S");

		/* error 22003 */
		CHKGetData(1, SQL_C_CHAR, buf, 17, NULL, "E");
		odbc_read_error();
		if (strcmp(odbc_sqlstate, "22003") != 0) {
			fprintf(stderr, "Unexpected sql state %s returned\n", odbc_sqlstate);
			odbc_disconnect();
			exit(1);
		}
		CHKGetData(1, SQL_C_CHAR, buf, 2, NULL, "No");
	}
	odbc_reset_statement();


	odbc_disconnect();

	odbc_use_version3 = true;
	odbc_conn_additional_params = "ClientCharset=UTF-8;";
	odbc_connect();

	/* test error from SQLGetData */
	/* wrong constant, SQL_VARCHAR is invalid as C type */
	test_err("prova 123",           SQL_VARCHAR,     "HY003");
	/* use ARD but no ARD data column */
	test_err("prova 123",           SQL_ARD_TYPE,    "07009");
	/* wrong conversion, int */
	test_err("prova 123",           SQL_C_LONG,      "22018");
	/* wrong conversion, date */
	test_err("prova 123",           SQL_C_TIMESTAMP, "22018");
	/* overflow */
	test_err("1234567890123456789", SQL_C_LONG,      "22003");

	/* test datetime precision */
	odbc_command("SELECT CONVERT(DATETIME, '2018-08-15 12:34:56.007')");

	CHKFetch("S");

	memset(&tss, 'x', sizeof(tss));
	CHKGetData(1, SQL_C_TYPE_TIMESTAMP, &tss, sizeof(tss), NULL, "S");
	if (tss.fraction != 7000000) {
		printf("Wrong data result: %lu\n", (unsigned long) tss.fraction);
		exit(1);
	}

	odbc_reset_statement();

	/* test for empty string from mssql */
	if (odbc_db_is_microsoft()) {
		type = SQL_C_CHAR;

		for (;;) {
			void *buf = ODBC_GET(lc);

			odbc_command("SELECT CONVERT(TEXT,'' COLLATE Latin1_General_CI_AS)");

			CHKFetch("S");

			len = 1234;
			CHKGetData(1, type, buf, lc, &len, "S");

			if (len != 0) {
				fprintf(stderr, "Wrong len returned, returned %ld\n", (long) len);
				return 1;
			}

			CHKGetData(1, type, buf, lc, NULL, "No");
			odbc_reset_statement();
			ODBC_FREE();

			buf = ODBC_GET(4096*lc);

			odbc_command("SELECT CONVERT(TEXT,'' COLLATE Latin1_General_CI_AS)");

			CHKFetch("S");

			len = 1234;
			CHKGetData(1, type, buf, lc*4096, &len, "S");

			if (len != 0) {
				fprintf(stderr, "Wrong len returned, returned %ld\n", (long) len);
				return 1;
			}

			CHKGetData(1, type, buf, lc*4096, NULL, "No");
			odbc_reset_statement();
			ODBC_FREE();

			if (type != SQL_C_CHAR)
				break;
			type = SQL_C_WCHAR;
		}	

		odbc_command("SELECT CONVERT(TEXT,'' COLLATE Latin1_General_CI_AS)");

		CHKFetch("S");

		len = 1234;
		CHKGetData(1, SQL_C_BINARY, buf, 1, &len, "S");

		if (len != 0) {
			fprintf(stderr, "Wrong len returned, returned %ld\n", (long) len);
			return 1;
		}

		CHKGetData(1, SQL_C_BINARY, buf, 1, NULL, "No");
	}

	odbc_reset_statement();

	/* test splitting UTF-8 from mssql 2019 */
	if (odbc_driver_is_freetds() && odbc_db_is_microsoft() && odbc_db_version_int() >= 0x0f000000u
	    && odbc_tds_version() >= 0x704) {
		void *buf;

		type = SQL_C_CHAR;

		odbc_command("SELECT CONVERT(VARCHAR(100), CONVERT(NVARCHAR(10), 0xc200c300c4003200) "
			     "COLLATE Latin1_General_100_CI_AI_SC_UTF8)");

		CHKFetch("S");

		buf = ODBC_GET(4);
		len = 1234;
		CHKGetData(1, type, buf, 4, &len, "SI");

		if (len != 7) {
			fprintf(stderr, "Wrong len returned, returned %ld\n", (long) len);
			return 1;
		}

		buf = ODBC_GET(10);
		len = 1234;
		CHKGetData(1, type, buf, 10, &len, "SI");

		if (len != 4) {
			fprintf(stderr, "Wrong len returned, returned %ld\n", (long) len);
			return 1;
		}

		CHKGetData(1, type, buf, lc, NULL, "No");
		odbc_reset_statement();
		ODBC_FREE();
	}

	odbc_disconnect();

	printf("Done.\n");
	return 0;
}
