AryaWu/sqlite
0
1#include "sqliteInt.h"2#include "unity.h"3#include <string.h>4#include <stdlib.h>5 6/* Globals used by tests */7static sqlite3 *gDb = NULL;8 9/* Unity setup/teardown */10void setUp(void) {11 /* Ensure no leftover db */12 if( gDb ){13 sqlite3_close(gDb);14 gDb = NULL;15 }16 TEST_ASSERT_EQUAL(SQLITE_OK, sqlite3_open(":memory:", &gDb));17}18 19void tearDown(void) {20 if( gDb ){21 sqlite3_close(gDb);22 gDb = NULL;23 }24}25 26/* Helpers */27 28/* Find the first VdbeOp with a given opcode. Returns NULL if not found. */29static VdbeOp *findFirstOpByOpcode(Vdbe *v, int opcode){30 if( v==0 || v->nOp<=0 ) return NULL;31 for(int i=0; i<v->nOp; i++){32 if( v->aOp[i].opcode==opcode ) return &v->aOp[i];33 }34 return NULL;35}36 37/* Find the last VdbeOp with a given opcode. Returns NULL if not found. */38static VdbeOp *findLastOpByOpcode(Vdbe *v, int opcode){39 if( v==0 || v->nOp<=0 ) return NULL;40 for(int i=v->nOp-1; i>=0; i--){41 if( v->aOp[i].opcode==opcode ) return &v->aOp[i];42 }43 return NULL;44}45 46/* Build the expected affinity string after sqlite3ComputeGeneratedColumns modifies47** the OP_Affinity for stored generated columns.48** The input is the table's current zColAff (baseline). We copy it into outBuf and49** then, scanning columns left-to-right, replace characters at positions corresponding50** to non-virtual stored generated columns with '@'. We only apply replacements while51** jj < baseLen, matching the target function's loop that stops at the end of zP4.52*/53static void buildExpectedAdjustedAffinityForStored(Table *pTab, char *outBuf, int outBufSize, int *pOutLen){54 TEST_ASSERT_NOT_NULL(pTab);55 TEST_ASSERT_NOT_NULL(pTab->zColAff);56 int baseLen = sqlite3Strlen30NN(pTab->zColAff);57 TEST_ASSERT_TRUE_MESSAGE(baseLen < outBufSize, "Output buffer too small for affinity string");58 memcpy(outBuf, pTab->zColAff, baseLen);59 outBuf[baseLen] = 0;60 int jj = 0;61 for(int ii=0; ii<pTab->nCol && jj<baseLen; ii++){62 Column *pCol = &pTab->aCol[ii];63 if( (pCol->colFlags & COLFLAG_VIRTUAL)!=0 ){64 continue;65 }66 if( (pCol->colFlags & COLFLAG_STORED)!=0 ){67 outBuf[jj] = SQLITE_AFF_NONE; /* '@' */68 }69 jj++;70 }71 if( pOutLen ) *pOutLen = baseLen;72}73 74/* Verify that no generated column retains COLFLAG_NOTAVAIL after the call */75static void assertGeneratedColsAvailable(Table *pTab){76 for(int i=0; i<pTab->nCol; i++){77 Column *pCol = &pTab->aCol[i];78 if( (pCol->colFlags & COLFLAG_GENERATED)!=0 ){79 TEST_ASSERT_BITS_LOW(COLFLAG_NOTAVAIL, pCol->colFlags);80 }81 }82}83 84/* Test 1: Non-STRICT table with both stored and virtual generated columns.85** Expect OP_Affinity P4 string to have '@' for stored generated columns.86** Also ensure pParse->iSelfTab reset and COLFLAG_NOTAVAIL cleared.87*/88void test_sqlite3ComputeGeneratedColumns_affinity_adjusts_for_stored(void){89 const char *sql =90 "CREATE TABLE t1(\n"91 " a TEXT,\n"92 " b INT GENERATED ALWAYS AS (length(a)) STORED,\n"93 " c TEXT GENERATED ALWAYS AS (a || 'x') VIRTUAL\n"94 ");";95 char *zErr = 0;96 TEST_ASSERT_EQUAL_INT(SQLITE_OK, sqlite3_exec(gDb, sql, 0, 0, &zErr));97 if( zErr ){ sqlite3_free(zErr); zErr = 0; }98 99 Table *pTab = sqlite3FindTable(gDb, "t1", "main");100 TEST_ASSERT_NOT_NULL(pTab);101 TEST_ASSERT_BITS_HIGH(TF_HasGenerated, pTab->tabFlags);102 TEST_ASSERT_BITS_LOW(TF_Strict, pTab->tabFlags); /* Non-STRICT */103 104 Parse sParse;105 memset(&sParse, 0, sizeof(sParse));106 sParse.db = gDb;107 sParse.pVdbe = sqlite3VdbeCreate(gDb);108 TEST_ASSERT_NOT_NULL(sParse.pVdbe);109 110 /* iRegStore = 1: pretend to load starting at register 1 */111 sqlite3ComputeGeneratedColumns(&sParse, 1, pTab);112 113 /* There should be an OP_Affinity whose P4 string is adjusted */114 VdbeOp *pAff = findFirstOpByOpcode(sParse.pVdbe, OP_Affinity);115 TEST_ASSERT_NOT_NULL_MESSAGE(pAff, "OP_Affinity not found");116 TEST_ASSERT_NOT_NULL(pTab->zColAff);117 118 char expected[64];119 int expectedLen = 0;120 buildExpectedAdjustedAffinityForStored(pTab, expected, (int)sizeof(expected), &expectedLen);121 122 TEST_ASSERT_GREATER_THAN_INT(0, expectedLen);123 TEST_ASSERT_EQUAL_INT(expectedLen, pAff->p2); /* p2 holds number of affinity chars */124 TEST_ASSERT_NOT_NULL(pAff->p4.z);125 126 /* Compare exactly p2 bytes */127 TEST_ASSERT_EQUAL_INT(0, memcmp(expected, pAff->p4.z, pAff->p2));128 129 /* Ensure iSelfTab restored */130 TEST_ASSERT_EQUAL_INT(0, sParse.iSelfTab);131 132 /* Ensure generated columns are no longer marked NOTAVAIL */133 assertGeneratedColsAvailable(pTab);134 135 sqlite3VdbeDelete(sParse.pVdbe);136}137 138/* Test 2: STRICT table with a stored generated column.139** Expect OP_TypeCheck with p3 set to 1 (skip checking generated columns).140*/141void test_sqlite3ComputeGeneratedColumns_strict_sets_typecheck_p3_for_stored(void){142 const char *sql =143 "CREATE TABLE t2(\n"144 " a TEXT,\n"145 " b INT GENERATED ALWAYS AS (length(a)) STORED\n"146 ") STRICT;";147 char *zErr = 0;148 TEST_ASSERT_EQUAL_INT(SQLITE_OK, sqlite3_exec(gDb, sql, 0, 0, &zErr));149 if( zErr ){ sqlite3_free(zErr); zErr = 0; }150 151 Table *pTab = sqlite3FindTable(gDb, "t2", "main");152 TEST_ASSERT_NOT_NULL(pTab);153 TEST_ASSERT_BITS_HIGH(TF_HasGenerated, pTab->tabFlags);154 TEST_ASSERT_BITS_HIGH(TF_Strict, pTab->tabFlags);155 156 Parse sParse;157 memset(&sParse, 0, sizeof(sParse));158 sParse.db = gDb;159 sParse.pVdbe = sqlite3VdbeCreate(gDb);160 TEST_ASSERT_NOT_NULL(sParse.pVdbe);161 162 sqlite3ComputeGeneratedColumns(&sParse, 3, pTab); /* arbitrary iRegStore */163 164 /* In STRICT mode, sqlite3TableAffinity emits OP_TypeCheck and we expect p3==1 */165 VdbeOp *pTC = findFirstOpByOpcode(sParse.pVdbe, OP_TypeCheck);166 TEST_ASSERT_NOT_NULL_MESSAGE(pTC, "OP_TypeCheck not found");167 TEST_ASSERT_EQUAL_INT(1, pTC->p3); /* Generated columns should not be type-checked */168 169 TEST_ASSERT_EQUAL_INT(0, sParse.iSelfTab);170 assertGeneratedColsAvailable(pTab);171 172 sqlite3VdbeDelete(sParse.pVdbe);173}174 175/* Test 3: STRICT table with only virtual generated columns.176** Expect OP_TypeCheck present but p3 remains 0 (nothing to skip).177*/178void test_sqlite3ComputeGeneratedColumns_strict_virtual_only_leaves_typecheck_p3_zero(void){179 const char *sql =180 "CREATE TABLE t3(\n"181 " a TEXT,\n"182 " c TEXT GENERATED ALWAYS AS (a || 'x') VIRTUAL\n"183 ") STRICT;";184 char *zErr = 0;185 TEST_ASSERT_EQUAL_INT(SQLITE_OK, sqlite3_exec(gDb, sql, 0, 0, &zErr));186 if( zErr ){ sqlite3_free(zErr); zErr = 0; }187 188 Table *pTab = sqlite3FindTable(gDb, "t3", "main");189 TEST_ASSERT_NOT_NULL(pTab);190 TEST_ASSERT_BITS_HIGH(TF_HasGenerated, pTab->tabFlags);191 TEST_ASSERT_BITS_HIGH(TF_Strict, pTab->tabFlags);192 193 /* Sanity: Ensure there are no stored generated columns */194 int hasStored = 0;195 for(int i=0; i<pTab->nCol; i++){196 if( (pTab->aCol[i].colFlags & COLFLAG_STORED)!=0 ){197 hasStored = 1;198 break;199 }200 }201 TEST_ASSERT_EQUAL_INT(0, hasStored);202 203 Parse sParse;204 memset(&sParse, 0, sizeof(sParse));205 sParse.db = gDb;206 sParse.pVdbe = sqlite3VdbeCreate(gDb);207 TEST_ASSERT_NOT_NULL(sParse.pVdbe);208 209 sqlite3ComputeGeneratedColumns(&sParse, 5, pTab);210 211 VdbeOp *pTC = findFirstOpByOpcode(sParse.pVdbe, OP_TypeCheck);212 TEST_ASSERT_NOT_NULL_MESSAGE(pTC, "OP_TypeCheck not found");213 TEST_ASSERT_EQUAL_INT(0, pTC->p3); /* Nothing to skip: no stored generated columns */214 215 TEST_ASSERT_EQUAL_INT(0, sParse.iSelfTab);216 assertGeneratedColsAvailable(pTab);217 218 sqlite3VdbeDelete(sParse.pVdbe);219}220 221/* Main */222int main(void){223 UNITY_BEGIN();224 RUN_TEST(test_sqlite3ComputeGeneratedColumns_affinity_adjusts_for_stored);225 RUN_TEST(test_sqlite3ComputeGeneratedColumns_strict_sets_typecheck_p3_for_stored);226 RUN_TEST(test_sqlite3ComputeGeneratedColumns_strict_virtual_only_leaves_typecheck_p3_zero);227 return UNITY_END();228}