#include "sqliteInt.h" #include "unity.h" #include #include /* The wrapper for the static function is provided by the module build. */ extern void test_dropColumnFunc(sqlite3_context *context, int NotUsed, sqlite3_value **argv); static sqlite3 *gDb = NULL; /* UDF trampoline that forwards directly to the function-under-test. */ static void udf_call_drop_column(sqlite3_context *ctx, int argc, sqlite3_value **argv){ /* Expect exactly 3 arguments as specified by dropColumnFunc. */ if( argc!=3 ){ sqlite3_result_error_code(ctx, SQLITE_MISUSE); return; } test_dropColumnFunc(ctx, 0, argv); } /* Helper: Execute SELECT call_drop_column(?, ?, ?) and capture result text or error. */ static int run_drop_and_capture( int iSchema, /* argv[0] */ const char *zCreate, /* argv[1] - may be NULL */ int iCol, /* argv[2] */ char **pzResult, /* OUT: sqlite3_malloc'ed copy (via sqlite3_mprintf) of result text if SQLITE_ROW */ int *pDbErr /* OUT: sqlite3_errcode(db) if not SQLITE_ROW, else SQLITE_OK */ ){ sqlite3_stmt *pStmt = NULL; int rcPrep = sqlite3_prepare_v2(gDb, "SELECT call_drop_column(?, ?, ?)", -1, &pStmt, 0); if( rcPrep!=SQLITE_OK ){ if( pDbErr ) *pDbErr = rcPrep; return rcPrep; } sqlite3_bind_int(pStmt, 1, iSchema); if( zCreate ){ sqlite3_bind_text(pStmt, 2, zCreate, -1, SQLITE_TRANSIENT); }else{ sqlite3_bind_null(pStmt, 2); } sqlite3_bind_int(pStmt, 3, iCol); int rcStep = sqlite3_step(pStmt); if( rcStep==SQLITE_ROW ){ const unsigned char *z = sqlite3_column_text(pStmt, 0); if( z && pzResult ){ *pzResult = sqlite3_mprintf("%s", z); } if( pDbErr ) *pDbErr = SQLITE_OK; }else{ if( pDbErr ) *pDbErr = sqlite3_errcode(gDb); } sqlite3_finalize(pStmt); return rcStep; } void setUp(void) { int rc = sqlite3_open(":memory:", &gDb); TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc); sqlite3_extended_result_codes(gDb, 1); rc = sqlite3_create_function(gDb, "call_drop_column", 3, SQLITE_UTF8, NULL, udf_call_drop_column, NULL, NULL); TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc); } void tearDown(void) { if( gDb ){ sqlite3_close(gDb); gDb = NULL; } } /* Simple: drop first column */ void test_dropColumnFunc_drop_first_simple(void){ char *zOut = NULL; int err = SQLITE_OK; int rc = run_drop_and_capture(0, "CREATE TABLE t(a,b,c)", 0, &zOut, &err); TEST_ASSERT_EQUAL_INT(SQLITE_ROW, rc); TEST_ASSERT_EQUAL_INT(SQLITE_OK, err); TEST_ASSERT_NOT_NULL(zOut); TEST_ASSERT_EQUAL_STRING("CREATE TABLE t(b,c)", zOut); sqlite3_free(zOut); } /* Simple: drop middle column */ void test_dropColumnFunc_drop_middle_simple(void){ char *zOut = NULL; int err = SQLITE_OK; int rc = run_drop_and_capture(0, "CREATE TABLE t(a,b,c)", 1, &zOut, &err); TEST_ASSERT_EQUAL_INT(SQLITE_ROW, rc); TEST_ASSERT_EQUAL_INT(SQLITE_OK, err); TEST_ASSERT_NOT_NULL(zOut); TEST_ASSERT_EQUAL_STRING("CREATE TABLE t(a,c)", zOut); sqlite3_free(zOut); } /* Simple: drop last column */ void test_dropColumnFunc_drop_last_simple(void){ char *zOut = NULL; int err = SQLITE_OK; int rc = run_drop_and_capture(0, "CREATE TABLE t(a,b,c)", 2, &zOut, &err); TEST_ASSERT_EQUAL_INT(SQLITE_ROW, rc); TEST_ASSERT_EQUAL_INT(SQLITE_OK, err); TEST_ASSERT_NOT_NULL(zOut); TEST_ASSERT_EQUAL_STRING("CREATE TABLE t(a,b)", zOut); sqlite3_free(zOut); } /* Drop last column when previous column has nested parentheses and commas in constraints */ void test_dropColumnFunc_drop_last_with_nested_constraints(void){ const char *zCreate = "CREATE TABLE t(a, b TEXT CHECK(b IN (1,2,3) AND (b IS NOT NULL)), c)"; char *zOut = NULL; int err = SQLITE_OK; int rc = run_drop_and_capture(0, zCreate, 2, &zOut, &err); TEST_ASSERT_EQUAL_INT(SQLITE_ROW, rc); TEST_ASSERT_EQUAL_INT(SQLITE_OK, err); TEST_ASSERT_NOT_NULL(zOut); TEST_ASSERT_EQUAL_STRING( "CREATE TABLE t(a, b TEXT CHECK(b IN (1,2,3) AND (b IS NOT NULL)))", zOut); sqlite3_free(zOut); } /* Error: invalid column index (>= nCol) */ void test_dropColumnFunc_invalid_index_error(void){ int err = SQLITE_OK; int rc = run_drop_and_capture(0, "CREATE TABLE t(a,b)", 5, NULL, &err); TEST_ASSERT_EQUAL_INT(SQLITE_ERROR, rc); TEST_ASSERT_EQUAL_INT(SQLITE_CORRUPT, err); } /* Error: single-column table cannot drop (nCol==1) */ void test_dropColumnFunc_single_column_error(void){ int err = SQLITE_OK; int rc = run_drop_and_capture(0, "CREATE TABLE t(a)", 0, NULL, &err); TEST_ASSERT_EQUAL_INT(SQLITE_ERROR, rc); TEST_ASSERT_EQUAL_INT(SQLITE_CORRUPT, err); } /* Error: SQL not starting with CREATE should produce corruption error */ void test_dropColumnFunc_invalid_sql_error(void){ int err = SQLITE_OK; int rc = run_drop_and_capture(0, "DROP TABLE t", 0, NULL, &err); TEST_ASSERT_EQUAL_INT(SQLITE_ERROR, rc); TEST_ASSERT_EQUAL_INT(SQLITE_CORRUPT, err); } /* Temp schema path (iSchema==1) */ void test_dropColumnFunc_temp_schema(void){ char *zOut = NULL; int err = SQLITE_OK; int rc = run_drop_and_capture(1, "CREATE TABLE t(a,b)", 1, &zOut, &err); TEST_ASSERT_EQUAL_INT(SQLITE_ROW, rc); TEST_ASSERT_EQUAL_INT(SQLITE_OK, err); TEST_ASSERT_NOT_NULL(zOut); TEST_ASSERT_EQUAL_STRING("CREATE TABLE t(a)", zOut); sqlite3_free(zOut); } /* Quoted identifiers and spacing preserved */ void test_dropColumnFunc_preserves_quotes_and_spacing(void){ const char *zCreate = "CREATE TABLE \"My T\"( \"a1\" INTEGER PRIMARY KEY, \"b-2\" TEXT, c )"; char *zOut = NULL; int err = SQLITE_OK; int rc = run_drop_and_capture(0, zCreate, 1, &zOut, &err); TEST_ASSERT_EQUAL_INT(SQLITE_ROW, rc); TEST_ASSERT_EQUAL_INT(SQLITE_OK, err); TEST_ASSERT_NOT_NULL(zOut); TEST_ASSERT_EQUAL_STRING( "CREATE TABLE \"My T\"( \"a1\" INTEGER PRIMARY KEY, c )", zOut); sqlite3_free(zOut); } /* NULL SQL argument -> treated as SQLITE_NOMEM by renameParseSql */ void test_dropColumnFunc_null_sql_error(void){ int err = SQLITE_OK; int rc = run_drop_and_capture(0, NULL, 0, NULL, &err); TEST_ASSERT_EQUAL_INT(SQLITE_ERROR, rc); TEST_ASSERT_EQUAL_INT(SQLITE_NOMEM, err); } int main(void) { UNITY_BEGIN(); RUN_TEST(test_dropColumnFunc_drop_first_simple); RUN_TEST(test_dropColumnFunc_drop_middle_simple); RUN_TEST(test_dropColumnFunc_drop_last_simple); RUN_TEST(test_dropColumnFunc_drop_last_with_nested_constraints); RUN_TEST(test_dropColumnFunc_invalid_index_error); RUN_TEST(test_dropColumnFunc_single_column_error); RUN_TEST(test_dropColumnFunc_invalid_sql_error); RUN_TEST(test_dropColumnFunc_temp_schema); RUN_TEST(test_dropColumnFunc_preserves_quotes_and_spacing); RUN_TEST(test_dropColumnFunc_null_sql_error); return UNITY_END(); }