#include "sqliteInt.h" #include "unity.h" #include #include /* Globals for tests */ static sqlite3 *gDb = NULL; /* Unity setup/teardown */ void setUp(void) { int rc = sqlite3_open(":memory:", &gDb); TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc); TEST_ASSERT_NOT_NULL(gDb); } void tearDown(void) { if (gDb) { sqlite3_close(gDb); gDb = NULL; } } /* Helper: create a SrcList with a single table name */ static SrcList* makeSrcList(const char *zName){ Token t; memset(&t, 0, sizeof(t)); t.z = zName; t.n = (int)strlen(zName); return sqlite3SrcListAppend(gDb, 0, &t, 0); } /* Helper: initialize a Parse object for our db */ static void initParse(Parse *p){ memset(p, 0, sizeof(*p)); p->db = gDb; } /* Test: successful copy for an ordinary table */ void test_sqlite3AlterBeginAddColumn_success_creates_partial_copy(void) { char *err = 0; int rc; /* Create a normal table with multiple columns and defaults */ rc = sqlite3_exec(gDb, "CREATE TABLE t(a INTEGER DEFAULT 1, b TEXT, c REAL DEFAULT 3.14);", 0, 0, &err); TEST_ASSERT_EQUAL_INT_MESSAGE(SQLITE_OK, rc, err ? err : "create failed"); if (err) sqlite3_free(err), err = 0; /* Find original table metadata */ Table *pOrig = sqlite3FindTable(gDb, "t", "main"); TEST_ASSERT_NOT_NULL(pOrig); TEST_ASSERT_TRUE(pOrig->nCol > 0); /* Prepare SrcList and Parse */ SrcList *pSrc = makeSrcList("t"); TEST_ASSERT_NOT_NULL(pSrc); Parse parse; initParse(&parse); TEST_ASSERT_NULL(parse.pNewTable); /* Call the target function while holding btree mutexes */ sqlite3BtreeEnterAll(gDb); sqlite3AlterBeginAddColumn(&parse, pSrc); sqlite3BtreeLeaveAll(gDb); /* Verify success: no error and pNewTable populated */ TEST_ASSERT_EQUAL_INT(0, parse.nErr); TEST_ASSERT_NOT_NULL(parse.pNewTable); Table *pNew = parse.pNewTable; /* Name must be prefixed as "sqlite_altertab_t" */ TEST_ASSERT_NOT_NULL(pNew->zName); TEST_ASSERT_EQUAL_STRING("sqlite_altertab_t", pNew->zName); /* Column count should match */ TEST_ASSERT_EQUAL_INT(pOrig->nCol, pNew->nCol); /* Column names duplicated and hashes computed */ for(int i=0; inCol; i++){ Column *cOrig = &pOrig->aCol[i]; Column *cNew = &pNew->aCol[i]; TEST_ASSERT_NOT_NULL(cNew->zCnName); TEST_ASSERT_NOT_NULL(cOrig->zCnName); /* Ensure duplicated pointer (not aliasing original) and same content */ TEST_ASSERT(cNew->zCnName != cOrig->zCnName); TEST_ASSERT_EQUAL_STRING(cOrig->zCnName, cNew->zCnName); /* Ensure hash is set to case-insensitive hash of the name */ TEST_ASSERT_EQUAL_INT(sqlite3StrIHash(cNew->zCnName), cNew->hName); } /* Schema and addColOffset should match original */ TEST_ASSERT(pNew->pSchema == pOrig->pSchema); TEST_ASSERT_TRUE(pNew->u.tab.addColOffset > 0); TEST_ASSERT_EQUAL_INT(pOrig->u.tab.addColOffset, pNew->u.tab.addColOffset); /* Default expression list should be duplicated if present on original */ if( pOrig->u.tab.pDfltList ){ TEST_ASSERT_NOT_NULL(pNew->u.tab.pDfltList); TEST_ASSERT_NOT_NULL(pOrig->u.tab.pDfltList); TEST_ASSERT(pNew->u.tab.pDfltList != pOrig->u.tab.pDfltList); TEST_ASSERT_EQUAL_INT(pOrig->u.tab.pDfltList->nExpr, pNew->u.tab.pDfltList->nExpr); } /* New table refcount should be 1 */ TEST_ASSERT_EQUAL_INT(1, pNew->nTabRef); /* Cleanup: delete the allocated table copy */ sqlite3DeleteTable(gDb, pNew); parse.pNewTable = 0; } /* Test: attempting to alter a view should raise an error */ void test_sqlite3AlterBeginAddColumn_on_view_sets_error(void) { char *err = 0; int rc; rc = sqlite3_exec(gDb, "CREATE VIEW v AS SELECT 1 AS x;", 0, 0, &err); TEST_ASSERT_EQUAL_INT_MESSAGE(SQLITE_OK, rc, err ? err : "create view failed"); if (err) sqlite3_free(err), err = 0; SrcList *pSrc = makeSrcList("v"); TEST_ASSERT_NOT_NULL(pSrc); Parse parse; initParse(&parse); sqlite3BtreeEnterAll(gDb); sqlite3AlterBeginAddColumn(&parse, pSrc); sqlite3BtreeLeaveAll(gDb); TEST_ASSERT_TRUE(parse.nErr > 0); TEST_ASSERT_NULL(parse.pNewTable); TEST_ASSERT_NOT_NULL(parse.zErrMsg); TEST_ASSERT_NOT_NULL(strstr(parse.zErrMsg, "Cannot add a column to a view")); sqlite3DbFree(gDb, parse.zErrMsg); parse.zErrMsg = 0; } /* Test: attempting to alter a system table (sqlite_sequence) should raise an error */ void test_sqlite3AlterBeginAddColumn_on_system_table_sets_error(void) { char *err = 0; int rc; /* Create an AUTOINCREMENT table to ensure sqlite_sequence exists */ rc = sqlite3_exec(gDb, "CREATE TABLE at(id INTEGER PRIMARY KEY AUTOINCREMENT, b TEXT);", 0, 0, &err); TEST_ASSERT_EQUAL_INT_MESSAGE(SQLITE_OK, rc, err ? err : "create autoinc failed"); if (err) sqlite3_free(err), err = 0; /* Ensure sqlite_sequence exists by inserting a row (usually created at table create, but this is safe) */ rc = sqlite3_exec(gDb, "INSERT INTO at(b) VALUES('x');", 0, 0, &err); TEST_ASSERT_EQUAL_INT_MESSAGE(SQLITE_OK, rc, err ? err : "insert failed"); if (err) sqlite3_free(err), err = 0; /* Now attempt to alter sqlite_sequence */ SrcList *pSrc = makeSrcList("sqlite_sequence"); TEST_ASSERT_NOT_NULL(pSrc); Parse parse; initParse(&parse); sqlite3BtreeEnterAll(gDb); sqlite3AlterBeginAddColumn(&parse, pSrc); sqlite3BtreeLeaveAll(gDb); TEST_ASSERT_TRUE(parse.nErr > 0); TEST_ASSERT_NULL(parse.pNewTable); TEST_ASSERT_NOT_NULL(parse.zErrMsg); TEST_ASSERT_NOT_NULL(strstr(parse.zErrMsg, "table sqlite_sequence may not be altered")); sqlite3DbFree(gDb, parse.zErrMsg); parse.zErrMsg = 0; } /* Test: table not found should set an error and no pNewTable */ void test_sqlite3AlterBeginAddColumn_table_not_found_sets_error(void) { SrcList *pSrc = makeSrcList("no_such_table_123"); TEST_ASSERT_NOT_NULL(pSrc); Parse parse; initParse(&parse); sqlite3BtreeEnterAll(gDb); sqlite3AlterBeginAddColumn(&parse, pSrc); sqlite3BtreeLeaveAll(gDb); TEST_ASSERT_TRUE(parse.nErr > 0); TEST_ASSERT_NULL(parse.pNewTable); TEST_ASSERT_NOT_NULL(parse.zErrMsg); TEST_ASSERT_NOT_NULL(strstr(parse.zErrMsg, "no such table")); sqlite3DbFree(gDb, parse.zErrMsg); parse.zErrMsg = 0; } /* Main runner */ int main(void) { UNITY_BEGIN(); RUN_TEST(test_sqlite3AlterBeginAddColumn_success_creates_partial_copy); RUN_TEST(test_sqlite3AlterBeginAddColumn_on_view_sets_error); RUN_TEST(test_sqlite3AlterBeginAddColumn_on_system_table_sets_error); RUN_TEST(test_sqlite3AlterBeginAddColumn_table_not_found_sets_error); return UNITY_END(); }