AryaWu/sqlite
0
1#include "sqliteInt.h"2#include "unity.h"3#include <string.h>4#include <stdio.h>5 6/* Global db handle for tests */7static sqlite3 *gDb = NULL;8 9/* Helpers to create minimal Parse/pNewTable environment needed by sqlite3AlterFinishAddColumn */10 11static void init_db_and_base_table(const char *zCreate){12 int rc;13 rc = sqlite3_open(":memory:", &gDb);14 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);15 rc = sqlite3_exec(gDb, zCreate, 0, 0, 0);16 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);17}18 19/* Allocate a minimal Table used as pParse->pNewTable for ALTER ADD COLUMN. */20static Table *allocNewAlterTable(sqlite3 *db, const char *zTabName){21 Table *pNew = (Table*)sqlite3DbMallocZero(db, sizeof(Table));22 TEST_ASSERT_NOT_NULL(pNew);23 /* Ordinary table with schema = main */24 pNew->pSchema = db->aDb[0].pSchema;25 /* pNew->zName must be "sqlite_altertab_<zTabName>" as code expects to skip first 16 chars */26 pNew->zName = sqlite3MPrintf(db, "sqlite_altertab_%s", zTabName);27 TEST_ASSERT_NOT_NULL(pNew->zName);28 /* Single column which is the new column */29 pNew->nCol = 1;30 pNew->aCol = (Column*)sqlite3DbMallocZero(db, sizeof(Column));31 TEST_ASSERT_NOT_NULL(pNew->aCol);32 /* Make sure this is treated as ordinary table */33 pNew->tabFlags = 0; /* IsOrdinaryTable() should pass */34 /* Some reasonable addColOffset value used by printf() in nested parse */35 pNew->u.tab.addColOffset = 1;36 return pNew;37}38 39/* Construct a TK_SPAN Expr whose pLeft has the given op (e.g., TK_NULL, TK_INTEGER, TK_FUNCTION) */40static Expr *makeSpanExprWithLeft(sqlite3 *db, int leftOp){41 Expr *pLeft = (Expr*)sqlite3DbMallocZero(db, sizeof(Expr));42 TEST_ASSERT_NOT_NULL(pLeft);43 pLeft->op = (u8)leftOp;44 Expr *pSpan = (Expr*)sqlite3DbMallocZero(db, sizeof(Expr));45 TEST_ASSERT_NOT_NULL(pSpan);46 pSpan->op = TK_SPAN;47 pSpan->pLeft = pLeft;48 return pSpan;49}50 51/* Initialize a Parse object with the given pNewTable and coldef Token. */52static void initParse(Parse *pParse, sqlite3 *db, Table *pNew, const char *zColDef, Token *pTokOut){53 memset(pParse, 0, sizeof(*pParse));54 pParse->db = db;55 db->pParse = pParse; /* Required by sqlite3AlterFinishAddColumn asserts */56 pParse->pNewTable = pNew;57 /* Token for column definition text */58 pTokOut->z = (const unsigned char*)zColDef;59 pTokOut->n = (int)strlen(zColDef);60 pTokOut->dyn = 0;61}62 63/* Cleanup minimal allocations done for pNewTable */64static void freeNewAlterTable(sqlite3 *db, Table *pNew){65 if( pNew ){66 if( pNew->aCol ){67 /* free any default expressions we might have attached */68 if( pNew->aCol[0].pDflt ){69 sqlite3ExprDelete(db, pNew->aCol[0].pDflt);70 pNew->aCol[0].pDflt = 0;71 }72 sqlite3DbFree(db, pNew->aCol);73 }74 if( pNew->zName ) sqlite3DbFree(db, pNew->zName);75 sqlite3DbFree(db, pNew);76 }77}78 79void setUp(void) {80 /* fresh in-memory db with base table */81 init_db_and_base_table("CREATE TABLE t1(a)");82}83 84void tearDown(void) {85 if( gDb ){86 sqlite3_close(gDb);87 gDb = NULL;88 }89}90 91/* Test: PRIMARY KEY on new column is rejected immediately with error message */92void test_sqlite3AlterFinishAddColumn_rejects_primary_key(void){93 Table *pNew = allocNewAlterTable(gDb, "t1");94 Column *pCol = &pNew->aCol[0];95 pCol->colFlags |= COLFLAG_PRIMKEY;96 97 Parse sParse;98 Token sTok;99 initParse(&sParse, gDb, pNew, "x INTEGER PRIMARY KEY", &sTok);100 101 sqlite3AlterFinishAddColumn(&sParse, &sTok);102 103 TEST_ASSERT_GREATER_THAN(0, sParse.nErr);104 TEST_ASSERT_NOT_NULL(sParse.zErrMsg);105 TEST_ASSERT_EQUAL_INT(0, strstr(sParse.zErrMsg, "Cannot add a PRIMARY KEY column")==NULL);106 107 freeNewAlterTable(gDb, pNew);108}109 110/* Test: UNIQUE constraint on new column is rejected immediately with error message */111void test_sqlite3AlterFinishAddColumn_rejects_unique(void){112 Table *pNew = allocNewAlterTable(gDb, "t1");113 Column *pCol = &pNew->aCol[0];114 pCol->colFlags &= ~COLFLAG_PRIMKEY; /* ensure not primary key */115 /* Any non-NULL pIndex indicates UNIQUE column in this code-path */116 pNew->pIndex = (Index*)pNew; /* dummy non-NULL */117 118 Parse sParse;119 Token sTok;120 initParse(&sParse, gDb, pNew, "x INTEGER UNIQUE", &sTok);121 122 sqlite3AlterFinishAddColumn(&sParse, &sTok);123 124 TEST_ASSERT_GREATER_THAN(0, sParse.nErr);125 TEST_ASSERT_NOT_NULL(sParse.zErrMsg);126 TEST_ASSERT_EQUAL_INT(0, strstr(sParse.zErrMsg, "Cannot add a UNIQUE column")==NULL);127 128 /* reset to avoid dangling pIndex into freed memory */129 pNew->pIndex = 0;130 freeNewAlterTable(gDb, pNew);131}132 133/* Test: NOT NULL with literal NULL default schedules runtime error (no immediate error, VDBE generated) */134void test_sqlite3AlterFinishAddColumn_notnull_with_null_default_generates_runtime_check(void){135 Table *pNew = allocNewAlterTable(gDb, "t1");136 Column *pCol = &pNew->aCol[0];137 pCol->notNull = 1; /* any non-zero triggers the check */138 /* Default is literal NULL: represented as TK_SPAN with pLeft->op == TK_NULL */139 pCol->pDflt = makeSpanExprWithLeft(gDb, TK_NULL);140 141 Parse sParse;142 Token sTok;143 initParse(&sParse, gDb, pNew, "x INTEGER NOT NULL DEFAULT NULL", &sTok);144 145 sqlite3AlterFinishAddColumn(&sParse, &sTok);146 147 TEST_ASSERT_EQUAL_INT(0, sParse.nErr);148 TEST_ASSERT_NOT_NULL(sParse.pVdbe); /* VDBE should be created due to nested parse and follow-up code */149 150 freeNewAlterTable(gDb, pNew);151}152 153/* Test: STORED generated column is rejected via runtime check path (no immediate error, VDBE generated) */154void test_sqlite3AlterFinishAddColumn_stored_generated_column_rejected_runtime(void){155 Table *pNew = allocNewAlterTable(gDb, "t1");156 Column *pCol = &pNew->aCol[0];157 pCol->colFlags |= (COLFLAG_GENERATED | COLFLAG_STORED);158 159 Parse sParse;160 Token sTok;161 initParse(&sParse, gDb, pNew, "x GENERATED ALWAYS AS (a+1) STORED", &sTok);162 163 sqlite3AlterFinishAddColumn(&sParse, &sTok);164 165 TEST_ASSERT_EQUAL_INT(0, sParse.nErr);166 TEST_ASSERT_NOT_NULL(sParse.pVdbe);167 168 freeNewAlterTable(gDb, pNew);169}170 171/* Test: Constant default value allowed (no immediate error, VDBE generated) */172void test_sqlite3AlterFinishAddColumn_constant_default_allowed(void){173 Table *pNew = allocNewAlterTable(gDb, "t1");174 Column *pCol = &pNew->aCol[0];175 /* Not generated column */176 pCol->colFlags &= ~COLFLAG_GENERATED;177 /* A simple constant default: represent as TK_SPAN with left TK_INTEGER */178 pCol->pDflt = makeSpanExprWithLeft(gDb, TK_INTEGER);179 180 Parse sParse;181 Token sTok;182 initParse(&sParse, gDb, pNew, "x INTEGER DEFAULT 5", &sTok);183 184 sqlite3AlterFinishAddColumn(&sParse, &sTok);185 186 TEST_ASSERT_EQUAL_INT(0, sParse.nErr);187 TEST_ASSERT_NOT_NULL(sParse.pVdbe);188 189 freeNewAlterTable(gDb, pNew);190}191 192/* Test: Non-constant default leads to runtime check (no immediate error, VDBE generated) */193void test_sqlite3AlterFinishAddColumn_nonconstant_default_generates_runtime_check(void){194 Table *pNew = allocNewAlterTable(gDb, "t1");195 Column *pCol = &pNew->aCol[0];196 /* Not generated */197 pCol->colFlags &= ~COLFLAG_GENERATED;198 /* Non-constant default: represent as TK_SPAN with left TK_FUNCTION (e.g., CURRENT_TIME) */199 pCol->pDflt = makeSpanExprWithLeft(gDb, TK_FUNCTION);200 201 Parse sParse;202 Token sTok;203 initParse(&sParse, gDb, pNew, "x TEXT DEFAULT (CURRENT_TIME)", &sTok);204 205 sqlite3AlterFinishAddColumn(&sParse, &sTok);206 207 TEST_ASSERT_EQUAL_INT(0, sParse.nErr);208 TEST_ASSERT_NOT_NULL(sParse.pVdbe);209 210 freeNewAlterTable(gDb, pNew);211}212 213int main(void){214 UNITY_BEGIN();215 RUN_TEST(test_sqlite3AlterFinishAddColumn_rejects_primary_key);216 RUN_TEST(test_sqlite3AlterFinishAddColumn_rejects_unique);217 RUN_TEST(test_sqlite3AlterFinishAddColumn_notnull_with_null_default_generates_runtime_check);218 RUN_TEST(test_sqlite3AlterFinishAddColumn_stored_generated_column_rejected_runtime);219 RUN_TEST(test_sqlite3AlterFinishAddColumn_constant_default_allowed);220 RUN_TEST(test_sqlite3AlterFinishAddColumn_nonconstant_default_generates_runtime_check);221 return UNITY_END();222}