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

/* The wrapper for the static function is provided by the module under test. */
extern void test_addConstraintFunc(sqlite3_context*, int, sqlite3_value**);

static sqlite3 *gDb = NULL;
static const char *gFuncName = "test_addc";

/* Helper to register the function under a SQL name on gDb */
static void register_add_constraint_func(void){
  int rc = sqlite3_create_function(gDb, gFuncName, 3, SQLITE_UTF8, 0,
                                   test_addConstraintFunc, 0, 0);
  TEST_ASSERT_EQUAL_INT_MESSAGE(SQLITE_OK, rc, "sqlite3_create_function failed");
}

/* Helper to call the function and capture result text or error.
   Returns a heap-allocated copy of the result text (sqlite3_mprintf) on success
   (caller must sqlite3_free), or NULL if result is SQL NULL or on error.
   If out_step_rc is non-NULL, it is set to the return from sqlite3_step.
   If out_errcode is non-NULL, it is set to sqlite3_errcode(gDb) after step.
*/
static char* call_add_constraint(const char *zCreate, const char *zCons, int iCol,
                                 int *out_step_rc, int *out_errcode){
  char *zSql = sqlite3_mprintf("SELECT %s(%Q,%Q,%d)", gFuncName, zCreate, zCons, iCol);
  TEST_ASSERT_NOT_NULL_MESSAGE(zSql, "sqlite3_mprintf returned NULL");
  sqlite3_stmt *pStmt = NULL;
  int rc = sqlite3_prepare_v2(gDb, zSql, -1, &pStmt, 0);
  sqlite3_free(zSql);
  TEST_ASSERT_EQUAL_INT_MESSAGE(SQLITE_OK, rc, "sqlite3_prepare_v2 failed");
  rc = sqlite3_step(pStmt);
  if( out_step_rc ) *out_step_rc = rc;
  if( out_errcode ) *out_errcode = sqlite3_errcode(gDb);
  char *res = NULL;
  if( rc==SQLITE_ROW ){
    const unsigned char *txt = sqlite3_column_text(pStmt, 0);
    if( txt ){
      res = (char*)sqlite3_mprintf("%s", txt);
    }
  }
  sqlite3_finalize(pStmt);
  return res;
}

void setUp(void) {
  int rc = sqlite3_open(":memory:", &gDb);
  TEST_ASSERT_EQUAL_INT_MESSAGE(SQLITE_OK, rc, "sqlite3_open failed");
  register_add_constraint_func();
}

void tearDown(void) {
  if( gDb ){
    sqlite3_close(gDb);
    gDb = NULL;
  }
}

/* Test: insert constraint into the first (0th) column */
void test_addConstraintFunc_adds_to_first_column_simple(void){
  int stepRc = 0, err = 0;
  const char *inSql = "CREATE TABLE t(a, b)";
  const char *cons  = "CHECK(a>0)";
  char *out = call_add_constraint(inSql, cons, 0, &stepRc, &err);
  TEST_ASSERT_EQUAL_INT(SQLITE_ROW, stepRc);
  TEST_ASSERT_EQUAL_INT(SQLITE_OK, err);
  TEST_ASSERT_NOT_NULL(out);
  TEST_ASSERT_EQUAL_STRING("CREATE TABLE t(a CHECK(a>0), b)", out);
  sqlite3_free(out);
}

/* Test: insert constraint into the second (1st) column */
void test_addConstraintFunc_adds_to_second_column_simple(void){
  int stepRc = 0, err = 0;
  const char *inSql = "CREATE TABLE t(a, b)";
  const char *cons  = "DEFAULT 5";
  char *out = call_add_constraint(inSql, cons, 1, &stepRc, &err);
  TEST_ASSERT_EQUAL_INT(SQLITE_ROW, stepRc);
  TEST_ASSERT_EQUAL_INT(SQLITE_OK, err);
  TEST_ASSERT_NOT_NULL(out);
  TEST_ASSERT_EQUAL_STRING("CREATE TABLE t(a, b DEFAULT 5)", out);
  sqlite3_free(out);
}

/* Test: append a table constraint with iCol < 0 (use -1) */
void test_addConstraintFunc_appends_table_constraint_neg1(void){
  int stepRc = 0, err = 0;
  const char *inSql = "CREATE TABLE t(a INTEGER, b TEXT)";
  const char *cons  = "UNIQUE(a,b)";
  char *out = call_add_constraint(inSql, cons, -1, &stepRc, &err);
  TEST_ASSERT_EQUAL_INT(SQLITE_ROW, stepRc);
  TEST_ASSERT_EQUAL_INT(SQLITE_OK, err);
  TEST_ASSERT_NOT_NULL(out);
  TEST_ASSERT_EQUAL_STRING("CREATE TABLE t(a INTEGER, b TEXT, UNIQUE(a,b))", out);
  sqlite3_free(out);
}

/* Test: handles comments and nested parentheses in type, inserts before delimiter.
   Note: The implementation advances past whitespace/comments before the delimiter,
   so the inserted constraint appears AFTER any trailing comments and BEFORE the comma. */
void test_addConstraintFunc_handles_comments_and_parens(void){
  int stepRc = 0, err = 0;
  const char *inSql =
      "CREATE TABLE t( /*c*/ a DECIMAL(10, 5) /*x*/, b TEXT )";
  const char *cons  = "NOT NULL";
  char *out = call_add_constraint(inSql, cons, 0, &stepRc, &err);
  TEST_ASSERT_EQUAL_INT(SQLITE_ROW, stepRc);
  TEST_ASSERT_EQUAL_INT(SQLITE_OK, err);
  TEST_ASSERT_NOT_NULL(out);
  TEST_ASSERT_EQUAL_STRING(
    "CREATE TABLE t( /*c*/ a DECIMAL(10, 5) /*x*/ NOT NULL, b TEXT )", out);
  sqlite3_free(out);
}

/* Test: nested parentheses inside default expressions */
void test_addConstraintFunc_handles_nested_parens_in_defaults(void){
  int stepRc = 0, err = 0;
  const char *inSql =
      "CREATE TABLE t(a TEXT DEFAULT (printf('x(%d)', 1)), b INT)";
  const char *cons  = "CHECK(length(a)>0)";
  char *out = call_add_constraint(inSql, cons, 0, &stepRc, &err);
  TEST_ASSERT_EQUAL_INT(SQLITE_ROW, stepRc);
  TEST_ASSERT_EQUAL_INT(SQLITE_OK, err);
  TEST_ASSERT_NOT_NULL(out);
  TEST_ASSERT_EQUAL_STRING(
    "CREATE TABLE t(a TEXT DEFAULT (printf('x(%d)', 1)) CHECK(length(a)>0), b INT)", out);
  sqlite3_free(out);
}

/* Test: NULL SQL input returns NULL result (no error set) */
void test_addConstraintFunc_null_sql_returns_null(void){
  int stepRc = 0, err = 0;
  char *out = call_add_constraint(NULL, "ANY", 0, &stepRc, &err);
  TEST_ASSERT_EQUAL_INT(SQLITE_ROW, stepRc);
  TEST_ASSERT_EQUAL_INT(SQLITE_OK, err);
  TEST_ASSERT_NULL(out);
  sqlite3_free(out); /* ok to free NULL */
}

/* Test: out-of-range column index triggers SQLITE_CORRUPT via illegal token handling */
void test_addConstraintFunc_out_of_range_column_reports_corrupt(void){
  int stepRc = 0, err = 0;
  /* Only 1 column present, ask for column index 5 */
  const char *inSql = "CREATE TABLE t(a)";
  char *out = call_add_constraint(inSql, "ZZZ", 5, &stepRc, &err);
  TEST_ASSERT(out == NULL);
  TEST_ASSERT_EQUAL_INT(SQLITE_ERROR, stepRc);
  TEST_ASSERT_EQUAL_INT(SQLITE_CORRUPT, err);
}

/* Test: malformed SQL (unterminated string) triggers SQLITE_CORRUPT */
void test_addConstraintFunc_malformed_sql_reports_corrupt(void){
  int stepRc = 0, err = 0;
  /* Unterminated string literal inside column definition */
  const char *inSql = "CREATE TABLE t(a DEFAULT 'unterminated, b INT)";
  char *out = call_add_constraint(inSql, "NOT NULL", 0, &stepRc, &err);
  TEST_ASSERT(out == NULL);
  TEST_ASSERT_EQUAL_INT(SQLITE_ERROR, stepRc);
  TEST_ASSERT_EQUAL_INT(SQLITE_CORRUPT, err);
}

int main(void) {
  UNITY_BEGIN();
  RUN_TEST(test_addConstraintFunc_adds_to_first_column_simple);
  RUN_TEST(test_addConstraintFunc_adds_to_second_column_simple);
  RUN_TEST(test_addConstraintFunc_appends_table_constraint_neg1);
  RUN_TEST(test_addConstraintFunc_handles_comments_and_parens);
  RUN_TEST(test_addConstraintFunc_handles_nested_parens_in_defaults);
  RUN_TEST(test_addConstraintFunc_null_sql_returns_null);
  RUN_TEST(test_addConstraintFunc_out_of_range_column_reports_corrupt);
  RUN_TEST(test_addConstraintFunc_malformed_sql_reports_corrupt);
  return UNITY_END();
}