#include "sqliteInt.h" #include "unity.h" #include #include #include /* Helpers */ static int exec_sql(sqlite3 *db, const char *sql, char **pzErr){ return sqlite3_exec(db, sql, 0, 0, pzErr); } static void assert_exec_ok(sqlite3 *db, const char *sql){ char *zErr = NULL; int rc = exec_sql(db, sql, &zErr); if( zErr ){ /* Ensure any error message is freed even if rc==SQLITE_OK (unlikely) */ sqlite3_free(zErr); zErr = NULL; } TEST_ASSERT_EQUAL_INT_MESSAGE(SQLITE_OK, rc, sql); } static int exec_expect_error_contains(sqlite3 *db, const char *sql, const char *needle, int *pRcOut){ char *zErr = NULL; int rc = exec_sql(db, sql, &zErr); if( pRcOut ) *pRcOut = rc; int ok = (rc!=SQLITE_OK) && (zErr!=NULL) && (strstr(zErr, needle)!=0); if( zErr ) sqlite3_free(zErr); return ok; } static char *get_table_sql(sqlite3 *db, const char *zName){ sqlite3_stmt *pStmt = NULL; const char *zSql = "SELECT sql FROM sqlite_schema WHERE name=?1"; if( sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0)!=SQLITE_OK ){ return NULL; } sqlite3_bind_text(pStmt, 1, zName, -1, SQLITE_STATIC); char *zOut = NULL; if( sqlite3_step(pStmt)==SQLITE_ROW ){ const unsigned char *z = sqlite3_column_text(pStmt, 0); if( z ){ zOut = sqlite3_mprintf("%s", z); } } sqlite3_finalize(pStmt); return zOut; } /* Unity fixtures */ void setUp(void) { /* empty */ } void tearDown(void) { /* empty */ } /* Test: Successful ALTER adds named constraint and enforces it for new rows */ void test_sqlite3AlterAddConstraint_add_named_and_enforce(void){ sqlite3 *db = NULL; TEST_ASSERT_EQUAL_INT(SQLITE_OK, sqlite3_open(":memory:", &db)); assert_exec_ok(db, "CREATE TABLE t1(x)"); assert_exec_ok(db, "INSERT INTO t1 VALUES(1)"); /* Add a named CHECK constraint */ assert_exec_ok(db, "ALTER TABLE t1 ADD CONSTRAINT ck_pos CHECK(x>0)"); /* Inserting a violating row should now fail */ int rc = SQLITE_OK; TEST_ASSERT_TRUE_MESSAGE( exec_expect_error_contains(db, "INSERT INTO t1 VALUES(0)", "CHECK constraint failed", &rc), "Expected CHECK constraint failure on INSERT after ALTER" ); TEST_ASSERT_EQUAL_INT_MESSAGE(SQLITE_CONSTRAINT, rc, "Expected SQLITE_CONSTRAINT"); /* Valid insert still succeeds */ assert_exec_ok(db, "INSERT INTO t1 VALUES(2)"); /* Adding another constraint with the same name should fail */ TEST_ASSERT_TRUE_MESSAGE( exec_expect_error_contains(db, "ALTER TABLE t1 ADD CONSTRAINT ck_pos CHECK(x<10)", "already exists", NULL ), "Expected duplicate constraint name error" ); sqlite3_close(db); } /* Test: ALTER fails if existing rows violate the new CHECK expression */ void test_sqlite3AlterAddConstraint_violation_on_alter(void){ sqlite3 *db = NULL; TEST_ASSERT_EQUAL_INT(SQLITE_OK, sqlite3_open(":memory:", &db)); assert_exec_ok(db, "CREATE TABLE t2(x)"); assert_exec_ok(db, "INSERT INTO t2 VALUES(-1)"); /* This should fail because existing row violates x>0 */ int rc = SQLITE_OK; TEST_ASSERT_TRUE_MESSAGE( exec_expect_error_contains(db, "ALTER TABLE t2 ADD CHECK(x>0)", "constraint failed", &rc ), "Expected 'constraint failed' during ALTER due to existing data" ); TEST_ASSERT_EQUAL_INT_MESSAGE(SQLITE_CONSTRAINT, rc, "Expected SQLITE_CONSTRAINT"); /* Schema should not be modified */ char *zSchema = get_table_sql(db, "t2"); TEST_ASSERT_NOT_NULL(zSchema); TEST_ASSERT_TRUE(strstr(zSchema, "CHECK")==NULL); sqlite3_free(zSchema); sqlite3_close(db); } /* Test: Trailing '--' is trimmed but trailing '/* ... *\/' comment is preserved */ void test_sqlite3AlterAddConstraint_trim_line_comment_preserve_c_comment(void){ sqlite3 *db = NULL; TEST_ASSERT_EQUAL_INT(SQLITE_OK, sqlite3_open(":memory:", &db)); assert_exec_ok(db, "CREATE TABLE t3(x)"); assert_exec_ok(db, "INSERT INTO t3 VALUES(1)"); /* Trailing C-style comment should be preserved; trailing -- comment removed */ assert_exec_ok(db, "ALTER TABLE t3 ADD CHECK (x>0) /*C-keep*/ -- line-drop"); char *zSchema = get_table_sql(db, "t3"); TEST_ASSERT_NOT_NULL(zSchema); /* Ensure C-style comment present, '--' tail absent */ TEST_ASSERT_NOT_NULL_MESSAGE(strstr(zSchema, "/*C-keep*/"), "Expected C-style comment preserved in schema SQL"); TEST_ASSERT_NULL_MESSAGE(strstr(zSchema, "-- line-drop"), "Expected '--' trailing comment removed from schema SQL"); sqlite3_free(zSchema); sqlite3_close(db); } /* Direct call smoke test: invoke sqlite3AlterAddConstraint with constructed Parse/SrcList/Tokens */ void test_sqlite3AlterAddConstraint_direct_call_smoke(void){ sqlite3 *db = NULL; TEST_ASSERT_EQUAL_INT(SQLITE_OK, sqlite3_open(":memory:", &db)); assert_exec_ok(db, "CREATE TABLE t4(a)"); /* Prepare Parse with a Vdbe */ Parse p; memset(&p, 0, sizeof(p)); p.db = db; /* Ensure there is a VDBE to receive generated bytecode */ sqlite3GetVdbe(&p); TEST_ASSERT_NOT_NULL(p.pVdbe); /* Build a SrcList referring to table t4 */ Token tTbl = { "t4", 2 }; SrcList *pSrc = sqlite3SrcListAppend(db, 0, &tTbl, 0); TEST_ASSERT_NOT_NULL(pSrc); TEST_ASSERT_EQUAL_INT(1, pSrc->nSrc); /* Construct constraint text and tokens */ const char *zCons = "CONSTRAINT ckx CHECK(a>0) /*preserve*/ -- trim"; Token firstTok; firstTok.z = zCons; firstTok.n = (int)strlen(zCons); Token nameTok = { "ckx", 3 }; const char *zExpr = "a>0"; int nExpr = 3; /* Set sLastToken to mark the end of the constraint text buffer */ p.sLastToken.z = zCons + strlen(zCons); p.sLastToken.n = 0; /* Invoke the target function directly */ sqlite3AlterAddConstraint(&p, pSrc, &firstTok, &nameTok, zExpr, nExpr); /* Basic sanity: no parse errors recorded and VDBE remains allocated */ TEST_ASSERT_NOT_NULL(p.pVdbe); TEST_ASSERT_EQUAL_INT(0, p.nErr); /* Cleanup */ sqlite3SrcListDelete(db, pSrc); sqlite3VdbeDelete(p.pVdbe); sqlite3_close(db); } int main(void) { UNITY_BEGIN(); RUN_TEST(test_sqlite3AlterAddConstraint_add_named_and_enforce); RUN_TEST(test_sqlite3AlterAddConstraint_violation_on_alter); RUN_TEST(test_sqlite3AlterAddConstraint_trim_line_comment_preserve_c_comment); RUN_TEST(test_sqlite3AlterAddConstraint_direct_call_smoke); return UNITY_END(); }