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

/* Global db handle for tests */
static sqlite3 *gDb = NULL;

/* Helpers to create minimal Parse/pNewTable environment needed by sqlite3AlterFinishAddColumn */

static void init_db_and_base_table(const char *zCreate){
  int rc;
  rc = sqlite3_open(":memory:", &gDb);
  TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);
  rc = sqlite3_exec(gDb, zCreate, 0, 0, 0);
  TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);
}

/* Allocate a minimal Table used as pParse->pNewTable for ALTER ADD COLUMN. */
static Table *allocNewAlterTable(sqlite3 *db, const char *zTabName){
  Table *pNew = (Table*)sqlite3DbMallocZero(db, sizeof(Table));
  TEST_ASSERT_NOT_NULL(pNew);
  /* Ordinary table with schema = main */
  pNew->pSchema = db->aDb[0].pSchema;
  /* pNew->zName must be "sqlite_altertab_<zTabName>" as code expects to skip first 16 chars */
  pNew->zName = sqlite3MPrintf(db, "sqlite_altertab_%s", zTabName);
  TEST_ASSERT_NOT_NULL(pNew->zName);
  /* Single column which is the new column */
  pNew->nCol = 1;
  pNew->aCol = (Column*)sqlite3DbMallocZero(db, sizeof(Column));
  TEST_ASSERT_NOT_NULL(pNew->aCol);
  /* Make sure this is treated as ordinary table */
  pNew->tabFlags = 0; /* IsOrdinaryTable() should pass */
  /* Some reasonable addColOffset value used by printf() in nested parse */
  pNew->u.tab.addColOffset = 1;
  return pNew;
}

/* Construct a TK_SPAN Expr whose pLeft has the given op (e.g., TK_NULL, TK_INTEGER, TK_FUNCTION) */
static Expr *makeSpanExprWithLeft(sqlite3 *db, int leftOp){
  Expr *pLeft = (Expr*)sqlite3DbMallocZero(db, sizeof(Expr));
  TEST_ASSERT_NOT_NULL(pLeft);
  pLeft->op = (u8)leftOp;
  Expr *pSpan = (Expr*)sqlite3DbMallocZero(db, sizeof(Expr));
  TEST_ASSERT_NOT_NULL(pSpan);
  pSpan->op = TK_SPAN;
  pSpan->pLeft = pLeft;
  return pSpan;
}

/* Initialize a Parse object with the given pNewTable and coldef Token. */
static void initParse(Parse *pParse, sqlite3 *db, Table *pNew, const char *zColDef, Token *pTokOut){
  memset(pParse, 0, sizeof(*pParse));
  pParse->db = db;
  db->pParse = pParse; /* Required by sqlite3AlterFinishAddColumn asserts */
  pParse->pNewTable = pNew;
  /* Token for column definition text */
  pTokOut->z = (const unsigned char*)zColDef;
  pTokOut->n = (int)strlen(zColDef);
  pTokOut->dyn = 0;
}

/* Cleanup minimal allocations done for pNewTable */
static void freeNewAlterTable(sqlite3 *db, Table *pNew){
  if( pNew ){
    if( pNew->aCol ){
      /* free any default expressions we might have attached */
      if( pNew->aCol[0].pDflt ){
        sqlite3ExprDelete(db, pNew->aCol[0].pDflt);
        pNew->aCol[0].pDflt = 0;
      }
      sqlite3DbFree(db, pNew->aCol);
    }
    if( pNew->zName ) sqlite3DbFree(db, pNew->zName);
    sqlite3DbFree(db, pNew);
  }
}

void setUp(void) {
  /* fresh in-memory db with base table */
  init_db_and_base_table("CREATE TABLE t1(a)");
}

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

/* Test: PRIMARY KEY on new column is rejected immediately with error message */
void test_sqlite3AlterFinishAddColumn_rejects_primary_key(void){
  Table *pNew = allocNewAlterTable(gDb, "t1");
  Column *pCol = &pNew->aCol[0];
  pCol->colFlags |= COLFLAG_PRIMKEY;

  Parse sParse;
  Token sTok;
  initParse(&sParse, gDb, pNew, "x INTEGER PRIMARY KEY", &sTok);

  sqlite3AlterFinishAddColumn(&sParse, &sTok);

  TEST_ASSERT_GREATER_THAN(0, sParse.nErr);
  TEST_ASSERT_NOT_NULL(sParse.zErrMsg);
  TEST_ASSERT_EQUAL_INT(0, strstr(sParse.zErrMsg, "Cannot add a PRIMARY KEY column")==NULL);

  freeNewAlterTable(gDb, pNew);
}

/* Test: UNIQUE constraint on new column is rejected immediately with error message */
void test_sqlite3AlterFinishAddColumn_rejects_unique(void){
  Table *pNew = allocNewAlterTable(gDb, "t1");
  Column *pCol = &pNew->aCol[0];
  pCol->colFlags &= ~COLFLAG_PRIMKEY; /* ensure not primary key */
  /* Any non-NULL pIndex indicates UNIQUE column in this code-path */
  pNew->pIndex = (Index*)pNew; /* dummy non-NULL */

  Parse sParse;
  Token sTok;
  initParse(&sParse, gDb, pNew, "x INTEGER UNIQUE", &sTok);

  sqlite3AlterFinishAddColumn(&sParse, &sTok);

  TEST_ASSERT_GREATER_THAN(0, sParse.nErr);
  TEST_ASSERT_NOT_NULL(sParse.zErrMsg);
  TEST_ASSERT_EQUAL_INT(0, strstr(sParse.zErrMsg, "Cannot add a UNIQUE column")==NULL);

  /* reset to avoid dangling pIndex into freed memory */
  pNew->pIndex = 0;
  freeNewAlterTable(gDb, pNew);
}

/* Test: NOT NULL with literal NULL default schedules runtime error (no immediate error, VDBE generated) */
void test_sqlite3AlterFinishAddColumn_notnull_with_null_default_generates_runtime_check(void){
  Table *pNew = allocNewAlterTable(gDb, "t1");
  Column *pCol = &pNew->aCol[0];
  pCol->notNull = 1; /* any non-zero triggers the check */
  /* Default is literal NULL: represented as TK_SPAN with pLeft->op == TK_NULL */
  pCol->pDflt = makeSpanExprWithLeft(gDb, TK_NULL);

  Parse sParse;
  Token sTok;
  initParse(&sParse, gDb, pNew, "x INTEGER NOT NULL DEFAULT NULL", &sTok);

  sqlite3AlterFinishAddColumn(&sParse, &sTok);

  TEST_ASSERT_EQUAL_INT(0, sParse.nErr);
  TEST_ASSERT_NOT_NULL(sParse.pVdbe); /* VDBE should be created due to nested parse and follow-up code */

  freeNewAlterTable(gDb, pNew);
}

/* Test: STORED generated column is rejected via runtime check path (no immediate error, VDBE generated) */
void test_sqlite3AlterFinishAddColumn_stored_generated_column_rejected_runtime(void){
  Table *pNew = allocNewAlterTable(gDb, "t1");
  Column *pCol = &pNew->aCol[0];
  pCol->colFlags |= (COLFLAG_GENERATED | COLFLAG_STORED);

  Parse sParse;
  Token sTok;
  initParse(&sParse, gDb, pNew, "x GENERATED ALWAYS AS (a+1) STORED", &sTok);

  sqlite3AlterFinishAddColumn(&sParse, &sTok);

  TEST_ASSERT_EQUAL_INT(0, sParse.nErr);
  TEST_ASSERT_NOT_NULL(sParse.pVdbe);

  freeNewAlterTable(gDb, pNew);
}

/* Test: Constant default value allowed (no immediate error, VDBE generated) */
void test_sqlite3AlterFinishAddColumn_constant_default_allowed(void){
  Table *pNew = allocNewAlterTable(gDb, "t1");
  Column *pCol = &pNew->aCol[0];
  /* Not generated column */
  pCol->colFlags &= ~COLFLAG_GENERATED;
  /* A simple constant default: represent as TK_SPAN with left TK_INTEGER */
  pCol->pDflt = makeSpanExprWithLeft(gDb, TK_INTEGER);

  Parse sParse;
  Token sTok;
  initParse(&sParse, gDb, pNew, "x INTEGER DEFAULT 5", &sTok);

  sqlite3AlterFinishAddColumn(&sParse, &sTok);

  TEST_ASSERT_EQUAL_INT(0, sParse.nErr);
  TEST_ASSERT_NOT_NULL(sParse.pVdbe);

  freeNewAlterTable(gDb, pNew);
}

/* Test: Non-constant default leads to runtime check (no immediate error, VDBE generated) */
void test_sqlite3AlterFinishAddColumn_nonconstant_default_generates_runtime_check(void){
  Table *pNew = allocNewAlterTable(gDb, "t1");
  Column *pCol = &pNew->aCol[0];
  /* Not generated */
  pCol->colFlags &= ~COLFLAG_GENERATED;
  /* Non-constant default: represent as TK_SPAN with left TK_FUNCTION (e.g., CURRENT_TIME) */
  pCol->pDflt = makeSpanExprWithLeft(gDb, TK_FUNCTION);

  Parse sParse;
  Token sTok;
  initParse(&sParse, gDb, pNew, "x TEXT DEFAULT (CURRENT_TIME)", &sTok);

  sqlite3AlterFinishAddColumn(&sParse, &sTok);

  TEST_ASSERT_EQUAL_INT(0, sParse.nErr);
  TEST_ASSERT_NOT_NULL(sParse.pVdbe);

  freeNewAlterTable(gDb, pNew);
}

int main(void){
  UNITY_BEGIN();
  RUN_TEST(test_sqlite3AlterFinishAddColumn_rejects_primary_key);
  RUN_TEST(test_sqlite3AlterFinishAddColumn_rejects_unique);
  RUN_TEST(test_sqlite3AlterFinishAddColumn_notnull_with_null_default_generates_runtime_check);
  RUN_TEST(test_sqlite3AlterFinishAddColumn_stored_generated_column_rejected_runtime);
  RUN_TEST(test_sqlite3AlterFinishAddColumn_constant_default_allowed);
  RUN_TEST(test_sqlite3AlterFinishAddColumn_nonconstant_default_generates_runtime_check);
  return UNITY_END();
}