#include "sqliteInt.h" #include "unity.h" #include #include #include /* Wrapper provided externally for static function under test */ extern void test_renameFixQuotes(Parse *pParse, const char *zDb, int bTemp); /* Utility: execute SQL and assert success */ static void exec_sql_ok(sqlite3 *db, const char *zSql){ char *zErr = 0; int rc = sqlite3_exec(db, zSql, 0, 0, &zErr); if( rc!=SQLITE_OK ){ /* Provide error context in assertion message */ const char *msg = zErr ? zErr : "no message"; /* Free error before assertion to avoid leak */ if( zErr ) sqlite3_free(zErr); TEST_FAIL_MESSAGE(msg); } if( zErr ) sqlite3_free(zErr); } /* Utility: fetch the sql text from "." LEGACY_SCHEMA_TABLE " where name=". ** Returns a heap-allocated copy using sqlite3_mprintf; caller must sqlite3_free(). */ static char *get_schema_sql(sqlite3 *db, const char *zDb, const char *zName){ char *zSql = sqlite3_mprintf( "SELECT sql FROM \"%w\"." LEGACY_SCHEMA_TABLE " WHERE name=%Q", zDb, zName ); sqlite3_stmt *pStmt = 0; char *zOut = 0; int rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0); sqlite3_free(zSql); if( rc!=SQLITE_OK ){ TEST_FAIL_MESSAGE("prepare failed in get_schema_sql"); return 0; } rc = sqlite3_step(pStmt); if( rc==SQLITE_ROW ){ const unsigned char *z = sqlite3_column_text(pStmt, 0); if( z ){ zOut = sqlite3_mprintf("%s", z); }else{ zOut = 0; } }else{ /* No such row found */ zOut = 0; } sqlite3_finalize(pStmt); return zOut; } /* Helpers to check substring presence */ static int contains(const char *hay, const char *needle){ if( hay==0 || needle==0 ) return 0; return strstr(hay, needle)!=0; } void setUp(void) { /* nothing */ } void tearDown(void) { /* nothing */ } /* Test: When bTemp==1, only the specified database (main) is updated; temp is not. */ void test_renameFixQuotes_updates_only_main_when_bTemp_is_true(void){ sqlite3 *db = 0; TEST_ASSERT_EQUAL_INT(SQLITE_OK, sqlite3_open(":memory:", &db)); /* Create main objects with double-quoted string literal in SQL */ exec_sql_ok(db, "CREATE TABLE t1(x);"); exec_sql_ok(db, "CREATE TRIGGER tr1 AFTER INSERT ON t1 BEGIN INSERT INTO t1 VALUES(\"abc\"); END;"); /* Create temp objects with double-quoted string literal in SQL */ exec_sql_ok(db, "CREATE TEMP TABLE tt1(x);"); exec_sql_ok(db, "CREATE TEMP TRIGGER trtemp AFTER INSERT ON tt1 BEGIN INSERT INTO tt1 VALUES(\"def\"); END;"); /* Precondition checks */ char *zMainTrig = get_schema_sql(db, "main", "tr1"); char *zTempTrig = get_schema_sql(db, "temp", "trtemp"); TEST_ASSERT_NOT_NULL(zMainTrig); TEST_ASSERT_NOT_NULL(zTempTrig); TEST_ASSERT_TRUE_MESSAGE(contains(zMainTrig, "VALUES(\"abc\")"), "Main trigger SQL missing expected double-quoted literal"); TEST_ASSERT_TRUE_MESSAGE(contains(zTempTrig, "VALUES(\"def\")"), "Temp trigger SQL missing expected double-quoted literal"); sqlite3_free(zMainTrig); sqlite3_free(zTempTrig); /* Call function under test: update only main (bTemp==1) */ Parse sParse; memset(&sParse, 0, sizeof(sParse)); sParse.db = db; test_renameFixQuotes(&sParse, "main", 1); /* Postcondition: main updated to single-quoted; temp unchanged */ zMainTrig = get_schema_sql(db, "main", "tr1"); zTempTrig = get_schema_sql(db, "temp", "trtemp"); TEST_ASSERT_NOT_NULL(zMainTrig); TEST_ASSERT_NOT_NULL(zTempTrig); TEST_ASSERT_TRUE_MESSAGE(contains(zMainTrig, "VALUES('abc')"), "Main trigger SQL was not converted to single quotes"); TEST_ASSERT_FALSE_MESSAGE(contains(zMainTrig, "VALUES(\"abc\")"), "Main trigger SQL still contains double-quoted literal"); TEST_ASSERT_TRUE_MESSAGE(contains(zTempTrig, "VALUES(\"def\")"), "Temp trigger SQL should remain with double-quoted literal when bTemp==1"); TEST_ASSERT_FALSE_MESSAGE(contains(zTempTrig, "VALUES('def')"), "Temp trigger SQL should not be converted when bTemp==1"); sqlite3_free(zMainTrig); sqlite3_free(zTempTrig); TEST_ASSERT_EQUAL_INT(SQLITE_OK, sqlite3_close(db)); } /* Test: When bTemp==0, both the specified database (main) and temp are updated. */ void test_renameFixQuotes_updates_main_and_temp_when_bTemp_is_false(void){ sqlite3 *db = 0; TEST_ASSERT_EQUAL_INT(SQLITE_OK, sqlite3_open(":memory:", &db)); exec_sql_ok(db, "CREATE TABLE t1(x);"); exec_sql_ok(db, "CREATE TRIGGER tr1 AFTER INSERT ON t1 BEGIN INSERT INTO t1 VALUES(\"abc\"); END;"); exec_sql_ok(db, "CREATE TEMP TABLE tt1(x);"); exec_sql_ok(db, "CREATE TEMP TRIGGER trtemp AFTER INSERT ON tt1 BEGIN INSERT INTO tt1 VALUES(\"def\"); END;"); /* Precondition sanity */ char *zMainTrig = get_schema_sql(db, "main", "tr1"); char *zTempTrig = get_schema_sql(db, "temp", "trtemp"); TEST_ASSERT_NOT_NULL(zMainTrig); TEST_ASSERT_NOT_NULL(zTempTrig); TEST_ASSERT_TRUE(contains(zMainTrig, "VALUES(\"abc\")")); TEST_ASSERT_TRUE(contains(zTempTrig, "VALUES(\"def\")")); sqlite3_free(zMainTrig); sqlite3_free(zTempTrig); /* Call function under test: update main and temp (bTemp==0) */ Parse sParse; memset(&sParse, 0, sizeof(sParse)); sParse.db = db; test_renameFixQuotes(&sParse, "main", 0); /* Postcondition: both main and temp converted */ zMainTrig = get_schema_sql(db, "main", "tr1"); zTempTrig = get_schema_sql(db, "temp", "trtemp"); TEST_ASSERT_NOT_NULL(zMainTrig); TEST_ASSERT_NOT_NULL(zTempTrig); TEST_ASSERT_TRUE(contains(zMainTrig, "VALUES('abc')")); TEST_ASSERT_FALSE(contains(zMainTrig, "VALUES(\"abc\")")); TEST_ASSERT_TRUE(contains(zTempTrig, "VALUES('def')")); TEST_ASSERT_FALSE(contains(zTempTrig, "VALUES(\"def\")")); sqlite3_free(zMainTrig); sqlite3_free(zTempTrig); TEST_ASSERT_EQUAL_INT(SQLITE_OK, sqlite3_close(db)); } /* Test: Other attached databases are not affected when zDb is "main". */ void test_renameFixQuotes_does_not_affect_other_attached_db(void){ sqlite3 *db = 0; TEST_ASSERT_EQUAL_INT(SQLITE_OK, sqlite3_open(":memory:", &db)); /* Main schema objects */ exec_sql_ok(db, "CREATE TABLE t1(x);"); exec_sql_ok(db, "CREATE TRIGGER tr1 AFTER INSERT ON t1 BEGIN INSERT INTO t1 VALUES(\"abc\"); END;"); /* Attach another database and create an object with double-quoted literal */ /* Use on-disk temp file for portability */ exec_sql_ok(db, "ATTACH 'test_aux.db' AS aux;"); exec_sql_ok(db, "CREATE TABLE aux.t2(y);"); exec_sql_ok(db, "CREATE TRIGGER aux.tr2 AFTER INSERT ON t2 BEGIN INSERT INTO t2 VALUES(\"ghi\"); END;"); /* Sanity preconditions */ char *zMainTrig = get_schema_sql(db, "main", "tr1"); char *zAuxTrig = get_schema_sql(db, "aux", "tr2"); TEST_ASSERT_NOT_NULL(zMainTrig); TEST_ASSERT_NOT_NULL(zAuxTrig); TEST_ASSERT_TRUE(contains(zMainTrig, "VALUES(\"abc\")")); TEST_ASSERT_TRUE(contains(zAuxTrig, "VALUES(\"ghi\")")); sqlite3_free(zMainTrig); sqlite3_free(zAuxTrig); /* Call function under test for "main"; do not update temp for this test */ Parse sParse; memset(&sParse, 0, sizeof(sParse)); sParse.db = db; test_renameFixQuotes(&sParse, "main", 1); /* Verify main updated, aux unchanged */ zMainTrig = get_schema_sql(db, "main", "tr1"); zAuxTrig = get_schema_sql(db, "aux", "tr2"); TEST_ASSERT_NOT_NULL(zMainTrig); TEST_ASSERT_NOT_NULL(zAuxTrig); TEST_ASSERT_TRUE(contains(zMainTrig, "VALUES('abc')")); TEST_ASSERT_FALSE(contains(zMainTrig, "VALUES(\"abc\")")); TEST_ASSERT_TRUE(contains(zAuxTrig, "VALUES(\"ghi\")")); TEST_ASSERT_FALSE(contains(zAuxTrig, "VALUES('ghi')")); sqlite3_free(zMainTrig); sqlite3_free(zAuxTrig); /* Cleanup */ exec_sql_ok(db, "DETACH aux;"); TEST_ASSERT_EQUAL_INT(SQLITE_OK, sqlite3_close(db)); } int main(void){ UNITY_BEGIN(); RUN_TEST(test_renameFixQuotes_updates_only_main_when_bTemp_is_true); RUN_TEST(test_renameFixQuotes_updates_main_and_temp_when_bTemp_is_false); RUN_TEST(test_renameFixQuotes_does_not_affect_other_attached_db); return UNITY_END(); }