#include "sqliteInt.h" #include "unity.h" #include #include #include /* The wrapper for the static function is provided by the module under test. */ extern void test_addConstraintFunc(sqlite3_context*, int, sqlite3_value**); static sqlite3 *gDb = NULL; static const char *gFuncName = "test_addc"; /* Helper to register the function under a SQL name on gDb */ static void register_add_constraint_func(void){ int rc = sqlite3_create_function(gDb, gFuncName, 3, SQLITE_UTF8, 0, test_addConstraintFunc, 0, 0); TEST_ASSERT_EQUAL_INT_MESSAGE(SQLITE_OK, rc, "sqlite3_create_function failed"); } /* Helper to call the function and capture result text or error. Returns a heap-allocated copy of the result text (sqlite3_mprintf) on success (caller must sqlite3_free), or NULL if result is SQL NULL or on error. If out_step_rc is non-NULL, it is set to the return from sqlite3_step. If out_errcode is non-NULL, it is set to sqlite3_errcode(gDb) after step. */ static char* call_add_constraint(const char *zCreate, const char *zCons, int iCol, int *out_step_rc, int *out_errcode){ char *zSql = sqlite3_mprintf("SELECT %s(%Q,%Q,%d)", gFuncName, zCreate, zCons, iCol); TEST_ASSERT_NOT_NULL_MESSAGE(zSql, "sqlite3_mprintf returned NULL"); sqlite3_stmt *pStmt = NULL; int rc = sqlite3_prepare_v2(gDb, zSql, -1, &pStmt, 0); sqlite3_free(zSql); TEST_ASSERT_EQUAL_INT_MESSAGE(SQLITE_OK, rc, "sqlite3_prepare_v2 failed"); rc = sqlite3_step(pStmt); if( out_step_rc ) *out_step_rc = rc; if( out_errcode ) *out_errcode = sqlite3_errcode(gDb); char *res = NULL; if( rc==SQLITE_ROW ){ const unsigned char *txt = sqlite3_column_text(pStmt, 0); if( txt ){ res = (char*)sqlite3_mprintf("%s", txt); } } sqlite3_finalize(pStmt); return res; } void setUp(void) { int rc = sqlite3_open(":memory:", &gDb); TEST_ASSERT_EQUAL_INT_MESSAGE(SQLITE_OK, rc, "sqlite3_open failed"); register_add_constraint_func(); } void tearDown(void) { if( gDb ){ sqlite3_close(gDb); gDb = NULL; } } /* Test: insert constraint into the first (0th) column */ void test_addConstraintFunc_adds_to_first_column_simple(void){ int stepRc = 0, err = 0; const char *inSql = "CREATE TABLE t(a, b)"; const char *cons = "CHECK(a>0)"; char *out = call_add_constraint(inSql, cons, 0, &stepRc, &err); TEST_ASSERT_EQUAL_INT(SQLITE_ROW, stepRc); TEST_ASSERT_EQUAL_INT(SQLITE_OK, err); TEST_ASSERT_NOT_NULL(out); TEST_ASSERT_EQUAL_STRING("CREATE TABLE t(a CHECK(a>0), b)", out); sqlite3_free(out); } /* Test: insert constraint into the second (1st) column */ void test_addConstraintFunc_adds_to_second_column_simple(void){ int stepRc = 0, err = 0; const char *inSql = "CREATE TABLE t(a, b)"; const char *cons = "DEFAULT 5"; char *out = call_add_constraint(inSql, cons, 1, &stepRc, &err); TEST_ASSERT_EQUAL_INT(SQLITE_ROW, stepRc); TEST_ASSERT_EQUAL_INT(SQLITE_OK, err); TEST_ASSERT_NOT_NULL(out); TEST_ASSERT_EQUAL_STRING("CREATE TABLE t(a, b DEFAULT 5)", out); sqlite3_free(out); } /* Test: append a table constraint with iCol < 0 (use -1) */ void test_addConstraintFunc_appends_table_constraint_neg1(void){ int stepRc = 0, err = 0; const char *inSql = "CREATE TABLE t(a INTEGER, b TEXT)"; const char *cons = "UNIQUE(a,b)"; char *out = call_add_constraint(inSql, cons, -1, &stepRc, &err); TEST_ASSERT_EQUAL_INT(SQLITE_ROW, stepRc); TEST_ASSERT_EQUAL_INT(SQLITE_OK, err); TEST_ASSERT_NOT_NULL(out); TEST_ASSERT_EQUAL_STRING("CREATE TABLE t(a INTEGER, b TEXT, UNIQUE(a,b))", out); sqlite3_free(out); } /* Test: handles comments and nested parentheses in type, inserts before delimiter. Note: The implementation advances past whitespace/comments before the delimiter, so the inserted constraint appears AFTER any trailing comments and BEFORE the comma. */ void test_addConstraintFunc_handles_comments_and_parens(void){ int stepRc = 0, err = 0; const char *inSql = "CREATE TABLE t( /*c*/ a DECIMAL(10, 5) /*x*/, b TEXT )"; const char *cons = "NOT NULL"; char *out = call_add_constraint(inSql, cons, 0, &stepRc, &err); TEST_ASSERT_EQUAL_INT(SQLITE_ROW, stepRc); TEST_ASSERT_EQUAL_INT(SQLITE_OK, err); TEST_ASSERT_NOT_NULL(out); TEST_ASSERT_EQUAL_STRING( "CREATE TABLE t( /*c*/ a DECIMAL(10, 5) /*x*/ NOT NULL, b TEXT )", out); sqlite3_free(out); } /* Test: nested parentheses inside default expressions */ void test_addConstraintFunc_handles_nested_parens_in_defaults(void){ int stepRc = 0, err = 0; const char *inSql = "CREATE TABLE t(a TEXT DEFAULT (printf('x(%d)', 1)), b INT)"; const char *cons = "CHECK(length(a)>0)"; char *out = call_add_constraint(inSql, cons, 0, &stepRc, &err); TEST_ASSERT_EQUAL_INT(SQLITE_ROW, stepRc); TEST_ASSERT_EQUAL_INT(SQLITE_OK, err); TEST_ASSERT_NOT_NULL(out); TEST_ASSERT_EQUAL_STRING( "CREATE TABLE t(a TEXT DEFAULT (printf('x(%d)', 1)) CHECK(length(a)>0), b INT)", out); sqlite3_free(out); } /* Test: NULL SQL input returns NULL result (no error set) */ void test_addConstraintFunc_null_sql_returns_null(void){ int stepRc = 0, err = 0; char *out = call_add_constraint(NULL, "ANY", 0, &stepRc, &err); TEST_ASSERT_EQUAL_INT(SQLITE_ROW, stepRc); TEST_ASSERT_EQUAL_INT(SQLITE_OK, err); TEST_ASSERT_NULL(out); sqlite3_free(out); /* ok to free NULL */ } /* Test: out-of-range column index triggers SQLITE_CORRUPT via illegal token handling */ void test_addConstraintFunc_out_of_range_column_reports_corrupt(void){ int stepRc = 0, err = 0; /* Only 1 column present, ask for column index 5 */ const char *inSql = "CREATE TABLE t(a)"; char *out = call_add_constraint(inSql, "ZZZ", 5, &stepRc, &err); TEST_ASSERT(out == NULL); TEST_ASSERT_EQUAL_INT(SQLITE_ERROR, stepRc); TEST_ASSERT_EQUAL_INT(SQLITE_CORRUPT, err); } /* Test: malformed SQL (unterminated string) triggers SQLITE_CORRUPT */ void test_addConstraintFunc_malformed_sql_reports_corrupt(void){ int stepRc = 0, err = 0; /* Unterminated string literal inside column definition */ const char *inSql = "CREATE TABLE t(a DEFAULT 'unterminated, b INT)"; char *out = call_add_constraint(inSql, "NOT NULL", 0, &stepRc, &err); TEST_ASSERT(out == NULL); TEST_ASSERT_EQUAL_INT(SQLITE_ERROR, stepRc); TEST_ASSERT_EQUAL_INT(SQLITE_CORRUPT, err); } int main(void) { UNITY_BEGIN(); RUN_TEST(test_addConstraintFunc_adds_to_first_column_simple); RUN_TEST(test_addConstraintFunc_adds_to_second_column_simple); RUN_TEST(test_addConstraintFunc_appends_table_constraint_neg1); RUN_TEST(test_addConstraintFunc_handles_comments_and_parens); RUN_TEST(test_addConstraintFunc_handles_nested_parens_in_defaults); RUN_TEST(test_addConstraintFunc_null_sql_returns_null); RUN_TEST(test_addConstraintFunc_out_of_range_column_reports_corrupt); RUN_TEST(test_addConstraintFunc_malformed_sql_reports_corrupt); return UNITY_END(); }