#include "sqliteInt.h" #include "unity.h" #include #include /* Wrapper for the static function under test (provided by the module). */ extern int test_renameEditSql( sqlite3_context *pCtx, RenameCtx *pRename, const char *zSql, const char *zNew, int bQuote ); /* Helper: add a RenameToken covering the span [start, start+n) within zSql. */ static void addTokenSpan(sqlite3 *db, RenameCtx *p, const char *zSql, int start, int n){ RenameToken *pTok = (RenameToken*)sqlite3DbMallocZero(db, sizeof(RenameToken)); TEST_ASSERT_NOT_NULL_MESSAGE(pTok, "Failed to allocate RenameToken"); pTok->t.z = zSql + start; pTok->t.n = n; pTok->pNext = p->pList; p->pList = pTok; p->nList++; } /* Helper: find and add token for the first occurrence of substring sub in zSql. */ static void addTokenForSubstring(sqlite3 *db, RenameCtx *p, const char *zSql, const char *sub){ const char *pos = strstr(zSql, sub); TEST_ASSERT_NOT_NULL_MESSAGE(pos, "Substring not found in SQL"); int start = (int)(pos - zSql); addTokenSpan(db, p, zSql, start, (int)strlen(sub)); } /* Helper: initialize a sqlite3_context that can be used by renameEditSql */ static void initContext(sqlite3_context *pCtx, sqlite3 *db, Vdbe *pVdbe){ memset(pCtx, 0, sizeof(*pCtx)); memset(pVdbe, 0, sizeof(*pVdbe)); pVdbe->db = db; pCtx->pVdbe = pVdbe; sqlite3VdbeMemInit(&pCtx->s, db, 0); } /* Helper: call test_renameEditSql and fetch result string from context. Returns rc and sets *pzOut to point to the Mem string (owned by pCtx->s). */ static int call_and_result(sqlite3 *db, RenameCtx *pRename, const char *zSql, const char *zNew, int bQuote, char const **pzOut){ sqlite3_context ctx; Vdbe vdbe; initContext(&ctx, db, &vdbe); int rc = test_renameEditSql(&ctx, pRename, zSql, zNew, bQuote); TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc); TEST_ASSERT_NOT_NULL_MESSAGE(ctx.s.z, "No result text produced"); *pzOut = (const char*)ctx.s.z; /* We intentionally do not release ctx.s here to allow caller to inspect it. Caller must call sqlite3VdbeMemRelease on a copy of the Mem. */ /* Make a shallow copy of Mem to release after check */ Mem sCopy = ctx.s; /* Zero original to avoid double-free after release of sCopy */ memset(&ctx.s, 0, sizeof(ctx.s)); sqlite3VdbeMemRelease(&sCopy); return rc; } void setUp(void) { /* Setup code here, or leave empty */ } void tearDown(void) { /* Cleanup code here, or leave empty */ } /* Test 1: Basic unquoted replacement with multiple tokens. */ void test_renameEditSql_basic_unquoted_multiple(void){ sqlite3 *db = 0; TEST_ASSERT_EQUAL_INT(SQLITE_OK, sqlite3_open(":memory:", &db)); const char *zSql = "CREATE TABLE t (old, b, old);"; RenameCtx ctxRename; memset(&ctxRename, 0, sizeof(ctxRename)); /* Add both occurrences of "old" as tokens (add in reverse order to test that order of pList is not required to be sorted). */ const char *first = strstr(zSql, "old"); const char *second = strstr(first+1, "old"); TEST_ASSERT_NOT_NULL(first); TEST_ASSERT_NOT_NULL(second); addTokenSpan(db, &ctxRename, zSql, (int)(second - zSql), 3); addTokenSpan(db, &ctxRename, zSql, (int)(first - zSql), 3); const char *zNew = "newname"; const char *zOut = NULL; call_and_result(db, &ctxRename, zSql, zNew, 0, &zOut); TEST_ASSERT_EQUAL_STRING("CREATE TABLE t (newname, b, newname);", zOut); /* The function should have consumed all tokens */ TEST_ASSERT_NULL(ctxRename.pList); sqlite3_close(db); } /* Test 2: Force quoting with bQuote==1. */ void test_renameEditSql_force_quote_bQuote1(void){ sqlite3 *db = 0; TEST_ASSERT_EQUAL_INT(SQLITE_OK, sqlite3_open(":memory:", &db)); const char *zSql = "CREATE TABLE t (old);"; RenameCtx ctxRename; memset(&ctxRename, 0, sizeof(ctxRename)); addTokenForSubstring(db, &ctxRename, zSql, "old"); const char *zNew = "col name"; /* requires quoting */ const char *zOut = NULL; call_and_result(db, &ctxRename, zSql, zNew, 1, &zOut); TEST_ASSERT_EQUAL_STRING("CREATE TABLE t (\"col name\");", zOut); TEST_ASSERT_NULL(ctxRename.pList); sqlite3_close(db); } /* Test 3: Original token is quoted, bQuote==0 still results in quoted replacement. */ void test_renameEditSql_quoted_input_bQuote0(void){ sqlite3 *db = 0; TEST_ASSERT_EQUAL_INT(SQLITE_OK, sqlite3_open(":memory:", &db)); const char *zSql = "SELECT \"old\" FROM t;"; RenameCtx ctxRename; memset(&ctxRename, 0, sizeof(ctxRename)); addTokenForSubstring(db, &ctxRename, zSql, "\"old\""); const char *zNew = "foo"; const char *zOut = NULL; call_and_result(db, &ctxRename, zSql, zNew, 0, &zOut); TEST_ASSERT_EQUAL_STRING("SELECT \"foo\" FROM t;", zOut); TEST_ASSERT_NULL(ctxRename.pList); sqlite3_close(db); } /* Test 4: zNew==NULL converts double-quoted token to single-quoted text and inserts a space if immediately followed by a single-quote. */ void test_renameEditSql_null_zNew_single_quote_conversion_with_space(void){ sqlite3 *db = 0; TEST_ASSERT_EQUAL_INT(SQLITE_OK, sqlite3_open(":memory:", &db)); /* Case with following single-quote -> space should be inserted */ { const char *zSql = "SELECT \"string\"'alias'"; RenameCtx ctxRename; memset(&ctxRename, 0, sizeof(ctxRename)); addTokenForSubstring(db, &ctxRename, zSql, "\"string\""); const char *zOut = NULL; call_and_result(db, &ctxRename, zSql, NULL, 0, &zOut); TEST_ASSERT_EQUAL_STRING("SELECT 'string' 'alias'", zOut); TEST_ASSERT_NULL(ctxRename.pList); } /* Case without following single-quote -> no extra space */ { const char *zSql = "SELECT \"data\";"; RenameCtx ctxRename; memset(&ctxRename, 0, sizeof(ctxRename)); addTokenForSubstring(db, &ctxRename, zSql, "\"data\""); const char *zOut = NULL; call_and_result(db, &ctxRename, zSql, NULL, 0, &zOut); TEST_ASSERT_EQUAL_STRING("SELECT 'data';", zOut); TEST_ASSERT_NULL(ctxRename.pList); } sqlite3_close(db); } /* Test 5: No tokens -> SQL should be returned unchanged (zNew non-NULL). */ void test_renameEditSql_no_tokens_sql_unchanged(void){ sqlite3 *db = 0; TEST_ASSERT_EQUAL_INT(SQLITE_OK, sqlite3_open(":memory:", &db)); const char *zSql = "CREATE VIEW v AS SELECT a, b FROM t;"; RenameCtx ctxRename; memset(&ctxRename, 0, sizeof(ctxRename)); const char *zOut = NULL; call_and_result(db, &ctxRename, zSql, "x", 0, &zOut); TEST_ASSERT_EQUAL_STRING(zSql, zOut); TEST_ASSERT_NULL(ctxRename.pList); sqlite3_close(db); } /* Test 6: Multiple tokens out-of-order in pList, ensure all replaced correctly. */ void test_renameEditSql_multiple_tokens_out_of_order(void){ sqlite3 *db = 0; TEST_ASSERT_EQUAL_INT(SQLITE_OK, sqlite3_open(":memory:", &db)); const char *zSql = "UPDATE t SET old = 1, x = old, y = old;"; RenameCtx ctxRename; memset(&ctxRename, 0, sizeof(ctxRename)); /* Find three occurrences of "old" and add them in jumbled order */ const char *p = zSql; int offs[3]; for(int i=0;i<3;i++){ p = strstr(p, "old"); TEST_ASSERT_NOT_NULL(p); offs[i] = (int)(p - zSql); p += 3; } /* Add in order 2,0,1 */ addTokenSpan(db, &ctxRename, zSql, offs[2], 3); addTokenSpan(db, &ctxRename, zSql, offs[0], 3); addTokenSpan(db, &ctxRename, zSql, offs[1], 3); const char *zOut = NULL; call_and_result(db, &ctxRename, zSql, "nn", 0, &zOut); TEST_ASSERT_EQUAL_STRING("UPDATE t SET nn = 1, x = nn, y = nn;", zOut); TEST_ASSERT_NULL(ctxRename.pList); sqlite3_close(db); } int main(void) { UNITY_BEGIN(); RUN_TEST(test_renameEditSql_basic_unquoted_multiple); RUN_TEST(test_renameEditSql_force_quote_bQuote1); RUN_TEST(test_renameEditSql_quoted_input_bQuote0); RUN_TEST(test_renameEditSql_null_zNew_single_quote_conversion_with_space); RUN_TEST(test_renameEditSql_no_tokens_sql_unchanged); RUN_TEST(test_renameEditSql_multiple_tokens_out_of_order); return UNITY_END(); }