File size: 7,996 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
#include "sqliteInt.h"
#include "unity.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

/* 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 "<zDb>." LEGACY_SCHEMA_TABLE " where name=<zName>".
** 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();
}