sqlite / tests /tests_alter_sqlite3AlterFinishAddColumn.c
AryaWu's picture
Upload folder using huggingface_hub
7510827 verified
#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();
}