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

static sqlite3 *gDb = NULL;

/* The wrapper for the static function under test is assumed to be provided. */
extern void test_dropConstraintFunc(sqlite3_context*, int, sqlite3_value**);

/* Register the test function as an SQL scalar function "dropc". */
static void register_dropc(sqlite3 *db){
  int rc = sqlite3_create_function(db, "dropc", 2, SQLITE_UTF8, NULL,
                                   test_dropConstraintFunc, NULL, NULL);
  TEST_ASSERT_EQUAL_INT_MESSAGE(SQLITE_OK, rc, "sqlite3_create_function(dropc) failed");
}

/* Helper: call dropc(SQL, TEXT) and capture result or error. 
   On success, returns 0 and *pzOut is sqlite3_malloc()'d string (caller must sqlite3_free()).
   On error, returns 1 and *pzErr is sqlite3_malloc()'d string (caller must sqlite3_free()).
*/
static int call_dropc_text(sqlite3 *db, const char *zSql, const char *zName,
                           char **pzOut, char **pzErr){
  int rc;
  sqlite3_stmt *pStmt = NULL;
  *pzOut = NULL;
  *pzErr = NULL;

  rc = sqlite3_prepare_v2(db, "SELECT dropc(?1, ?2)", -1, &pStmt, NULL);
  if( rc!=SQLITE_OK ){
    *pzErr = sqlite3_mprintf("%s", sqlite3_errmsg(db));
    return 1;
  }
  rc = sqlite3_bind_text(pStmt, 1, zSql, -1, SQLITE_TRANSIENT);
  if( rc==SQLITE_OK ){
    rc = sqlite3_bind_text(pStmt, 2, zName, -1, SQLITE_TRANSIENT);
  }
  if( rc!=SQLITE_OK ){
    sqlite3_finalize(pStmt);
    *pzErr = sqlite3_mprintf("%s", sqlite3_errmsg(db));
    return 1;
  }

  rc = sqlite3_step(pStmt);
  if( rc==SQLITE_ROW ){
    const unsigned char *z = sqlite3_column_text(pStmt, 0);
    if( z ){
      *pzOut = sqlite3_mprintf("%s", (const char*)z);
    }else{
      *pzOut = NULL; /* explicit NULL result */
    }
    sqlite3_finalize(pStmt);
    return 0;
  }else{
    *pzErr = sqlite3_mprintf("%s", sqlite3_errmsg(db));
    sqlite3_finalize(pStmt);
    return 1;
  }
}

/* Helper: call dropc(SQL, INTEGER) and capture result or error. */
static int call_dropc_int(sqlite3 *db, const char *zSql, int iCol,
                          char **pzOut, char **pzErr){
  int rc;
  sqlite3_stmt *pStmt = NULL;
  *pzOut = NULL;
  *pzErr = NULL;

  rc = sqlite3_prepare_v2(db, "SELECT dropc(?1, ?2)", -1, &pStmt, NULL);
  if( rc!=SQLITE_OK ){
    *pzErr = sqlite3_mprintf("%s", sqlite3_errmsg(db));
    return 1;
  }
  rc = sqlite3_bind_text(pStmt, 1, zSql, -1, SQLITE_TRANSIENT);
  if( rc==SQLITE_OK ){
    rc = sqlite3_bind_int(pStmt, 2, iCol);
  }
  if( rc!=SQLITE_OK ){
    sqlite3_finalize(pStmt);
    *pzErr = sqlite3_mprintf("%s", sqlite3_errmsg(db));
    return 1;
  }

  rc = sqlite3_step(pStmt);
  if( rc==SQLITE_ROW ){
    const unsigned char *z = sqlite3_column_text(pStmt, 0);
    if( z ){
      *pzOut = sqlite3_mprintf("%s", (const char*)z);
    }else{
      *pzOut = NULL;
    }
    sqlite3_finalize(pStmt);
    return 0;
  }else{
    *pzErr = sqlite3_mprintf("%s", sqlite3_errmsg(db));
    sqlite3_finalize(pStmt);
    return 1;
  }
}

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

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

/* Drop NOT NULL on first column */
void test_dropConstraintFunc_drop_not_null_first_column(void){
  const char *inSql = "CREATE TABLE t1(a NOT NULL,b)";
  const char *expect = "CREATE TABLE t1(a ,b)";
  char *out = NULL, *err = NULL;
  int rc = call_dropc_int(gDb, inSql, 0, &out, &err);
  TEST_ASSERT_EQUAL_INT_MESSAGE(0, rc, err ? err : "Unexpected error");
  TEST_ASSERT_NOT_NULL(out);
  TEST_ASSERT_EQUAL_STRING(expect, out);
  sqlite3_free(out);
}

/* Drop NOT NULL on column that is not NOT NULL -> should return original SQL unchanged */
void test_dropConstraintFunc_drop_not_null_noop_when_absent(void){
  const char *inSql = "CREATE TABLE t1(a,b)";
  char *out = NULL, *err = NULL;
  int rc = call_dropc_int(gDb, inSql, 1, &out, &err);
  TEST_ASSERT_EQUAL_INT_MESSAGE(0, rc, err ? err : "Unexpected error");
  TEST_ASSERT_NOT_NULL(out);
  TEST_ASSERT_EQUAL_STRING(inSql, out);
  sqlite3_free(out);
}

/* Drop named CHECK table constraint and verify comma handling */
void test_dropConstraintFunc_drop_named_check_table_constraint(void){
  const char *inSql = "CREATE TABLE t1(a,CONSTRAINT ck CHECK(a))";
  const char *expect = "CREATE TABLE t1(a)";
  char *out = NULL, *err = NULL;
  int rc = call_dropc_text(gDb, inSql, "ck", &out, &err);
  TEST_ASSERT_EQUAL_INT_MESSAGE(0, rc, err ? err : "Unexpected error");
  TEST_ASSERT_NOT_NULL(out);
  TEST_ASSERT_EQUAL_STRING(expect, out);
  sqlite3_free(out);
}

/* Attempt to drop disallowed constraint (PRIMARY KEY) -> error */
void test_dropConstraintFunc_error_on_primary_key(void){
  const char *inSql = "CREATE TABLE t1(a,CONSTRAINT pk PRIMARY KEY(a))";
  char *out = NULL, *err = NULL;
  int rc = call_dropc_text(gDb, inSql, "pk", &out, &err);
  TEST_ASSERT_EQUAL_INT(1, rc);
  TEST_ASSERT_NULL(out);
  TEST_ASSERT_NOT_NULL(err);
  TEST_ASSERT_EQUAL_STRING("constraint may not be dropped: pk", err);
  sqlite3_free(err);
}

/* Named constraint not found -> error */
void test_dropConstraintFunc_error_on_no_such_constraint(void){
  const char *inSql = "CREATE TABLE t1(a,CONSTRAINT ck CHECK(a))";
  char *out = NULL, *err = NULL;
  int rc = call_dropc_text(gDb, inSql, "nosuch", &out, &err);
  TEST_ASSERT_EQUAL_INT(1, rc);
  TEST_ASSERT_NULL(out);
  TEST_ASSERT_NOT_NULL(err);
  TEST_ASSERT_EQUAL_STRING("no such constraint: nosuch", err);
  sqlite3_free(err);
}

/* Drop named NOT NULL column constraint */
void test_dropConstraintFunc_drop_named_not_null_column_constraint(void){
  const char *inSql = "CREATE TABLE t1(a CONSTRAINT nn NOT NULL,b)";
  const char *expect = "CREATE TABLE t1(a ,b)";
  char *out = NULL, *err = NULL;
  int rc = call_dropc_text(gDb, inSql, "nn", &out, &err);
  TEST_ASSERT_EQUAL_INT_MESSAGE(0, rc, err ? err : "Unexpected error");
  TEST_ASSERT_NOT_NULL(out);
  TEST_ASSERT_EQUAL_STRING(expect, out);
  sqlite3_free(out);
}

/* Case-insensitive and quoted constraint name matching */
void test_dropConstraintFunc_case_insensitive_and_quoted_name(void){
  const char *inSql = "CREATE TABLE t1(a,CONSTRAINT \"MiX\" CHECK(a>0))";
  const char *expect = "CREATE TABLE t1(a)";
  char *out = NULL, *err = NULL;
  int rc = call_dropc_text(gDb, inSql, "mix", &out, &err);
  TEST_ASSERT_EQUAL_INT_MESSAGE(0, rc, err ? err : "Unexpected error");
  TEST_ASSERT_NOT_NULL(out);
  TEST_ASSERT_EQUAL_STRING(expect, out);
  sqlite3_free(out);
}

/* Drop only the first of two consecutive CONSTRAINT names */
void test_dropConstraintFunc_drop_first_of_two_constraint_names(void){
  const char *inSql = "CREATE TABLE t1(x CONSTRAINT one CONSTRAINT two NOT NULL)";
  const char *expect = "CREATE TABLE t1(x CONSTRAINT two NOT NULL)";
  char *out = NULL, *err = NULL;
  int rc = call_dropc_text(gDb, inSql, "one", &out, &err);
  TEST_ASSERT_EQUAL_INT_MESSAGE(0, rc, err ? err : "Unexpected error");
  TEST_ASSERT_NOT_NULL(out);
  TEST_ASSERT_EQUAL_STRING(expect, out);
  sqlite3_free(out);
}

int main(void) {
  UNITY_BEGIN();
  RUN_TEST(test_dropConstraintFunc_drop_not_null_first_column);
  RUN_TEST(test_dropConstraintFunc_drop_not_null_noop_when_absent);
  RUN_TEST(test_dropConstraintFunc_drop_named_check_table_constraint);
  RUN_TEST(test_dropConstraintFunc_error_on_primary_key);
  RUN_TEST(test_dropConstraintFunc_error_on_no_such_constraint);
  RUN_TEST(test_dropConstraintFunc_drop_named_not_null_column_constraint);
  RUN_TEST(test_dropConstraintFunc_case_insensitive_and_quoted_name);
  RUN_TEST(test_dropConstraintFunc_drop_first_of_two_constraint_names);
  return UNITY_END();
}