#include "sqliteInt.h" #include "unity.h" #include #include /* Helper: execute SQL and assert success */ static void execSQL(sqlite3 *db, const char *zSql){ char *zErr = 0; int rc = sqlite3_exec(db, zSql, 0, 0, &zErr); if( rc!=SQLITE_OK ){ fprintf(stderr, "SQL error (%d): %s while running: %s\n", rc, zErr?zErr:"(null)", zSql); } TEST_ASSERT_EQUAL_INT_MESSAGE(SQLITE_OK, rc, "sqlite3_exec failed"); if( zErr ) sqlite3_free(zErr); } /* Helper: create a SrcList for a single table name */ static SrcList *makeSrcList(sqlite3 *db, const char *zName){ Token tName; tName.z = zName; tName.n = (int)strlen(zName); return sqlite3SrcListAppend(db, 0, &tName, 0); } /* Helper: initialize a Parse object */ static void initParse(sqlite3 *db, Parse *p){ memset(p, 0, sizeof(*p)); p->db = db; } /* Helper: free Parse resources left after calling the target function */ static void cleanupParse(sqlite3 *db, Parse *p){ if( p->zErrMsg ){ sqlite3DbFree(db, p->zErrMsg); p->zErrMsg = 0; } if( p->pVdbe ){ sqlite3VdbeDelete(p->pVdbe); p->pVdbe = 0; } } void setUp(void) { /* No global setup */ } void tearDown(void) { /* No global teardown */ } /* Test: dropping a non-existent column reports an error */ void test_sqlite3AlterDropColumn_no_such_column(void){ sqlite3 *db = 0; TEST_ASSERT_EQUAL_INT(SQLITE_OK, sqlite3_open(":memory:", &db)); execSQL(db, "CREATE TABLE t1(a, b);"); Parse p; initParse(db, &p); SrcList *pSrc = makeSrcList(db, "t1"); Token tCol; tCol.z = "c"; tCol.n = 1; sqlite3BtreeEnterAll(db); sqlite3AlterDropColumn(&p, pSrc, &tCol); sqlite3BtreeLeaveAll(db); TEST_ASSERT_TRUE_MESSAGE(p.nErr>0, "Expected error for non-existent column"); TEST_ASSERT_NOT_NULL(p.zErrMsg); TEST_ASSERT_NOT_EQUAL(-1, (int)sqlite3StrLike("no such column:%", p.zErrMsg, 0)); /* rough check */ /* Also check substring for robustness */ TEST_ASSERT_NOT_EQUAL(NULL, strstr(p.zErrMsg, "no such column")); cleanupParse(db, &p); sqlite3_close(db); } /* Test: cannot drop PRIMARY KEY column */ void test_sqlite3AlterDropColumn_drop_primary_key_column(void){ sqlite3 *db = 0; TEST_ASSERT_EQUAL_INT(SQLITE_OK, sqlite3_open(":memory:", &db)); execSQL(db, "CREATE TABLE tpk(a INTEGER PRIMARY KEY, b);"); Parse p; initParse(db, &p); SrcList *pSrc = makeSrcList(db, "tpk"); Token tCol; tCol.z = "a"; tCol.n = 1; sqlite3BtreeEnterAll(db); sqlite3AlterDropColumn(&p, pSrc, &tCol); sqlite3BtreeLeaveAll(db); TEST_ASSERT_TRUE_MESSAGE(p.nErr>0, "Expected error dropping PRIMARY KEY column"); TEST_ASSERT_NOT_NULL(p.zErrMsg); TEST_ASSERT_NOT_EQUAL(NULL, strstr(p.zErrMsg, "PRIMARY KEY")); cleanupParse(db, &p); sqlite3_close(db); } /* Test: cannot drop a column with a column-level UNIQUE constraint */ void test_sqlite3AlterDropColumn_drop_unique_column(void){ sqlite3 *db = 0; TEST_ASSERT_EQUAL_INT(SQLITE_OK, sqlite3_open(":memory:", &db)); /* Column-level UNIQUE to set COLFLAG_UNIQUE */ execSQL(db, "CREATE TABLE tu(a UNIQUE, b);"); Parse p; initParse(db, &p); SrcList *pSrc = makeSrcList(db, "tu"); Token tCol; tCol.z = "a"; tCol.n = 1; sqlite3BtreeEnterAll(db); sqlite3AlterDropColumn(&p, pSrc, &tCol); sqlite3BtreeLeaveAll(db); TEST_ASSERT_TRUE_MESSAGE(p.nErr>0, "Expected error dropping UNIQUE column"); TEST_ASSERT_NOT_NULL(p.zErrMsg); TEST_ASSERT_NOT_EQUAL(NULL, strstr(p.zErrMsg, "UNIQUE")); cleanupParse(db, &p); sqlite3_close(db); } /* Test: cannot drop the last remaining column of a table */ void test_sqlite3AlterDropColumn_drop_only_remaining_column(void){ sqlite3 *db = 0; TEST_ASSERT_EQUAL_INT(SQLITE_OK, sqlite3_open(":memory:", &db)); execSQL(db, "CREATE TABLE t1col(a);"); Parse p; initParse(db, &p); SrcList *pSrc = makeSrcList(db, "t1col"); Token tCol; tCol.z = "a"; tCol.n = 1; sqlite3BtreeEnterAll(db); sqlite3AlterDropColumn(&p, pSrc, &tCol); sqlite3BtreeLeaveAll(db); TEST_ASSERT_TRUE_MESSAGE(p.nErr>0, "Expected error dropping the only column"); TEST_ASSERT_NOT_NULL(p.zErrMsg); TEST_ASSERT_NOT_EQUAL(NULL, strstr(p.zErrMsg, "no other columns exist")); cleanupParse(db, &p); sqlite3_close(db); } /* Test: cannot drop a column from a view */ void test_sqlite3AlterDropColumn_on_view(void){ sqlite3 *db = 0; TEST_ASSERT_EQUAL_INT(SQLITE_OK, sqlite3_open(":memory:", &db)); execSQL(db, "CREATE VIEW v1 AS SELECT 1 AS a, 2 AS b;"); Parse p; initParse(db, &p); SrcList *pSrc = makeSrcList(db, "v1"); Token tCol; tCol.z = "a"; tCol.n = 1; sqlite3BtreeEnterAll(db); sqlite3AlterDropColumn(&p, pSrc, &tCol); sqlite3BtreeLeaveAll(db); TEST_ASSERT_TRUE_MESSAGE(p.nErr>0, "Expected error dropping column from view"); TEST_ASSERT_NOT_NULL(p.zErrMsg); TEST_ASSERT_NOT_EQUAL(NULL, strstr(p.zErrMsg, "drop column from view")); cleanupParse(db, &p); sqlite3_close(db); } /* Test: successful generation for dropping a regular column from a table */ void test_sqlite3AlterDropColumn_success_generates_vdbe(void){ sqlite3 *db = 0; TEST_ASSERT_EQUAL_INT(SQLITE_OK, sqlite3_open(":memory:", &db)); execSQL(db, "CREATE TABLE tgood(a, b, c);"); execSQL(db, "INSERT INTO tgood VALUES(1,2,3);"); Parse p; initParse(db, &p); SrcList *pSrc = makeSrcList(db, "tgood"); Token tCol; tCol.z = "b"; tCol.n = 1; sqlite3BtreeEnterAll(db); sqlite3AlterDropColumn(&p, pSrc, &tCol); sqlite3BtreeLeaveAll(db); TEST_ASSERT_EQUAL_INT_MESSAGE(0, p.nErr, "Unexpected error during valid drop column codegen"); TEST_ASSERT_NOT_NULL_MESSAGE(p.pVdbe, "Expected VDBE code to be generated"); TEST_ASSERT_TRUE_MESSAGE(p.nMem>0 || p.nTab>0, "Expected some registers/cursors allocated"); cleanupParse(db, &p); sqlite3_close(db); } int main(void){ UNITY_BEGIN(); RUN_TEST(test_sqlite3AlterDropColumn_no_such_column); RUN_TEST(test_sqlite3AlterDropColumn_drop_primary_key_column); RUN_TEST(test_sqlite3AlterDropColumn_drop_unique_column); RUN_TEST(test_sqlite3AlterDropColumn_drop_only_remaining_column); RUN_TEST(test_sqlite3AlterDropColumn_on_view); RUN_TEST(test_sqlite3AlterDropColumn_success_generates_vdbe); return UNITY_END(); }