File size: 7,946 Bytes
7510827
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
#include "sqliteInt.h"
#include "unity.h"
#include <string.h>
#include <stdio.h>

/* 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();
}