AryaWu/sqlite
0
1#include "sqliteInt.h"2#include "unity.h"3#include <stdlib.h>4#include <string.h>5#include <limits.h>6#include <math.h>7 8/* The wrapper provided for calling the static function under test */9extern void test_sumStep(sqlite3_context *context, int argc, sqlite3_value **argv);10 11/* Helper to initialize a sqlite3_context for aggregate usage */12static void initAggContext(sqlite3 *db, sqlite3_context *pCtx, Mem *pAggMem){13 memset(pCtx, 0, sizeof(*pCtx));14 memset(pAggMem, 0, sizeof(*pAggMem));15 sqlite3VdbeMemInit(pAggMem, db, 0);16 pCtx->db = db;17 pCtx->pMem = pAggMem;18}19 20/* Helpers to create sqlite3_value instances */21static sqlite3_value *makeIntValue(sqlite3 *db, i64 v){22 sqlite3_value *p = sqlite3ValueNew(db);23 TEST_ASSERT_NOT_NULL(p);24 sqlite3VdbeMemSetInt64((Mem*)p, v);25 return p;26}27static sqlite3_value *makeDoubleValue(sqlite3 *db, double v){28 sqlite3_value *p = sqlite3ValueNew(db);29 TEST_ASSERT_NOT_NULL(p);30 sqlite3VdbeMemSetDouble((Mem*)p, v);31 return p;32}33static sqlite3_value *makeNullValue(sqlite3 *db){34 sqlite3_value *p = sqlite3ValueNew(db);35 TEST_ASSERT_NOT_NULL(p);36 sqlite3VdbeMemSetNull((Mem*)p);37 return p;38}39 40/* Helper to get SumCtx from aggregate context */41static SumCtx *getSumCtx(sqlite3_context *pCtx){42 void *p = sqlite3_aggregate_context(pCtx, (int)sizeof(SumCtx));43 return (SumCtx*)p;44}45 46static double kbn_total(const SumCtx *p){47 return p->rSum + p->rErr;48}49 50void setUp(void) {51 /* No-op */52}53void tearDown(void) {54 /* No-op */55}56 57/* Test: NULL input should not change counters/flags */58void test_sumStep_null_input_does_not_increment(void){59 sqlite3 *db = 0;60 TEST_ASSERT_EQUAL(SQLITE_OK, sqlite3_open(":memory:", &db));61 sqlite3_context ctx;62 Mem agg;63 initAggContext(db, &ctx, &agg);64 65 sqlite3_value *v = makeNullValue(db);66 sqlite3_value *argv[1] = { v };67 test_sumStep(&ctx, 1, argv);68 69 SumCtx *p = getSumCtx(&ctx);70 TEST_ASSERT_NOT_NULL(p);71 TEST_ASSERT_EQUAL_INT64(0, p->cnt);72 TEST_ASSERT_EQUAL_INT(0, p->approx);73 TEST_ASSERT_EQUAL_INT(0, p->ovrfl);74 TEST_ASSERT_EQUAL_INT64(0, p->iSum);75 TEST_ASSERT_DOUBLE_WITHIN(0.0, 0.0, p->rSum);76 TEST_ASSERT_DOUBLE_WITHIN(0.0, 0.0, p->rErr);77 78 sqlite3ValueFree((Mem*)v);79 sqlite3VdbeMemSetNull(&agg);80 sqlite3_close(db);81}82 83/* Test: Single integer accumulates in iSum with approx==0 */84void test_sumStep_single_integer(void){85 sqlite3 *db = 0;86 TEST_ASSERT_EQUAL(SQLITE_OK, sqlite3_open(":memory:", &db));87 sqlite3_context ctx;88 Mem agg;89 initAggContext(db, &ctx, &agg);90 91 sqlite3_value *v = makeIntValue(db, 5);92 sqlite3_value *argv[1] = { v };93 test_sumStep(&ctx, 1, argv);94 95 SumCtx *p = getSumCtx(&ctx);96 TEST_ASSERT_NOT_NULL(p);97 TEST_ASSERT_EQUAL_INT64(1, p->cnt);98 TEST_ASSERT_EQUAL_INT(0, p->approx);99 TEST_ASSERT_EQUAL_INT(0, p->ovrfl);100 TEST_ASSERT_EQUAL_INT64(5, p->iSum);101 102 sqlite3ValueFree((Mem*)v);103 sqlite3VdbeMemSetNull(&agg);104 sqlite3_close(db);105}106 107/* Test: Two integers sum with no overflow keeps approx==0 */108void test_sumStep_two_integers_no_overflow(void){109 sqlite3 *db = 0;110 TEST_ASSERT_EQUAL(SQLITE_OK, sqlite3_open(":memory:", &db));111 sqlite3_context ctx;112 Mem agg;113 initAggContext(db, &ctx, &agg);114 115 sqlite3_value *v1 = makeIntValue(db, 7);116 sqlite3_value *argv[1] = { v1 };117 test_sumStep(&ctx, 1, argv);118 sqlite3ValueFree((Mem*)v1);119 120 sqlite3_value *v2 = makeIntValue(db, 8);121 argv[0] = v2;122 test_sumStep(&ctx, 1, argv);123 sqlite3ValueFree((Mem*)v2);124 125 SumCtx *p = getSumCtx(&ctx);126 TEST_ASSERT_NOT_NULL(p);127 TEST_ASSERT_EQUAL_INT64(2, p->cnt);128 TEST_ASSERT_EQUAL_INT(0, p->approx);129 TEST_ASSERT_EQUAL_INT(0, p->ovrfl);130 TEST_ASSERT_EQUAL_INT64(15, p->iSum);131 132 sqlite3VdbeMemSetNull(&agg);133 sqlite3_close(db);134}135 136/* Test: Integer overflow triggers approx==1 and ovrfl==1 */137void test_sumStep_integer_overflow_sets_approx_and_ovrfl(void){138 sqlite3 *db = 0;139 TEST_ASSERT_EQUAL(SQLITE_OK, sqlite3_open(":memory:", &db));140 sqlite3_context ctx;141 Mem agg;142 initAggContext(db, &ctx, &agg);143 144 sqlite3_value *v1 = makeIntValue(db, LLONG_MAX);145 sqlite3_value *argv[1] = { v1 };146 test_sumStep(&ctx, 1, argv);147 sqlite3ValueFree((Mem*)v1);148 149 sqlite3_value *v2 = makeIntValue(db, 1);150 argv[0] = v2;151 test_sumStep(&ctx, 1, argv);152 sqlite3ValueFree((Mem*)v2);153 154 SumCtx *p = getSumCtx(&ctx);155 TEST_ASSERT_NOT_NULL(p);156 TEST_ASSERT_EQUAL_INT64(2, p->cnt);157 TEST_ASSERT_EQUAL_INT(1, p->approx);158 TEST_ASSERT_EQUAL_INT(1, p->ovrfl);159 /* iSum remains the pre-overflow integer sum (LLONG_MAX) */160 TEST_ASSERT_EQUAL_INT64(LLONG_MAX, p->iSum);161 /* The KBN running total should be large; we check it's > 9e18 */162 TEST_ASSERT_TRUE(kbn_total(p) > 9e18);163 164 sqlite3VdbeMemSetNull(&agg);165 sqlite3_close(db);166}167 168/* Test: While approx==1, integer steps preserve ovrfl and real step clears it */169void test_sumStep_ovrfl_persists_with_integers_and_clears_on_real(void){170 sqlite3 *db = 0;171 TEST_ASSERT_EQUAL(SQLITE_OK, sqlite3_open(":memory:", &db));172 sqlite3_context ctx;173 Mem agg;174 initAggContext(db, &ctx, &agg);175 176 /* Cause overflow to set approx=1 and ovrfl=1 */177 sqlite3_value *v1 = makeIntValue(db, LLONG_MAX);178 sqlite3_value *argv[1] = { v1 };179 test_sumStep(&ctx, 1, argv);180 sqlite3ValueFree((Mem*)v1);181 182 sqlite3_value *v2 = makeIntValue(db, 1);183 argv[0] = v2;184 test_sumStep(&ctx, 1, argv);185 sqlite3ValueFree((Mem*)v2);186 187 SumCtx *p = getSumCtx(&ctx);188 TEST_ASSERT_NOT_NULL(p);189 TEST_ASSERT_EQUAL_INT(1, p->approx);190 TEST_ASSERT_EQUAL_INT(1, p->ovrfl);191 192 /* Add another integer: ovrfl should remain 1 */193 sqlite3_value *v3 = makeIntValue(db, 5);194 argv[0] = v3;195 test_sumStep(&ctx, 1, argv);196 sqlite3ValueFree((Mem*)v3);197 198 p = getSumCtx(&ctx);199 TEST_ASSERT_EQUAL_INT(1, p->ovrfl);200 201 /* Add a real: ovrfl should reset to 0 */202 sqlite3_value *v4 = makeDoubleValue(db, 0.0);203 argv[0] = v4;204 test_sumStep(&ctx, 1, argv);205 sqlite3ValueFree((Mem*)v4);206 207 p = getSumCtx(&ctx);208 TEST_ASSERT_EQUAL_INT(0, p->ovrfl);209 TEST_ASSERT_EQUAL_INT(1, p->approx);210 211 sqlite3VdbeMemSetNull(&agg);212 sqlite3_close(db);213}214 215/* Test: Kahan–Babuška–Neumaier precision: 1e16 + 1 - 1e16 ~= 1 */216void test_sumStep_real_cancellation_kahan_precision(void){217 sqlite3 *db = 0;218 TEST_ASSERT_EQUAL(SQLITE_OK, sqlite3_open(":memory:", &db));219 sqlite3_context ctx;220 Mem agg;221 initAggContext(db, &ctx, &agg);222 223 sqlite3_value *a = makeDoubleValue(db, 1e16);224 sqlite3_value *argv[1] = { a };225 test_sumStep(&ctx, 1, argv);226 sqlite3ValueFree((Mem*)a);227 228 sqlite3_value *b = makeDoubleValue(db, 1.0);229 argv[0] = b;230 test_sumStep(&ctx, 1, argv);231 sqlite3ValueFree((Mem*)b);232 233 sqlite3_value *c = makeDoubleValue(db, -1e16);234 argv[0] = c;235 test_sumStep(&ctx, 1, argv);236 sqlite3ValueFree((Mem*)c);237 238 SumCtx *p = getSumCtx(&ctx);239 TEST_ASSERT_NOT_NULL(p);240 TEST_ASSERT_EQUAL_INT(1, p->approx);241 TEST_ASSERT_DOUBLE_WITHIN(1e-9, 1.0, kbn_total(p));242 243 sqlite3VdbeMemSetNull(&agg);244 sqlite3_close(db);245}246 247/* Test: Integers first then real initializes KBN from integer sum */248void test_sumStep_integer_then_real_initializes_kbn_from_iSum(void){249 sqlite3 *db = 0;250 TEST_ASSERT_EQUAL(SQLITE_OK, sqlite3_open(":memory:", &db));251 sqlite3_context ctx;252 Mem agg;253 initAggContext(db, &ctx, &agg);254 255 sqlite3_value *i1 = makeIntValue(db, 2);256 sqlite3_value *argv[1] = { i1 };257 test_sumStep(&ctx, 1, argv);258 sqlite3ValueFree((Mem*)i1);259 260 sqlite3_value *i2 = makeIntValue(db, 3);261 argv[0] = i2;262 test_sumStep(&ctx, 1, argv);263 sqlite3ValueFree((Mem*)i2);264 265 /* Now add a real 0.5, should switch to approx and initialize from iSum=5 */266 sqlite3_value *r = makeDoubleValue(db, 0.5);267 argv[0] = r;268 test_sumStep(&ctx, 1, argv);269 sqlite3ValueFree((Mem*)r);270 271 SumCtx *p = getSumCtx(&ctx);272 TEST_ASSERT_NOT_NULL(p);273 TEST_ASSERT_EQUAL_INT(1, p->approx);274 TEST_ASSERT_EQUAL_INT64(3, p->cnt); /* NULLs are not added here */275 TEST_ASSERT_DOUBLE_WITHIN(1e-12, 5.5, kbn_total(p));276 TEST_ASSERT_EQUAL_INT(0, p->ovrfl);277 278 sqlite3VdbeMemSetNull(&agg);279 sqlite3_close(db);280}281 282/* Test: Real first then integer accumulates via KBN */283void test_sumStep_real_then_integer_accumulates_with_kbn(void){284 sqlite3 *db = 0;285 TEST_ASSERT_EQUAL(SQLITE_OK, sqlite3_open(":memory:", &db));286 sqlite3_context ctx;287 Mem agg;288 initAggContext(db, &ctx, &agg);289 290 sqlite3_value *r = makeDoubleValue(db, 2.25);291 sqlite3_value *argv[1] = { r };292 test_sumStep(&ctx, 1, argv);293 sqlite3ValueFree((Mem*)r);294 295 sqlite3_value *i = makeIntValue(db, 2);296 argv[0] = i;297 test_sumStep(&ctx, 1, argv);298 sqlite3ValueFree((Mem*)i);299 300 SumCtx *p = getSumCtx(&ctx);301 TEST_ASSERT_NOT_NULL(p);302 TEST_ASSERT_EQUAL_INT(1, p->approx);303 TEST_ASSERT_DOUBLE_WITHIN(1e-12, 4.25, kbn_total(p));304 TEST_ASSERT_EQUAL_INT(0, p->ovrfl);305 306 sqlite3VdbeMemSetNull(&agg);307 sqlite3_close(db);308}309 310int main(void){311 UNITY_BEGIN();312 RUN_TEST(test_sumStep_null_input_does_not_increment);313 RUN_TEST(test_sumStep_single_integer);314 RUN_TEST(test_sumStep_two_integers_no_overflow);315 RUN_TEST(test_sumStep_integer_overflow_sets_approx_and_ovrfl);316 RUN_TEST(test_sumStep_ovrfl_persists_with_integers_and_clears_on_real);317 RUN_TEST(test_sumStep_real_cancellation_kahan_precision);318 RUN_TEST(test_sumStep_integer_then_real_initializes_kbn_from_iSum);319 RUN_TEST(test_sumStep_real_then_integer_accumulates_with_kbn);320 return UNITY_END();321}