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();
} |