AryaWu/sqlite
0
1#include "sqliteInt.h"2#include "unity.h"3#include <string.h>4 5static sqlite3 *gDb = NULL;6 7static void initParse(Parse *pParse){8 memset(pParse, 0, sizeof(*pParse));9 pParse->db = gDb;10}11 12static Table* allocTable(const char *zName, int nCol){13 Table *p = (Table*)sqlite3DbMallocZero(gDb, sizeof(Table));14 TEST_ASSERT_NOT_NULL_MESSAGE(p, "sqlite3DbMallocZero(Table) failed");15 p->zName = sqlite3DbStrDup(gDb, zName);16 TEST_ASSERT_NOT_NULL_MESSAGE(p->zName, "sqlite3DbStrDup(zName) failed");17 p->nCol = nCol;18 p->nNVCol = nCol;19 p->aCol = (Column*)sqlite3DbMallocZero(gDb, sizeof(Column)*nCol);20 TEST_ASSERT_NOT_NULL_MESSAGE(p->aCol, "sqlite3DbMallocZero(aCol) failed");21 p->iPKey = -1; /* No INTEGER PRIMARY KEY */22 /* Ordinary table (default flags 0). Leave p->pSchema NULL for early-return tests. */23 return p;24}25 26static void freeTable(Table *p){27 if( p ){28 /* Use the SQLite helper that frees all parts of Table safely. */29 sqlite3DeleteTable(gDb, p);30 }31}32 33void setUp(void) {34 int rc = sqlite3_open(":memory:", &gDb);35 TEST_ASSERT_EQUAL_INT_MESSAGE(SQLITE_OK, rc, "sqlite3_open failed");36}37 38void tearDown(void) {39 if( gDb ){40 sqlite3_close(gDb);41 gDb = NULL;42 }43}44 45/* Helper to make a minimal non-NULL Token to avoid top-level early return */46static void makeDummyEndToken(Token *pTok){47 static const char zDummy[] = ")";48 pTok->z = zDummy;49 pTok->n = 1;50}51 52/* Test: early return when pEnd==NULL and pSelect==NULL */53void test_sqlite3EndTable_early_return_when_no_end_or_select(void){54 Parse pParse; initParse(&pParse);55 Table *p = allocTable("t", 1);56 pParse.pNewTable = p;57 58 /* Before call */59 unsigned preFlags = p->tabFlags;60 int preErr = pParse.nErr;61 62 sqlite3EndTable(&pParse, NULL, NULL, 0, NULL);63 64 TEST_ASSERT_EQUAL_UINT(preFlags, p->tabFlags);65 TEST_ASSERT_EQUAL_INT(preErr, pParse.nErr);66 67 freeTable(p);68}69 70/* Test: STRICT mode with missing datatype (COLTYPE_CUSTOM without COLFLAG_HASTYPE) -> error */71void test_sqlite3EndTable_strict_missing_datatype_error(void){72 Parse pParse; initParse(&pParse);73 Table *p = allocTable("t", 1);74 pParse.pNewTable = p;75 76 /* Column with custom type but no explicit type string -> missing datatype */77 p->aCol[0].zCnName = sqlite3DbStrDup(gDb, "c1");78 TEST_ASSERT_NOT_NULL(p->aCol[0].zCnName);79 p->aCol[0].eCType = COLTYPE_CUSTOM;80 p->aCol[0].colFlags = 0; /* no COLFLAG_HASTYPE */81 p->aCol[0].notNull = OE_None;82 83 Token tEnd; makeDummyEndToken(&tEnd);84 85 int preErr = pParse.nErr;86 sqlite3EndTable(&pParse, NULL, &tEnd, TF_Strict, NULL);87 88 TEST_ASSERT_TRUE_MESSAGE(pParse.nErr > preErr, "STRICT missing datatype should set an error");89 TEST_ASSERT_TRUE(p->tabFlags & TF_Strict);90 91 freeTable(p);92}93 94/* Test: STRICT mode with unknown datatype (COLTYPE_CUSTOM with COLFLAG_HASTYPE) -> error */95void test_sqlite3EndTable_strict_unknown_datatype_error(void){96 Parse pParse; initParse(&pParse);97 Table *p = allocTable("t", 1);98 pParse.pNewTable = p;99 100 p->aCol[0].zCnName = sqlite3DbStrDup(gDb, "c1");101 TEST_ASSERT_NOT_NULL(p->aCol[0].zCnName);102 p->aCol[0].eCType = COLTYPE_CUSTOM;103 p->aCol[0].colFlags = COLFLAG_HASTYPE; /* has a (custom) type name */104 p->aCol[0].notNull = OE_None;105 106 Token tEnd; makeDummyEndToken(&tEnd);107 108 int preErr = pParse.nErr;109 sqlite3EndTable(&pParse, NULL, &tEnd, TF_Strict, NULL);110 111 TEST_ASSERT_TRUE_MESSAGE(pParse.nErr > preErr, "STRICT unknown datatype should set an error");112 TEST_ASSERT_TRUE(p->tabFlags & TF_Strict);113 114 freeTable(p);115}116 117/* Test: STRICT promotes PK cols to NOT NULL and sets affinity for COLTYPE_ANY.118 Force early return later using WITHOUT ROWID + AUTOINCREMENT conflict to avoid deep VDBE paths. */119void test_sqlite3EndTable_strict_promotes_pk_to_not_null_and_sets_affinity(void){120 Parse pParse; initParse(&pParse);121 Table *p = allocTable("t", 2);122 pParse.pNewTable = p;123 124 /* Two PK columns, initially not-null=OE_None */125 p->aCol[0].zCnName = sqlite3DbStrDup(gDb, "c1");126 p->aCol[1].zCnName = sqlite3DbStrDup(gDb, "c2");127 TEST_ASSERT_NOT_NULL(p->aCol[0].zCnName);128 TEST_ASSERT_NOT_NULL(p->aCol[1].zCnName);129 p->aCol[0].colFlags = COLFLAG_PRIMKEY;130 p->aCol[1].colFlags = COLFLAG_PRIMKEY;131 p->aCol[0].notNull = OE_None;132 p->aCol[1].notNull = OE_None;133 134 /* Set types so STRICT does not throw datatype error */135 p->aCol[0].eCType = COLTYPE_ANY; /* will set affinity to BLOB */136 p->aCol[1].eCType = COLTYPE_ANY; /* will set affinity to BLOB */137 138 /* Make AUTOINCREMENT to conflict with WITHOUT ROWID and force return after STRICT */139 p->tabFlags |= TF_Autoincrement;140 141 Token tEnd; makeDummyEndToken(&tEnd);142 143 int preErr = pParse.nErr;144 sqlite3EndTable(&pParse, NULL, &tEnd, (TF_Strict | TF_WithoutRowid), NULL);145 146 /* STRICT effects should have occurred before the WITHOUT ROWID error */147 TEST_ASSERT_TRUE(p->tabFlags & TF_Strict);148 TEST_ASSERT_TRUE(p->tabFlags & TF_HasNotNull);149 TEST_ASSERT_EQUAL_INT(OE_Abort, p->aCol[0].notNull);150 TEST_ASSERT_EQUAL_INT(OE_Abort, p->aCol[1].notNull);151 TEST_ASSERT_EQUAL_INT(SQLITE_AFF_BLOB, p->aCol[0].affinity);152 TEST_ASSERT_EQUAL_INT(SQLITE_AFF_BLOB, p->aCol[1].affinity);153 154 /* Confirm an error was reported due to AUTOINCREMENT not allowed on WITHOUT ROWID */155 TEST_ASSERT_TRUE_MESSAGE(pParse.nErr > preErr, "Expected error from AUTOINCREMENT on WITHOUT ROWID table");156 157 freeTable(p);158}159 160/* Test: db->init.busy branch sets tnum and TF_Readonly when newTnum==1.161 Use STRICT missing datatype to force early return after init.busy handling. */162void test_sqlite3EndTable_init_busy_sets_tnum_and_readonly(void){163 Parse pParse; initParse(&pParse);164 Table *p = allocTable("t", 1);165 pParse.pNewTable = p;166 167 /* Prepare a STRICT error to return after init.busy handling */168 p->aCol[0].zCnName = sqlite3DbStrDup(gDb, "c1");169 TEST_ASSERT_NOT_NULL(p->aCol[0].zCnName);170 p->aCol[0].eCType = COLTYPE_CUSTOM; /* custom type */171 p->aCol[0].colFlags = 0; /* no HASTYPE -> missing datatype */172 p->aCol[0].notNull = OE_None;173 174 /* Force init.busy path */175 gDb->init.busy = 1;176 gDb->init.newTnum = 1; /* will cause TF_Readonly */177 178 Token tEnd; makeDummyEndToken(&tEnd);179 180 int preErr = pParse.nErr;181 sqlite3EndTable(&pParse, NULL, &tEnd, TF_Strict, NULL);182 183 TEST_ASSERT_EQUAL_INT(1, p->tnum);184 TEST_ASSERT_TRUE(p->tabFlags & TF_Readonly);185 TEST_ASSERT_TRUE_MESSAGE(pParse.nErr > preErr, "STRICT missing datatype should set an error");186 187 /* Reset init state for safety */188 gDb->init.busy = 0;189 gDb->init.newTnum = 0;190 191 freeTable(p);192}193 194/* Test: WITHOUT ROWID requires PRIMARY KEY -> error when missing */195void test_sqlite3EndTable_without_rowid_missing_pk_error(void){196 Parse pParse; initParse(&pParse);197 Table *p = allocTable("t", 1);198 pParse.pNewTable = p;199 200 /* No TF_HasPrimaryKey bit and no PK index -> should error */201 p->aCol[0].zCnName = sqlite3DbStrDup(gDb, "c1");202 TEST_ASSERT_NOT_NULL(p->aCol[0].zCnName);203 p->aCol[0].eCType = COLTYPE_ANY; /* benign */204 205 Token tEnd; makeDummyEndToken(&tEnd);206 207 int preErr = pParse.nErr;208 sqlite3EndTable(&pParse, NULL, &tEnd, TF_WithoutRowid, NULL);209 210 TEST_ASSERT_TRUE_MESSAGE(pParse.nErr > preErr, "WITHOUT ROWID without PRIMARY KEY should set an error");211 212 freeTable(p);213}214 215int main(void) {216 UNITY_BEGIN();217 RUN_TEST(test_sqlite3EndTable_early_return_when_no_end_or_select);218 RUN_TEST(test_sqlite3EndTable_strict_missing_datatype_error);219 RUN_TEST(test_sqlite3EndTable_strict_unknown_datatype_error);220 RUN_TEST(test_sqlite3EndTable_strict_promotes_pk_to_not_null_and_sets_affinity);221 RUN_TEST(test_sqlite3EndTable_init_busy_sets_tnum_and_readonly);222 RUN_TEST(test_sqlite3EndTable_without_rowid_missing_pk_error);223 return UNITY_END();224}