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

/* Wrapper for the static function provided in the module under test */
extern int test_skipCreateTable(sqlite3_context *ctx, const u8 *zSql, int *piOff);

void setUp(void) {
  /* Setup code here, or leave empty */
}

void tearDown(void) {
  /* Cleanup code here, or leave empty */
}

/* Helper: call the wrapper with a NULL sqlite3_context, suitable for non-error paths */
static int call_skip_with_null_ctx(const char *zSql, int *piOff){
  return test_skipCreateTable(NULL, (const unsigned char*)zSql, piOff);
}

/* Test 1: Basic CREATE TABLE with straightforward '(' */
void test_skipCreateTable_basic_simple(void) {
  const char *zSql = "CREATE TABLE t(a INTEGER, b TEXT)";
  int off = -1;
  int rc = call_skip_with_null_ctx(zSql, &off);

  TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);
  TEST_ASSERT_TRUE(off > 0);
  TEST_ASSERT_EQUAL_CHAR('(', zSql[off-1]);

  /* For this simple case with no prior '(', the first '(' is the column list */
  const char *pFirst = strchr(zSql, '(');
  TEST_ASSERT_NOT_NULL(pFirst);
  TEST_ASSERT_EQUAL_INT((int)(pFirst - zSql + 1), off);
}

/* Test 2: With whitespace, comments, schema and quoted identifier */
void test_skipCreateTable_with_schema_and_quotes(void) {
  const char *zSql = "   CREATE  TEMP   TABLE  IF NOT EXISTS main.\"t name\"   (  col1  INTEGER )";
  int off = -1;
  int rc = call_skip_with_null_ctx(zSql, &off);

  TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);
  TEST_ASSERT_TRUE(off > 0);
  TEST_ASSERT_EQUAL_CHAR('(', zSql[off-1]);

  const char *pFirst = strchr(zSql, '(');
  TEST_ASSERT_NOT_NULL(pFirst);
  TEST_ASSERT_EQUAL_INT((int)(pFirst - zSql + 1), off);
}

/* Test 3: Ensure '(' inside a string literal is ignored by tokenizer; offset is at the true column-list '(' */
void test_skipCreateTable_ignores_paren_inside_string_literal(void) {
  /* The first '(' appears inside the string literal and should be ignored.
     The actual column-list '(' is the second '(' in the SQL text. */
  const char *zSql = "CREATE TABLE t 'ignore (this) completely' (x INT)";
  int off = -1;
  int rc = call_skip_with_null_ctx(zSql, &off);

  TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);
  TEST_ASSERT_TRUE(off > 0);
  TEST_ASSERT_EQUAL_CHAR('(', zSql[off-1]);

  /* The correct '(' is the last one in this crafted SQL */
  const char *pLast = strrchr(zSql, '(');
  TEST_ASSERT_NOT_NULL(pLast);
  TEST_ASSERT_EQUAL_INT((int)(pLast - zSql + 1), off);
}

/* Test 4: Parentheses inside a bracket-quoted identifier are ignored; still find the column-list '(' */
void test_skipCreateTable_ignores_paren_inside_quoted_identifier(void) {
  const char *zSql = "CREATE TABLE [a(b)] (c TEXT)";
  int off = -1;
  int rc = call_skip_with_null_ctx(zSql, &off);

  TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);
  TEST_ASSERT_TRUE(off > 0);
  TEST_ASSERT_EQUAL_CHAR('(', zSql[off-1]);

  /* The correct '(' is the second '(' in the SQL; for this crafted SQL it's the last '(' */
  const char *pLast = strrchr(zSql, '(');
  TEST_ASSERT_NOT_NULL(pLast);
  TEST_ASSERT_EQUAL_INT((int)(pLast - zSql + 1), off);
}

/* Test 5: NULL input -> SQLITE_ERROR and piOff must remain unchanged */
void test_skipCreateTable_null_input_returns_error(void) {
  int off = 1234;
  int rc = test_skipCreateTable(NULL, NULL, &off);
  TEST_ASSERT_EQUAL_INT(SQLITE_ERROR, rc);
  TEST_ASSERT_EQUAL_INT(1234, off);
}

/* Globals used to capture results from UDF-based test */
static int g_rc_from_udf = 0;
static int g_off_from_udf = 0;

/* UDF used to obtain a valid sqlite3_context for exercising the error path */
static void udf_call_skip(sqlite3_context *ctx, int argc, sqlite3_value **argv){
  (void)argc;
  const unsigned char *zSql = sqlite3_value_text(argv[0]);
  int off = -1;
  int rc = test_skipCreateTable(ctx, zSql, &off);
  g_rc_from_udf = rc;
  g_off_from_udf = off;

  /* Return rc as function result too (engine may still treat prior error as authoritative) */
  sqlite3_result_int(ctx, rc);
}

/* Test 6: No '(' present – via UDF to provide valid sqlite3_context; expect SQLITE_ERROR */
void test_skipCreateTable_no_open_paren_illegal_token_via_udf(void) {
  sqlite3 *db = NULL;
  sqlite3_stmt *stmt = NULL;

  int rc = sqlite3_open(":memory:", &db);
  TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);

  rc = sqlite3_create_function_v2(db, "xskip", 1, SQLITE_UTF8, NULL,
                                  udf_call_skip, NULL, NULL, NULL);
  TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);

  /* No '(' in this SQL text; skipCreateTable should eventually hit an error path */
  const char *sql = "SELECT xskip('CREATE TABLE t')";
  rc = sqlite3_prepare_v2(db, sql, -1, &stmt, 0);
  TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);

  int steprc = sqlite3_step(stmt);
  /* Engine may return SQLITE_ERROR due to sqlite3_result_error_code(ctx,...)
     We are tolerant to either SQLITE_ERROR or SQLITE_ROW depending on internal behavior,
     but we strictly verify the function's rc captured from inside the UDF. */
  (void)steprc;

  rc = sqlite3_finalize(stmt);
  TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);

  sqlite3_close(db);

  TEST_ASSERT_EQUAL_INT(SQLITE_ERROR, g_rc_from_udf);
  /* piOff should not have been set on error */
  TEST_ASSERT_EQUAL_INT(-1, g_off_from_udf);
}

int main(void) {
  UNITY_BEGIN();
  RUN_TEST(test_skipCreateTable_basic_simple);
  RUN_TEST(test_skipCreateTable_with_schema_and_quotes);
  RUN_TEST(test_skipCreateTable_ignores_paren_inside_string_literal);
  RUN_TEST(test_skipCreateTable_ignores_paren_inside_quoted_identifier);
  RUN_TEST(test_skipCreateTable_null_input_returns_error);
  RUN_TEST(test_skipCreateTable_no_open_paren_illegal_token_via_udf);
  return UNITY_END();
}