From 6e91f1ca4a4806543b97b00353ec66cdaf52d5ea Mon Sep 17 00:00:00 2001 From: "Aaron M. Ucko" Date: Wed, 3 Jul 2024 14:57:13 -0400 Subject: [PATCH 1/2] Let ctlib and dblib unit tests work better with Sybase libraries ... for comparison purposes. * sqldb.h: Don't insist on the accompanying sybdb.h, which might clash with Sybase headers or result in skew with Sybase libraries. * sqlfront.h: Likewise for sybfront.h; moreover, additionally pull in sybdb.h and don't rely on having USHORT. * ctlib tests: Use centrally defined UT_{CS,BLK}_VERSION macros rather than hardcoding {CS,BLK}_VERSION_100, which could result in blk_describe failure when run against modern servers. Allow both to be overridden, but default UT_CS_VERSION to CS_CURRENT_VERSION where available (falling back on CS_VERSION_160) and UT_BLK_VERSION to UT_CS_VERSION because the numbers always match up in practice. Signed-off-by: Aaron M. Ucko --- include/sqldb.h | 2 +- include/sqlfront.h | 5 +++-- src/ctlib/unittests/all_types.c | 2 +- src/ctlib/unittests/blk_in.c | 2 +- src/ctlib/unittests/blk_in2.c | 2 +- src/ctlib/unittests/blk_out.c | 2 +- src/ctlib/unittests/common.c | 4 ++-- src/ctlib/unittests/common.h | 11 +++++++++++ src/ctlib/unittests/connect_fail.c | 4 ++-- src/ctlib/unittests/cs_config.c | 4 ++-- src/ctlib/unittests/cs_convert.c | 2 +- src/ctlib/unittests/cs_diag.c | 4 ++-- src/ctlib/unittests/errors.c | 2 +- src/ctlib/unittests/t0008.c | 4 ++-- 14 files changed, 31 insertions(+), 19 deletions(-) diff --git a/include/sqldb.h b/include/sqldb.h index 55e9195f66..3f495a9e2f 100644 --- a/include/sqldb.h +++ b/include/sqldb.h @@ -20,7 +20,7 @@ #ifndef SQLDB_h #define SQLDB_h -#include "./sybdb.h" +#include #define SQLCHAR SYBCHAR #define SQLVARCHAR SYBVARCHAR diff --git a/include/sqlfront.h b/include/sqlfront.h index d351f88df5..7634613b96 100644 --- a/include/sqlfront.h +++ b/include/sqlfront.h @@ -21,7 +21,8 @@ #ifndef SQLFRONT_h #define SQLFRONT_h -#include "./sybfront.h" +#include +#include static const char rcsid_sqlfront_h[] = "$Id: sqlfront.h,v 1.10 2011-07-13 11:06:31 freddy77 Exp $"; static const void *const no_unused_sqlfront_h_warn[] = { rcsid_sqlfront_h, no_unused_sqlfront_h_warn }; @@ -43,7 +44,7 @@ typedef const LPINT LPCINT; #define _LPCBYTE_DEFINED typedef const BYTE * LPCBYTE; #endif -typedef USHORT * LPUSHORT; +typedef unsigned short * LPUSHORT; typedef const LPUSHORT LPCUSHORT; typedef DBINT * LPDBINT; typedef const LPDBINT LPCDBINT; diff --git a/src/ctlib/unittests/all_types.c b/src/ctlib/unittests/all_types.c index 40e2e02d72..a407edfce7 100644 --- a/src/ctlib/unittests/all_types.c +++ b/src/ctlib/unittests/all_types.c @@ -73,7 +73,7 @@ main(void) tdsdump_open(tds_dir_getenv(TDS_DIR("TDSDUMP"))); - check_call(cs_ctx_alloc, (CS_VERSION_100, &ctx)); + check_call(cs_ctx_alloc, (UT_CS_VERSION, &ctx)); tds_ctx = tds_alloc_context(NULL); assert(tds_ctx); diff --git a/src/ctlib/unittests/blk_in.c b/src/ctlib/unittests/blk_in.c index 00bdb171de..2291f6027b 100644 --- a/src/ctlib/unittests/blk_in.c +++ b/src/ctlib/unittests/blk_in.c @@ -224,7 +224,7 @@ main(void) check_call(run_command, (cmd, create_table_sql)); - check_call(blk_alloc, (conn, BLK_VERSION_100, &blkdesc)); + check_call(blk_alloc, (conn, UT_BLK_VERSION, &blkdesc)); check_call(blk_init, (blkdesc, CS_BLK_IN, (char *) table_name, CS_NULLTERM)); diff --git a/src/ctlib/unittests/blk_in2.c b/src/ctlib/unittests/blk_in2.c index 01bcbcc2f6..29b61a2ad4 100644 --- a/src/ctlib/unittests/blk_in2.c +++ b/src/ctlib/unittests/blk_in2.c @@ -75,7 +75,7 @@ main(void) check_call(run_command, (cmd, create_table_sql)); - check_call(blk_alloc, (conn, BLK_VERSION_100, &blkdesc)); + check_call(blk_alloc, (conn, UT_BLK_VERSION, &blkdesc)); for (i = 0; i < 10; i++) { /* compute some data */ diff --git a/src/ctlib/unittests/blk_out.c b/src/ctlib/unittests/blk_out.c index 746dab1150..28d9b32f44 100644 --- a/src/ctlib/unittests/blk_out.c +++ b/src/ctlib/unittests/blk_out.c @@ -53,7 +53,7 @@ main(void) check_call(run_command, (cmd, "insert into #ctlibarray values (8, 'DDDD', 'Jan 4 2002 10:00:00AM')")); check_call(run_command, (cmd, "insert into #ctlibarray values (NULL, 'EEEE', 'Jan 5 2002 10:00:00AM')")); - check_call(blk_alloc, (conn, BLK_VERSION_100, &blkdesc)); + check_call(blk_alloc, (conn, UT_BLK_VERSION, &blkdesc)); check_call(blk_init, (blkdesc, CS_BLK_OUT, "#ctlibarray", CS_NULLTERM)); diff --git a/src/ctlib/unittests/common.c b/src/ctlib/unittests/common.c index ee55f5c451..7dfd5b88be 100644 --- a/src/ctlib/unittests/common.c +++ b/src/ctlib/unittests/common.c @@ -185,7 +185,7 @@ continue_logging_in(CS_CONTEXT ** ctx, CS_CONNECTION ** conn, CS_COMMAND ** cmd, TDSCONTEXT *tds_ctx; #endif - ret = cs_ctx_alloc(CS_VERSION_100, ctx); + ret = cs_ctx_alloc(UT_CS_VERSION, ctx); if (ret != CS_SUCCEED) { if (verbose) { fprintf(stderr, "Context Alloc failed!\n"); @@ -202,7 +202,7 @@ continue_logging_in(CS_CONTEXT ** ctx, CS_CONNECTION ** conn, CS_COMMAND ** cmd, } #endif - ret = ct_init(*ctx, CS_VERSION_100); + ret = ct_init(*ctx, UT_CS_VERSION); if (ret != CS_SUCCEED) { if (verbose) { fprintf(stderr, "Library Init failed!\n"); diff --git a/src/ctlib/unittests/common.h b/src/ctlib/unittests/common.h index 9f6a72cb52..d788ea94e9 100644 --- a/src/ctlib/unittests/common.h +++ b/src/ctlib/unittests/common.h @@ -4,6 +4,17 @@ #include +#ifndef UT_CS_VERSION +# ifdef CS_CURRENT_VERSION +# define UT_CS_VERSION CS_CURRENT_VERSION +# else +# define UT_CS_VERSION CS_VERSION_160 +# endif +#endif +#ifndef UT_BLK_VERSION +# define UT_BLK_VERSION UT_CS_VERSION +#endif + extern char SERVER[512]; extern char DATABASE[512]; extern char USER[512]; diff --git a/src/ctlib/unittests/connect_fail.c b/src/ctlib/unittests/connect_fail.c index cfb9ecf625..3b6f7b9b4a 100644 --- a/src/ctlib/unittests/connect_fail.c +++ b/src/ctlib/unittests/connect_fail.c @@ -13,8 +13,8 @@ main(void) read_login_info(); - check_call(cs_ctx_alloc, (CS_VERSION_100, &ctx)); - check_call(ct_init, (ctx, CS_VERSION_100)); + check_call(cs_ctx_alloc, (UT_CS_VERSION, &ctx)); + check_call(ct_init, (ctx, UT_CS_VERSION)); check_call(ct_con_alloc, (ctx, &conn)); check_call(ct_con_props, (conn, CS_SET, CS_USERNAME, (CS_VOID*) "sa", CS_NULLTERM, NULL)); check_call(ct_con_props, (conn, CS_SET, CS_PASSWORD, (CS_VOID*) "invalid", CS_NULLTERM, NULL)); diff --git a/src/ctlib/unittests/cs_config.c b/src/ctlib/unittests/cs_config.c index 505dbff33e..a028dc2642 100644 --- a/src/ctlib/unittests/cs_config.c +++ b/src/ctlib/unittests/cs_config.c @@ -24,8 +24,8 @@ main(void) printf("Trying cs_config with CS_USERDATA\n\n"); } - check_call(cs_ctx_alloc, (CS_VERSION_100, &ctx)); - check_call(ct_init, (ctx, CS_VERSION_100)); + check_call(cs_ctx_alloc, (UT_CS_VERSION, &ctx)); + check_call(ct_init, (ctx, UT_CS_VERSION)); printf("Testing CS_SET/GET USERDATA with char array\n"); diff --git a/src/ctlib/unittests/cs_convert.c b/src/ctlib/unittests/cs_convert.c index c782518ced..5cc04b9711 100644 --- a/src/ctlib/unittests/cs_convert.c +++ b/src/ctlib/unittests/cs_convert.c @@ -133,7 +133,7 @@ main(void) printf("%s: Testing conversion\n", __FILE__); - check_call(cs_ctx_alloc, (CS_VERSION_100, &ctx)); + check_call(cs_ctx_alloc, (UT_CS_VERSION, &ctx)); /* TODO For each conversion test different values of fromlen and tolen */ diff --git a/src/ctlib/unittests/cs_diag.c b/src/ctlib/unittests/cs_diag.c index 2ebabae8b8..f32f1462af 100644 --- a/src/ctlib/unittests/cs_diag.c +++ b/src/ctlib/unittests/cs_diag.c @@ -40,8 +40,8 @@ main(void) if (verbose) { printf("Trying clientmsg_cb with context\n"); } - check_call(cs_ctx_alloc, (CS_VERSION_100, &ctx)); - check_call(ct_init, (ctx, CS_VERSION_100)); + check_call(cs_ctx_alloc, (UT_CS_VERSION, &ctx)); + check_call(ct_init, (ctx, UT_CS_VERSION)); check_call(cs_diag, (ctx, CS_INIT, CS_UNUSED, CS_UNUSED, NULL)); diff --git a/src/ctlib/unittests/errors.c b/src/ctlib/unittests/errors.c index 149273e8c3..e9048f46cc 100644 --- a/src/ctlib/unittests/errors.c +++ b/src/ctlib/unittests/errors.c @@ -288,7 +288,7 @@ static void test_blk_init(void) { CS_BLKDESC *blkdesc; - check_call(blk_alloc, (conn, BLK_VERSION_100, &blkdesc)); + check_call(blk_alloc, (conn, UT_BLK_VERSION, &blkdesc)); /* invalid direction */ check_fail(blk_init, (blkdesc, 100, "testname", CS_NULLTERM)); diff --git a/src/ctlib/unittests/t0008.c b/src/ctlib/unittests/t0008.c index 32c19ead32..653a88deb6 100644 --- a/src/ctlib/unittests/t0008.c +++ b/src/ctlib/unittests/t0008.c @@ -37,8 +37,8 @@ main(void) if (verbose) { printf("Trying clientmsg_cb with context\n"); } - check_call(cs_ctx_alloc, (CS_VERSION_100, &ctx)); - check_call(ct_init, (ctx, CS_VERSION_100)); + check_call(cs_ctx_alloc, (UT_CS_VERSION, &ctx)); + check_call(ct_init, (ctx, UT_CS_VERSION)); check_call(ct_callback, (ctx, NULL, CS_SET, CS_CLIENTMSG_CB, (CS_VOID*) clientmsg_cb)); clientmsg_cb_invoked = 0; From b45838c57ae779e8a1897d352f9cb9b5cad0ac9b Mon Sep 17 00:00:00 2001 From: "Aaron M. Ucko" Date: Wed, 3 Jul 2024 15:20:39 -0400 Subject: [PATCH 2/2] Add ctlib and dblib "tests" that summarize BCP truncation semantics. They don't enforce specific results, just note them all in tabular form to facilitate comparison with Sybase libraries' behavior. Signed-off-by: Aaron M. Ucko --- src/ctlib/unittests/CMakeLists.txt | 2 +- src/ctlib/unittests/Makefile.am | 2 + src/ctlib/unittests/blk_in3.c | 595 +++++++++++++++++++++++++++++ src/dblib/unittests/CMakeLists.txt | 2 +- src/dblib/unittests/Makefile.am | 2 + src/dblib/unittests/bcp3.c | 473 +++++++++++++++++++++++ 6 files changed, 1074 insertions(+), 2 deletions(-) create mode 100755 src/ctlib/unittests/blk_in3.c create mode 100755 src/dblib/unittests/bcp3.c diff --git a/src/ctlib/unittests/CMakeLists.txt b/src/ctlib/unittests/CMakeLists.txt index 60e722bb77..7a8f6352c7 100644 --- a/src/ctlib/unittests/CMakeLists.txt +++ b/src/ctlib/unittests/CMakeLists.txt @@ -8,7 +8,7 @@ foreach(target t0001 t0002 t0003 t0004 ct_diagclient ct_diagserver ct_diagall cs_config cancel blk_in blk_out ct_cursor ct_cursors - ct_dynamic blk_in2 datafmt data + ct_dynamic blk_in2 blk_in3 datafmt data all_types long_binary will_convert variant errors ct_command) add_executable(c_${target} EXCLUDE_FROM_ALL ${target}.c) diff --git a/src/ctlib/unittests/Makefile.am b/src/ctlib/unittests/Makefile.am index 426d3e4b14..8326e0b08d 100644 --- a/src/ctlib/unittests/Makefile.am +++ b/src/ctlib/unittests/Makefile.am @@ -28,6 +28,7 @@ TESTS = \ ct_cursors$(EXEEXT) \ ct_dynamic$(EXEEXT) \ blk_in2$(EXEEXT) \ + blk_in3$(EXEEXT) \ datafmt$(EXEEXT) \ data$(EXEEXT) \ rpc_fail$(EXEEXT) \ @@ -70,6 +71,7 @@ ct_cursor_SOURCES = ct_cursor.c ct_cursors_SOURCES = ct_cursors.c ct_dynamic_SOURCES = ct_dynamic.c blk_in2_SOURCES = blk_in2.c +blk_in3_SOURCES = blk_in3.c datafmt_SOURCES = datafmt.c data_SOURCES = data.c rpc_fail_SOURCES = rpc_fail.c diff --git a/src/ctlib/unittests/blk_in3.c b/src/ctlib/unittests/blk_in3.c new file mode 100755 index 0000000000..1806752adc --- /dev/null +++ b/src/ctlib/unittests/blk_in3.c @@ -0,0 +1,595 @@ +#include +#include + +#include +#include +#include + +#include +#include +#include "common.h" + +static int last_error; +static const char * error_descriptions[512]; + +struct full_result +{ + CS_RETCODE rc; + int error; // zero if none +}; + +struct bi_input +{ + CS_INT host_type; + CS_INT host_format; + CS_INT host_maxlen; + const char * var_text; + const void * var_addr; + CS_INT var_len; +}; + + +static CS_INT +record_cslibmsg(CS_CONTEXT * context TDS_UNUSED, CS_CLIENTMSG * errmsg) +{ + int index = (((CS_LAYER(errmsg->msgnumber) - 1) << 8) + | CS_NUMBER(errmsg->msgnumber)); + last_error = errmsg->msgnumber; + if (error_descriptions[index] == NULL) { + error_descriptions[index] = strdup(errmsg->msgstring); + } + return CS_SUCCEED; +} + +static CS_RETCODE +record_ctlibmsg(CS_CONTEXT * context, CS_CONNECTION * connection TDS_UNUSED, + CS_CLIENTMSG * errmsg) +{ + return record_cslibmsg(context, errmsg); +} + + +#define capture_result(fr, f, args) \ + do { \ + struct full_result *_fr = (fr); \ + last_error = 0; \ + _fr->rc = f args; \ + _fr->error = last_error; \ + } while(0) + +static void +print_full_result(const struct full_result * fr) +{ + switch (fr->rc) { + case CS_SUCCEED: fputs(" +", stdout); break; + case CS_FAIL: fputs("- ", stdout); break; + case CS_MEM_ERROR: fputs("-M", stdout); break; + case CS_PENDING: fputs("-P", stdout); break; + case CS_QUIET: fputs("-Q", stdout); break; + case CS_BUSY: fputs("-B", stdout); break; + case CS_INTERRUPT: fputs("-I", stdout); break; + case CS_BLK_HAS_TEXT: fputs("-K", stdout); break; + case CS_CONTINUE: fputs("-C", stdout); break; + case CS_FATAL: fputs("-F", stdout); break; + case CS_RET_HAFAILOVER: fputs("-H", stdout); break; + case CS_CANCELED: fputs("-X", stdout); break; + case CS_ROW_FAIL: fputs("-R", stdout); break; + case CS_END_DATA: fputs("-d", stdout); break; + case CS_END_RESULTS: fputs("-r", stdout); break; + case CS_END_ITEM: fputs("-i", stdout); break; + case CS_NOMSG: fputs("-N", stdout); break; + case CS_TIMED_OUT: fputs("-T", stdout); break; + default: fputs("-?", stdout); break; + } + if (fr->error) { + printf("%d:%d", CS_LAYER(fr->error), CS_NUMBER(fr->error)); + } +} + + +static CS_RETCODE +do_bind(CS_BLKDESC * blkdesc, const struct bi_input * bi, bool is_null) +{ + CS_DATAFMT datafmt; + CS_RETCODE ret; + + static /* const */ CS_INT colnum = 1, zero_len = 0; + static /* const */ CS_SMALLINT null_ind = -1, not_null_ind = 0; + + ret = blk_describe(blkdesc, colnum, &datafmt); + if (ret != CS_SUCCEED) { + fprintf(stderr, "blk_describe(%d) failed", colnum); + return ret; + } + + datafmt.format = bi->host_format; + datafmt.datatype = bi->host_type; + datafmt.maxlength = bi->host_maxlen; + datafmt.count = 1; + switch (bi->host_type) { + case CS_DECIMAL_TYPE: + datafmt.scale = ((CS_DECIMAL *) bi->var_addr)->scale; + datafmt.precision = ((CS_DECIMAL *) bi->var_addr)->precision; + break; + case CS_NUMERIC_TYPE: + datafmt.scale = ((CS_NUMERIC *) bi->var_addr)->scale; + datafmt.precision = ((CS_NUMERIC *) bi->var_addr)->precision; + break; + default: + break; + } + + return blk_bind(blkdesc, colnum, &datafmt, (CS_VOID *) bi->var_addr, + is_null ? &zero_len : (CS_INT *) &bi->var_len, + is_null ? &null_ind : ¬_null_ind); +} + + +static char * +bin_str(const void * p, CS_INT l) +{ + /* Slight overkill, but simplifies logic */ + char * s = malloc(l * 3 + 1); + int i; + for (i = 0; i < l; ++i) { + snprintf(s + 3 * i, 4, "%02x ", ((unsigned char *) p)[i]); + } + s[l * 3 - 1] = '\0'; + return s; +} + + +static int +do_fetch(CS_COMMAND * cmd) +{ + CS_INT count, row_count = 0; + CS_RETCODE ret; + + while ((ret = ct_fetch(cmd, CS_UNUSED, CS_UNUSED, CS_UNUSED, &count)) + == CS_SUCCEED) { + char buffer[256]; + CS_DATAFMT datafmt; + CS_INT len; + + row_count += count; + ct_describe(cmd, 1, &datafmt); + ct_get_data(cmd, 1, buffer, sizeof(buffer), &len); + if (datafmt.datatype == CS_CHAR_TYPE) { + printf(" '%.*s'", len, buffer); + } else if (len > 0) { + char * s = bin_str(buffer, len); + printf(" %s", s); + free(s); + } + } + if (ret == CS_ROW_FAIL) { + fputs(" [FAIL]", stdout); + return 1; + } else if (ret == CS_END_DATA) { + return 0; + } else { + printf(" [??? (%d)]", ret); + return 1; + } +} + +static CS_RETCODE +do_query(CS_COMMAND * cmd, const char * query) +{ + int result_num; + CS_RETCODE results_ret, result_type; + + check_call(ct_command, (cmd, CS_LANG_CMD, query, CS_NULLTERM, + CS_UNUSED)); + check_call(ct_send, (cmd)); + + result_num = 0; + while ((results_ret = ct_results(cmd, &result_type)) == CS_SUCCEED) { + if (result_type == CS_STATUS_RESULT) + continue; + switch ((int) result_type) { + case CS_ROW_RESULT: + if (do_fetch(cmd)) { + return CS_FAIL; + } + break; + } + result_num++; + } + return results_ret; +} + +static const char * +host_type_name(CS_INT host_type) +{ + switch (host_type) { + case CS_CHAR_TYPE: return "CHAR"; + case CS_BINARY_TYPE: return "BINARY"; + case CS_LONGCHAR_TYPE: return "LNGCHAR"; + case CS_LONGBINARY_TYPE: return "LONGBIN"; + case CS_TEXT_TYPE: return "TEXT"; + case CS_IMAGE_TYPE: return "IMAGE"; + case CS_TINYINT_TYPE: return "TINYINT"; + case CS_SMALLINT_TYPE: return "SMOLINT"; + case CS_INT_TYPE: return "INT"; + case CS_REAL_TYPE: return "REAL"; + case CS_FLOAT_TYPE: return "FLOAT"; + case CS_BIT_TYPE: return "BIT"; + case CS_DATETIME_TYPE: return "DT"; + case CS_DATETIME4_TYPE: return "DT4"; + case CS_MONEY_TYPE: return "MONEY"; + case CS_MONEY4_TYPE: return "MONEY4"; + case CS_NUMERIC_TYPE: return "NUMERIC"; + case CS_DECIMAL_TYPE: return "DECIMAL"; + case CS_VARCHAR_TYPE: return "VARCHAR"; + case CS_VARBINARY_TYPE: return "VARBIN"; + case CS_LONG_TYPE: return "LONG"; + case CS_SENSITIVITY_TYPE: return "SENS"; + case CS_BOUNDARY_TYPE: return "BOUND"; + case CS_VOID_TYPE: return "VOID"; + case CS_USHORT_TYPE: return "USHORT"; + case CS_UNICHAR_TYPE: return "UNICHAR"; + case CS_BLOB_TYPE: return "BLOB"; + case CS_DATE_TYPE: return "DATE"; + case CS_TIME_TYPE: return "TIME"; +#ifdef CS_UNITEXT_TYPE + case CS_UNITEXT_TYPE: return "UNITEXT"; +#endif +#ifdef CS_BIGINT_TYPE + case CS_BIGINT_TYPE: return "BIGINT"; +#endif +#ifdef CS_UINT_TYPE + case CS_USMALLINT_TYPE: return "USMLINT"; + case CS_UINT_TYPE: return "UINT"; + case CS_UBIGINT_TYPE: return "UBIGINT"; +#endif +#ifdef CS_XML_TYPE + case CS_XML_TYPE: return "XML"; +#endif +#ifdef CS_BIGDATETIME_TYPE + case CS_BIGDATETIME_TYPE: return "BIGDT"; + case CS_BIGTIME_TYPE: return "BIGTIME"; +#endif +#ifdef CS_TEXTLOCATOR_TYPE + case CS_TEXTLOCATOR_TYPE: return "TEXTLOC"; + case CS_UNITEXTLOCATOR_TYPE: return "UTXTLOC"; +#endif +#ifdef CS_UNIQUE_TYPE + case CS_UNIQUE_TYPE: return "UNIQUE"; +#endif + } + return "???"; +} + +static void +test_one_case(CS_CONNECTION * conn, CS_COMMAND * cmd, + const struct bi_input * bi, const char * sql_abbrev, + bool is_null) +{ + struct full_result fr; + CS_BLKDESC * blkdesc; + CS_INT count; + + printf("%s\t%s\t", host_type_name(bi->host_type), sql_abbrev); + + check_call(blk_alloc, (conn, UT_BLK_VERSION, &blkdesc)); + check_call(blk_init, (blkdesc, CS_BLK_IN, "#t", CS_NULLTERM)); + capture_result(&fr, do_bind, (blkdesc, bi, is_null)); + print_full_result(&fr); + putchar('\t'); + capture_result(&fr, blk_rowxfer, (blkdesc)); + print_full_result(&fr); + check_call(blk_done, (blkdesc, CS_BLK_ALL, &count)); + blk_drop(blkdesc); + if (is_null) { + fputs("\tNULL ->", stdout); + } else { + printf("\t%s ->", bi->var_text); + } + if (count) { + do_query(cmd, "SELECT x FROM #t"); + } else { + fputs(" N/A", stdout); + } + putchar('\n'); + fflush(stdout); +} + +static void +test_case(CS_CONNECTION * conn, CS_COMMAND * cmd, const struct bi_input * bi, + const char * sql_type, const char * sql_abbrev) +{ + char sql[64]; + snprintf(sql, sizeof(sql), "CREATE TABLE #t (x %s NULL)", sql_type); + run_command(cmd, sql); + test_one_case(conn, cmd, bi, sql_abbrev, false); + run_command(cmd, "TRUNCATE TABLE #t"); + test_one_case(conn, cmd, bi, sql_abbrev, true); + run_command(cmd, "DROP TABLE #t"); +} + +static void +routine_tests(CS_CONNECTION * conn, CS_COMMAND * cmd, + const struct bi_input * bi) +{ + test_case(conn, cmd, bi, "BINARY(1)", "B(1)"); + test_case(conn, cmd, bi, "VARBINARY(1)", "VB(1)"); + test_case(conn, cmd, bi, "BINARY(64)", "B(64)"); + test_case(conn, cmd, bi, "VARBINARY(64)", "VB(64)"); + test_case(conn, cmd, bi, "CHAR(1)", "C(1)"); + test_case(conn, cmd, bi, "VARCHAR(1)", "VC(1)"); + test_case(conn, cmd, bi, "CHAR(64)", "C(64)"); + test_case(conn, cmd, bi, "VARCHAR(64)", "VC(64)"); +} + +static void +c2b_tests(CS_CONNECTION * conn, CS_COMMAND * cmd, + const struct bi_input * bi_in, const char * sql_type, + const char * sql_abbrev) +{ + char sql[64]; + struct bi_input bi; + char * s; + char * alt_text; + CS_SMALLINT l, offset; + + memcpy(&bi, bi_in, sizeof(bi)); + bi.var_addr = malloc(bi_in->host_maxlen); + memcpy((void *) bi.var_addr, bi_in->var_addr, bi.var_len); + if (bi.host_type == CS_VARCHAR_TYPE) { + s = ((CS_VARCHAR *) bi.var_addr)->str; + offset = sizeof(CS_SMALLINT); + } else { + s = (char*) bi.var_addr; + offset = 0; + } + alt_text = malloc(bi_in->host_maxlen - offset); + + snprintf(sql, sizeof(sql), "CREATE TABLE #t (x %s NULL)", sql_type); + run_command(cmd, sql); + for (l = 1; l + offset <= bi_in->var_len; ++l) { + memcpy(alt_text, bi_in->var_text, l); + memcpy(s, bi_in->var_text, l); + alt_text[l] = s[l] = '\0'; + bi.var_text = alt_text; + bi.var_len = l + offset; + if (bi.host_type == CS_VARCHAR_TYPE) + ((CS_VARCHAR *) bi.var_addr)->len = l; + run_command(cmd, "TRUNCATE TABLE #t"); + test_one_case(conn, cmd, &bi, sql_abbrev, false); + run_command(cmd, "TRUNCATE TABLE #t"); + s[l >> 1] = '!'; + alt_text[l >> 1] = '!'; + test_one_case(conn, cmd, &bi, sql_abbrev, false); + if (l > 1) { + run_command(cmd, "TRUNCATE TABLE #t"); + alt_text[(l >> 1) - 1] = ' '; + s[(l >> 1) - 1] = '\0'; + test_one_case(conn, cmd, &bi, sql_abbrev, false); + } + } + run_command(cmd, "DROP TABLE #t"); + free(alt_text); + free((void *) bi.var_addr); +} + +static void +anychar_tests(CS_CONNECTION * conn, CS_COMMAND * cmd, + const struct bi_input * bi) +{ + routine_tests(conn, cmd, bi); + c2b_tests(conn, cmd, bi, "BINARY(1)", "B(1)"); + c2b_tests(conn, cmd, bi, "VARBINARY(1)", "VB(1)"); + c2b_tests(conn, cmd, bi, "BINARY(2)", "B(2)"); + c2b_tests(conn, cmd, bi, "VARBINARY(2)", "VB(2)"); + c2b_tests(conn, cmd, bi, "BINARY(3)", "B(3)"); + c2b_tests(conn, cmd, bi, "VARBINARY(3)", "VB(3)"); + c2b_tests(conn, cmd, bi, "BINARY(4)", "B(4)"); + c2b_tests(conn, cmd, bi, "VARBINARY(4)", "VB(4)"); +} + +static void +char_tests(CS_CONNECTION * conn, CS_COMMAND * cmd, const char * s) +{ + struct bi_input bi = + { CS_CHAR_TYPE, CS_NULLTERM, strlen(s) + 1, s, s, strlen(s) }; + anychar_tests(conn, cmd, &bi); + bi.host_type = CS_LONGCHAR_TYPE; + anychar_tests(conn, cmd, &bi); +#if 0 + bi.host_type = CS_UNICHAR_TYPE; + anychar_tests(conn, cmd, &bi); +#endif +} + +static void +varchar_tests(CS_CONNECTION * conn, CS_COMMAND * cmd, const CS_VARCHAR * vc) +{ + struct bi_input bi = + { CS_VARCHAR_TYPE, CS_UNUSED, vc->len + sizeof(vc->len) + 1, + vc->str, vc, vc->len + sizeof(vc->len) }; + anychar_tests(conn, cmd, &bi); +} + + +static void +b2c_tests(CS_CONNECTION * conn, CS_COMMAND * cmd, + const struct bi_input * bi_in, const char * sql_type, + const char * sql_abbrev) +{ + char sql[64]; + struct bi_input bi; + char * alt_text; + CS_SMALLINT l, offset; + + memcpy(&bi, bi_in, sizeof(bi)); + bi.var_addr = malloc(bi.var_len); + memcpy((void *) bi.var_addr, bi_in->var_addr, bi.var_len); + if (bi.host_type == CS_VARBINARY_TYPE) { + offset = sizeof(CS_SMALLINT); + } else { + offset = 0; + } + alt_text = malloc((bi_in->host_maxlen - offset) * 3); + bi.var_text = alt_text; + + snprintf(sql, sizeof(sql), "CREATE TABLE #t (x %s NULL)", sql_type); + run_command(cmd, sql); + for (l = 1 + offset; l <= bi_in->var_len; ++l) { + memcpy(alt_text, bi_in->var_text, (l - offset) * 3 - 1); + alt_text[(l - offset) * 3 - 1] = '\0'; + bi.var_len = l; + if (bi.host_type == CS_VARBINARY_TYPE) + ((CS_VARBINARY *) bi.var_addr)->len = l - offset; + run_command(cmd, "TRUNCATE TABLE #t"); + test_one_case(conn, cmd, &bi, sql_abbrev, false); + } + run_command(cmd, "DROP TABLE #t"); + free(alt_text); + free((void *) bi.var_addr); +} + +static void +anybin_tests(CS_CONNECTION * conn, CS_COMMAND * cmd, struct bi_input * bi_in) +{ + struct bi_input bi; + routine_tests(conn, cmd, bi_in); + memcpy(&bi, bi_in, sizeof(bi)); + b2c_tests(conn, cmd, bi_in, "CHAR(2)", "C(2)"); + b2c_tests(conn, cmd, bi_in, "VARCHAR(2)", "VC(2)"); + b2c_tests(conn, cmd, bi_in, "CHAR(3)", "C(3)"); + b2c_tests(conn, cmd, bi_in, "VARCHAR(3)", "VC(3)"); + b2c_tests(conn, cmd, bi_in, "CHAR(4)", "C(4)"); + b2c_tests(conn, cmd, bi_in, "VARCHAR(4)", "VC(4)"); + b2c_tests(conn, cmd, bi_in, "CHAR(5)", "C(5)"); + b2c_tests(conn, cmd, bi_in, "VARCHAR(5)", "VC(5)"); + b2c_tests(conn, cmd, bi_in, "CHAR(6)", "C(6)"); + b2c_tests(conn, cmd, bi_in, "VARCHAR(6)", "VC(6)"); + b2c_tests(conn, cmd, bi_in, "CHAR(7)", "C(7)"); + b2c_tests(conn, cmd, bi_in, "VARCHAR(7)", "VC(7)"); + b2c_tests(conn, cmd, bi_in, "CHAR(8)", "C(8)"); + b2c_tests(conn, cmd, bi_in, "VARCHAR(8)", "VC(8)"); +} + +static void +bin_tests(CS_CONNECTION * conn, CS_COMMAND * cmd, const void * p, CS_INT l) +{ + char * s = bin_str(p, l); + struct bi_input bi = { CS_BINARY_TYPE, CS_UNUSED, l, s, p, l }; + anybin_tests(conn, cmd, &bi); + bi.host_type = CS_LONGBINARY_TYPE; + anybin_tests(conn, cmd, &bi); + free(s); +} + +static void +varbin_tests(CS_CONNECTION * conn, CS_COMMAND * cmd, const CS_VARBINARY * vb) +{ + char * s = bin_str(vb->array, vb->len); + CS_SMALLINT tl = vb->len + sizeof(vb->len); + struct bi_input bi = { CS_VARBINARY_TYPE, CS_UNUSED, tl, s, vb, tl }; + anybin_tests(conn, cmd, &bi); + free(s); +} + +#define PRIMITIVE_TESTS_EX(tag, value, s) \ + do { \ + CS_##tag _value = (value); \ + struct bi_input bi = \ + { CS_##tag##_TYPE, CS_UNUSED, sizeof(CS_##tag), \ + s, &_value, sizeof(CS_##tag) }; \ + routine_tests(conn, cmd, &bi); \ + } while (0) + +#define PRIMITIVE_TESTS(tag, value) PRIMITIVE_TESTS_EX(tag, value, #value) + +#define COMPOUND_TESTS(tag, s, ...) \ + do { \ + CS_##tag _value = { __VA_ARGS__ }; \ + struct bi_input bi = \ + { CS_##tag##_TYPE, CS_UNUSED, sizeof(CS_##tag), \ + s, &_value, sizeof(CS_##tag) }; \ + routine_tests(conn, cmd, &bi); \ + } while (0) + +int +main(int argc TDS_UNUSED, char** argv TDS_UNUSED) +{ + CS_CONTEXT *ctx; + CS_CONNECTION *conn; + CS_COMMAND *cmd; + int i; + + check_call(try_ctlogin, (&ctx, &conn, &cmd, false)); + check_call(cs_config, (ctx, CS_SET, CS_MESSAGE_CB, + (CS_VOID*) record_cslibmsg, CS_UNUSED, NULL)); + check_call(ct_callback, (ctx, NULL, CS_SET, CS_CLIENTMSG_CB, + (CS_VOID*) record_ctlibmsg)); + /* Compensate for Sybase ct_callback semantics */ + check_call(ct_callback, (NULL, conn, CS_SET, CS_CLIENTMSG_CB, + (CS_VOID*) record_ctlibmsg)); + + printf("# FROM\tTO\tBIND\tROWXFER\tVALUES\n"); + char_tests(conn, cmd, "abcde12345"); + char_tests(conn, cmd, "0x123456789a"); + { + CS_VARCHAR vc = { 10, "abcde12345" }; + varchar_tests(conn, cmd, &vc); + } + { + CS_VARCHAR vc = { 12, "0x123456789a" }; + varchar_tests(conn, cmd, &vc); + } + { + CS_BYTE bin[] = { 0x34, 0x56, 0x78 }; + bin_tests(conn, cmd, bin, sizeof(bin)); + } + { + CS_VARBINARY vb = { 3, { 0x34, 0x56, 0x78 } }; + varbin_tests(conn, cmd, &vb); + } + PRIMITIVE_TESTS(BIT, true); + COMPOUND_TESTS(DATETIME, "2003-12-17T15:44", 37970, 944 * 18000); + COMPOUND_TESTS(DATETIME4, "2003-12-17T15:44", 37970, 944); + COMPOUND_TESTS(MONEY, "$12.34", 0, 123400); + COMPOUND_TESTS(MONEY4, "$12.34", 123400); + PRIMITIVE_TESTS(FLOAT, 12.34); + PRIMITIVE_TESTS(REAL, 12.34f); + COMPOUND_TESTS(DECIMAL, "12.34", 4, 2, { 0, 1234 / 256, 1234 % 256 }); + COMPOUND_TESTS(NUMERIC, "12.34", 4, 2, { 0, 1234 / 256, 1234 % 256 }); + PRIMITIVE_TESTS(INT, 1234); + PRIMITIVE_TESTS(SMALLINT, 1234); + PRIMITIVE_TESTS(TINYINT, 123); + PRIMITIVE_TESTS(LONG, 1234); + PRIMITIVE_TESTS_EX(DATE, 37970, "2003-12-17"); + PRIMITIVE_TESTS_EX(TIME, 944 * 18000, "15:44"); +#ifdef CS_BIGINT_TYPE + PRIMITIVE_TESTS(BIGINT, 1234); +#endif + PRIMITIVE_TESTS(USHORT, 1234); +#ifdef CS_UINT_TYPE + PRIMITIVE_TESTS(USMALLINT, 123); + PRIMITIVE_TESTS(UINT, 1234); + PRIMITIVE_TESTS(UBIGINT, 1234); +#endif +#ifdef CS_BIGDATETIME_TYPE + PRIMITIVE_TESTS_EX(BIGDATETIME, + ((693961 + 37970) * 86400UL + 944 * 60) * 1000000, + "2003-12-17T15:44"); + PRIMITIVE_TESTS_EX(BIGTIME, 944 * 60000000UL, "15:44"); +#endif + + check_call(try_ctlogout, (ctx, conn, cmd, false)); + + printf("\n"); + for (i = 0; + i < sizeof(error_descriptions) / sizeof(*error_descriptions); + ++i) { + if (error_descriptions[i]) { + printf("%d:%d: %s\n", + (i >> 8) + 1, i & 255, error_descriptions[i]); + } + } + + return 0; +} diff --git a/src/dblib/unittests/CMakeLists.txt b/src/dblib/unittests/CMakeLists.txt index e0c4b2b89f..1a0286c212 100644 --- a/src/dblib/unittests/CMakeLists.txt +++ b/src/dblib/unittests/CMakeLists.txt @@ -5,7 +5,7 @@ foreach(target t0001 t0002 t0003 t0004 t0005 t0006 t0007 t0008 t0009 dbsafestr t0022 t0023 rpc dbmorecmds bcp thread text_buffer done_handling timeout hang null null2 setnull numeric pending cancel spid canquery batch_stmt_ins_sel batch_stmt_ins_upd bcp_getl - empty_rowsets string_bind colinfo bcp2 proc_limit) + empty_rowsets string_bind colinfo bcp2 bcp3 proc_limit) add_executable(d_${target} EXCLUDE_FROM_ALL ${target}.c) set_target_properties(d_${target} PROPERTIES OUTPUT_NAME ${target}) target_link_libraries(d_${target} d_common sybdb replacements tdsutils ${lib_NETWORK} ${lib_BASE}) diff --git a/src/dblib/unittests/Makefile.am b/src/dblib/unittests/Makefile.am index 11987f4c7a..b2e0b9ec1a 100644 --- a/src/dblib/unittests/Makefile.am +++ b/src/dblib/unittests/Makefile.am @@ -44,6 +44,7 @@ TESTS = \ string_bind$(EXEEXT) \ colinfo$(EXEEXT) \ bcp2$(EXEEXT) \ + bcp3$(EXEEXT) \ proc_limit$(EXEEXT) check_PROGRAMS = $(TESTS) @@ -99,6 +100,7 @@ empty_rowsets_SOURCES = empty_rowsets.c empty_rowsets.sql string_bind_SOURCES = string_bind.c colinfo_SOURCES = colinfo.c colinfo.sql bcp2_SOURCES = bcp2.c bcp2.sql +bcp3_SOURCES = bcp3.c proc_limit_SOURCES = proc_limit.c noinst_LIBRARIES = libcommon.a diff --git a/src/dblib/unittests/bcp3.c b/src/dblib/unittests/bcp3.c new file mode 100755 index 0000000000..8b97aff6a2 --- /dev/null +++ b/src/dblib/unittests/bcp3.c @@ -0,0 +1,473 @@ +/* + * Purpose: Test bcp truncation + * Functions: bcp_batch bcp_bind bcp_done bcp_init bcp_sendrow + */ + +#include "common.h" + +#include + +static DBINT last_error; +static const char * error_descriptions[32768]; + +struct full_result +{ + RETCODE rc; + DBINT error; // zero if none +}; + +struct bi_input +{ + int type; + BYTE * terminator; + int termlen; + const char * var_text; + const BYTE * var_addr; + DBINT var_len; +}; + + +static int +record_msg(DBPROCESS * dbproc TDS_UNUSED, DBINT msgno, + int msgstate TDS_UNUSED, int severity TDS_UNUSED, + char *msgtext, char *srvname TDS_UNUSED, char *procname TDS_UNUSED, + int line TDS_UNUSED) +{ + last_error = msgno; + if (error_descriptions[msgno] == NULL) { + error_descriptions[msgno] = strdup(msgtext); + } + return 0; +} + +static int +record_err(DBPROCESS * dbproc TDS_UNUSED, int severity TDS_UNUSED, + int dberr, int oserr TDS_UNUSED, char *dberrstr, + char *oserrstr TDS_UNUSED) +{ + last_error = dberr; + if (error_descriptions[dberr] == NULL) { + error_descriptions[dberr] = strdup(dberrstr); + } + return INT_CANCEL; +} + + +#define capture_result(fr, f, args) \ + do { \ + struct full_result *_fr = (fr); \ + last_error = 0; \ + _fr->rc = f args; \ + _fr->error = last_error; \ + } while(0) + +static void +print_full_result(const struct full_result * fr) +{ + switch (fr->rc) { + case SUCCEED: fputs(" +", stdout); break; + case FAIL: fputs("- ", stdout); break; + case BUF_FULL: fputs("-B", stdout); break; + default: fputs("-?", stdout); break; + } + if (fr->error) { + printf("%d", fr->error); + } +} + + +static RETCODE +do_bind(DBPROCESS * dbproc, struct bi_input * bi, bool is_null) +{ + return bcp_bind(dbproc, (BYTE *) bi->var_addr, 0 /* prefixlen */, + is_null ? 0 : bi->var_len, bi->terminator, + bi->termlen, bi->type, 1 /* colnum */); +} + + +static char * +bin_str(const void * p, DBINT l) +{ + /* Slight overkill, but simplifies logic */ + char * s = malloc(l * 3 + 1); + int i; + for (i = 0; i < l; ++i) { + snprintf(s + 3 * i, 4, "%02x ", ((unsigned char *) p)[i]); + } + s[l * 3 - 1] = '\0'; + return s; +} + + +static void +do_fetch(DBPROCESS * dbproc, int type) +{ + BYTE buffer[256]; + RETCODE rc; + + rc = dbbind(dbproc, 1, type, sizeof(buffer), buffer); + if (rc != SUCCEED) { + fputs(" [FAIL]", stdout); + return; + } + while ((rc = dbnextrow(dbproc)) == REG_ROW) { + int len = dbdatlen(dbproc, 1); + if (type == CHARBIND) { + printf(" '%.*s'", len, buffer); + } else if (len > 0) { + char * s = bin_str(buffer, len); + printf(" %s", s); + free(s); + } + } + if (rc != NO_MORE_ROWS) + fputs(" [FAIL]", stdout); +} + +static void +do_query(DBPROCESS * dbproc, const char * query, int type) +{ + dbcmd(dbproc, query); + dbsqlexec(dbproc); + while (dbresults(dbproc) == SUCCEED) { + do_fetch(dbproc, type); + } +} + +static const char * +host_type_name(int host_type) +{ + switch (host_type) { + case SYBCHAR: return "CHAR"; + case SYBVARCHAR: return "VARCHAR"; + case SYBINT1: return "INT1"; + case SYBINT2: return "INT2"; + case SYBINT4: return "INT4"; +#ifdef SYBINT8 + case SYBINT8: return "INT8"; +#endif + case SYBFLT8: return "FLT8"; + case SYBDATETIME: return "DT"; + case SYBBIT: return "BIT"; + case SYBTEXT: return "TEXT"; +#ifdef SYBNTEXT + case SYBNTEXT: return "NTEXT"; +#endif + case SYBIMAGE: return "IMAGE"; + case SYBMONEY4: return "MONEY4"; + case SYBMONEY: return "MONEY"; + case SYBDATETIME4: return "DT4"; + case SYBREAL: return "REAL"; + case SYBBINARY: return "BINARY"; + case SYBVOID: return "VOID"; + case SYBVARBINARY: return "VARBIN"; + case SYBNUMERIC: return "NUMERIC"; + case SYBDECIMAL: return "DECIMAL"; +#ifdef SYBNVARCHAR + case SYBNVARCHAR: return "NVRCHAR"; +#endif +#ifdef SYBDATE + case SYBDATE: return "DATE"; + case SYBTIME: return "TIME"; + case SYBBIGDATETIME: return "BIGDT"; + case SYBBIGTIME: return "BIGTIME"; + case SYBMSDATE: return "MSDATE"; + case SYBMSTIME: return "MSTIME"; + case SYBMSDATETIME2: return "MSDT2"; + case SYBMSDATETIMEOFFSET: return "MSDTO"; +#endif + } + return "???"; +} + +static void +test_one_case(DBPROCESS * dbproc, struct bi_input * bi, + const char *sql_abbrev, int result_type, bool is_null) +{ + struct full_result fr; + DBINT count; + + printf("%s\t%s\t", host_type_name(bi->type), sql_abbrev); + bcp_init(dbproc, "#t", NULL, NULL, DB_IN); + capture_result(&fr, do_bind, (dbproc, bi, is_null)); + print_full_result(&fr); + putchar('\t'); + capture_result(&fr, bcp_sendrow, (dbproc)); + print_full_result(&fr); + count = bcp_batch(dbproc); + bcp_done(dbproc); + if (is_null) { + fputs("\tNULL ->", stdout); + } else { + printf("\t%s ->", bi->var_text); + } + if (count) { + do_query(dbproc, "SELECT x FROM #t", result_type); + } else { + fputs(" N/A", stdout); + } + putchar('\n'); + fflush(stdout); +} + +static void +run_command(DBPROCESS * dbproc, const char * sql) +{ + dbcmd(dbproc, sql); + dbsqlexec(dbproc); + while (dbresults(dbproc) != NO_MORE_RESULTS) { + /* nop */ + } + +} + +static void +test_case(DBPROCESS * dbproc, struct bi_input * bi, const char * sql_type, + const char * sql_abbrev, int result_type) +{ + char sql[64]; + snprintf(sql, sizeof(sql), "CREATE TABLE #t (x %s NULL)", sql_type); + run_command(dbproc, sql); + test_one_case(dbproc, bi, sql_abbrev, result_type, false); + run_command(dbproc, "TRUNCATE TABLE #t"); + test_one_case(dbproc, bi, sql_abbrev, result_type, true); + run_command(dbproc, "DROP TABLE #t"); +} + +static void +routine_tests(DBPROCESS * dbproc, struct bi_input * bi) +{ + test_case(dbproc, bi, "BINARY(1)", "B(1)", BINARYBIND); + test_case(dbproc, bi, "VARBINARY(1)", "VB(1)", BINARYBIND); +#ifndef TDS_STATIC_CAST /* avoid crashes with commercial libraries */ + if (bi->type != SYBDECIMAL && bi->type != SYBNUMERIC) +#endif + { + test_case(dbproc, bi, "BINARY(64)", "B(64)", BINARYBIND); + test_case(dbproc, bi, "VARBINARY(64)", "VB(64)", BINARYBIND); + } + test_case(dbproc, bi, "CHAR(1)", "C(1)", CHARBIND); + test_case(dbproc, bi, "VARCHAR(1)", "VC(1)", CHARBIND); +#ifndef TDS_STATIC_CAST + if (bi->type != SYBDECIMAL && bi->type != SYBNUMERIC) +#endif + { + test_case(dbproc, bi, "CHAR(64)", "C(64)", CHARBIND); + test_case(dbproc, bi, "VARCHAR(64)", "VC(64)", CHARBIND); + } +} + + +static void +c2b_tests(DBPROCESS * dbproc, const struct bi_input * bi_in, + const char * sql_type, const char * sql_abbrev) +{ + char sql[64]; + struct bi_input bi; + char * alt_text; + int l; + + memcpy(&bi, bi_in, sizeof(bi)); + bi.var_addr = malloc(bi_in->var_len + 1); + memcpy((void *) bi.var_addr, bi_in->var_addr, bi.var_len); + alt_text = malloc(bi_in->var_len + 1); + + snprintf(sql, sizeof(sql), "CREATE TABLE #t (x %s NULL)", sql_type); + run_command(dbproc, sql); + for (l = 1; l <= bi_in->var_len; ++l) { + char * s = (char *) bi.var_addr; + memcpy(alt_text, bi_in->var_text, l); + memcpy(s, bi_in->var_text, l); + alt_text[l] = s[l] = '\0'; + bi.var_text = alt_text; + bi.var_len = l; + run_command(dbproc, "TRUNCATE TABLE #t"); + test_one_case(dbproc, &bi, sql_abbrev, BINARYBIND, false); + run_command(dbproc, "TRUNCATE TABLE #t"); + s[l >> 1] = '!'; + alt_text[l >> 1] = '!'; + test_one_case(dbproc, &bi, sql_abbrev, BINARYBIND, false); + if (l > 1) { + run_command(dbproc, "TRUNCATE TABLE #t"); + alt_text[(l >> 1) - 1] = ' '; + s[(l >> 1) - 1] = '\0'; + test_one_case(dbproc, &bi, sql_abbrev, BINARYBIND, + false); + } + } + run_command(dbproc, "DROP TABLE #t"); + free(alt_text); + free((void *) bi.var_addr); +} + +static void +char_tests(DBPROCESS * dbproc, const char * s) +{ + struct bi_input bi = { SYBCHAR, (BYTE *) "", 1, s, (const BYTE *) s, + strlen(s) }; + routine_tests(dbproc, &bi); + c2b_tests(dbproc, &bi, "BINARY(1)", "B(1)"); + c2b_tests(dbproc, &bi, "VARBINARY(1)", "VB(1)"); + c2b_tests(dbproc, &bi, "BINARY(2)", "B(2)"); + c2b_tests(dbproc, &bi, "VARBINARY(2)", "VB(2)"); + c2b_tests(dbproc, &bi, "BINARY(3)", "B(3)"); + c2b_tests(dbproc, &bi, "VARBINARY(3)", "VB(3)"); + c2b_tests(dbproc, &bi, "BINARY(4)", "B(4)"); + c2b_tests(dbproc, &bi, "VARBINARY(4)", "VB(4)"); +} + + +static void +b2c_tests(DBPROCESS * dbproc, const struct bi_input * bi_in, + const char * sql_type, const char * sql_abbrev) +{ + char sql[64]; + struct bi_input bi; + char * alt_text; + int l; + + memcpy(&bi, bi_in, sizeof(bi)); + bi.var_addr = malloc(bi.var_len); + memcpy((void *) bi.var_addr, bi_in->var_addr, bi.var_len); + alt_text = malloc(bi_in->var_len * 3); + bi.var_text = alt_text; + + snprintf(sql, sizeof(sql), "CREATE TABLE #t (x %s NULL)", sql_type); + run_command(dbproc, sql); + for (l = 1; l <= bi_in->var_len; ++l) { + memcpy(alt_text, bi_in->var_text, l * 3 - 1); + alt_text[l * 3 - 1] = '\0'; + bi.var_len = l; + run_command(dbproc, "TRUNCATE TABLE #t"); + test_one_case(dbproc, &bi, sql_abbrev, CHARBIND, false); + } + run_command(dbproc, "DROP TABLE #t"); + free(alt_text); + free((void *) bi.var_addr); +} + +static void +bin_tests(DBPROCESS * dbproc, const void * p, DBINT l) +{ + char * s = bin_str(p, l); + struct bi_input bi = { SYBBINARY, NULL, 0, s, p, l }; + routine_tests(dbproc, &bi); + b2c_tests(dbproc, &bi, "CHAR(2)", "C(2)"); + b2c_tests(dbproc, &bi, "VARCHAR(2)", "VC(2)"); + b2c_tests(dbproc, &bi, "CHAR(3)", "C(3)"); + b2c_tests(dbproc, &bi, "VARCHAR(3)", "VC(3)"); + b2c_tests(dbproc, &bi, "CHAR(4)", "C(4)"); + b2c_tests(dbproc, &bi, "VARCHAR(4)", "VC(4)"); + b2c_tests(dbproc, &bi, "CHAR(5)", "C(5)"); + b2c_tests(dbproc, &bi, "VARCHAR(5)", "VC(5)"); + b2c_tests(dbproc, &bi, "CHAR(6)", "C(6)"); + b2c_tests(dbproc, &bi, "VARCHAR(6)", "VC(6)"); + b2c_tests(dbproc, &bi, "CHAR(7)", "C(7)"); + b2c_tests(dbproc, &bi, "VARCHAR(7)", "VC(7)"); + b2c_tests(dbproc, &bi, "CHAR(8)", "C(8)"); + b2c_tests(dbproc, &bi, "VARCHAR(8)", "VC(8)"); + free(s); +} + +#define PRIMITIVE_TESTS_EX(tag, type, value, s) \ + do { \ + DB##type _value = (value); \ + struct bi_input bi = \ + { SYB##tag, NULL, 0, s, (const BYTE *) &_value, -1 }; \ + routine_tests(dbproc, &bi); \ + } while (0) + +#define PRIMITIVE_TESTS(tag, type, value) \ + PRIMITIVE_TESTS_EX(tag, type, value, #value) + +#define COMPOUND_TESTS(tag, s, ...) \ + do { \ + DB##tag _value = { __VA_ARGS__ }; \ + struct bi_input bi = \ + { SYB##tag, NULL, 0, s, (const BYTE *) &_value, -1 }; \ + routine_tests(dbproc, &bi); \ + } while (0) + +int +main(int argc, char **argv) +{ + LOGINREC *login; + DBPROCESS *dbproc; + int i; + + set_malloc_options(); + + read_login_info(argc, argv); + + printf("Starting %s\n", argv[0]); + + dbsetversion(DBVERSION_100); + dbinit(); + + dberrhandle(syb_err_handler); + dbmsghandle(syb_msg_handler); + + printf("About to logon\n"); + + login = dblogin(); + DBSETLPWD(login, PASSWORD); + DBSETLUSER(login, USER); + DBSETLAPP(login, "bcp3.c unit test"); + BCP_SETL(login, 1); + + printf("About to open %s.%s\n", SERVER, DATABASE); + + dbproc = dbopen(login, SERVER); + if (strlen(DATABASE)) + dbuse(dbproc, DATABASE); + dbloginfree(login); + + dberrhandle(record_err); + dbmsghandle(record_msg); + + printf("# FROM\tTO\tBIND\tROWXFER\tVALUES\n"); + char_tests(dbproc, "abcde12345"); + char_tests(dbproc, "0x123456789a"); + { + BYTE bin[] = { 0x34, 0x56, 0x78 }; + bin_tests(dbproc, bin, sizeof(bin)); + } + PRIMITIVE_TESTS(BIT, BIT, true); + COMPOUND_TESTS(DATETIME, "2003-12-17T15:44", 37970, 944 * 18000); + COMPOUND_TESTS(DATETIME4, "2003-12-17T15:44", 37970, 944); + COMPOUND_TESTS(MONEY, "$12.34", 0, 123400); + COMPOUND_TESTS(MONEY4, "$12.34", 123400); + PRIMITIVE_TESTS(FLT8, FLT8, 12.34); + PRIMITIVE_TESTS(REAL, REAL, 12.34f); + COMPOUND_TESTS(DECIMAL, "12.34", 4, 2, { 0, 1234 / 256, 1234 % 256 }); + COMPOUND_TESTS(NUMERIC, "12.34", 4, 2, { 0, 1234 / 256, 1234 % 256 }); + PRIMITIVE_TESTS(INT4, INT, 1234); + PRIMITIVE_TESTS(INT2, SMALLINT, 1234); + PRIMITIVE_TESTS(INT1, TINYINT, 123); +#ifdef SYBDATE + PRIMITIVE_TESTS_EX(DATE, INT, 37970, "2003-12-17"); + PRIMITIVE_TESTS_EX(TIME, INT, 944 * 18000, "15:44"); +#endif +#ifdef SYBINT8 + PRIMITIVE_TESTS(INT8, BIGINT, 1234); +#endif +#ifdef SYBBIGDATETIME + PRIMITIVE_TESTS_EX(BIGDATETIME, BIGINT, + ((693961 + 37970) * 86400UL + 944 * 60) * 1000000, + "2003-12-17T15:44"); + PRIMITIVE_TESTS_EX(BIGTIME, BIGINT, 944 * 60000000UL, "15:44"); +#endif + + dbexit(); + + printf("\n"); + for (i = 0; + i < sizeof(error_descriptions) / sizeof(*error_descriptions); + ++i) { + if (error_descriptions[i]) { + printf("%d: %s\n", i, error_descriptions[i]); + } + } + + return 0; +}