AryaWu/sqlite
0
1#include "sqliteInt.h"2#include "unity.h"3 4#include <stdlib.h>5#include <string.h>6#include <stdint.h>7 8/* Unity fixtures */9void setUp(void) {10 /* Setup code here, or leave empty */11}12void tearDown(void) {13 /* Cleanup code here, or leave empty */14}15 16/* A simple window/aggregate function: sum of the first argument (ignores NULLs) */17typedef struct SumCtx {18 sqlite3_int64 sum;19} SumCtx;20 21static void winSumStep(sqlite3_context *ctx, int argc, sqlite3_value **argv){22 SumCtx *p = (SumCtx*)sqlite3_aggregate_context(ctx, (int)sizeof(SumCtx));23 if( p==0 ) return;24 if( argc>0 && sqlite3_value_type(argv[0])!=SQLITE_NULL ){25 p->sum += sqlite3_value_int64(argv[0]);26 }27}28static void winSumFinal(sqlite3_context *ctx){29 SumCtx *p = (SumCtx*)sqlite3_aggregate_context(ctx, 0);30 sqlite3_result_int64(ctx, p ? p->sum : 0);31}32static void winSumValue(sqlite3_context *ctx){33 SumCtx *p = (SumCtx*)sqlite3_aggregate_context(ctx, 0);34 sqlite3_result_int64(ctx, p ? p->sum : 0);35}36static void winSumInverse(sqlite3_context *ctx, int argc, sqlite3_value **argv){37 SumCtx *p = (SumCtx*)sqlite3_aggregate_context(ctx, 0);38 if( p==0 ) return;39 if( argc>0 && sqlite3_value_type(argv[0])!=SQLITE_NULL ){40 p->sum -= sqlite3_value_int64(argv[0]);41 }42}43 44/* Destructor that increments an integer counter pointed to by p */45static void xDestroyCounter(void *p){46 if( p ){47 int *pc = (int*)p;48 (*pc)++;49 }50}51 52/* Helper: create :memory: db */53static sqlite3* open_memory_db(void){54 sqlite3 *db = 0;55 int rc = sqlite3_open(":memory:", &db);56 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);57 TEST_ASSERT_NOT_NULL(db);58 return db;59}60 61/* Helper: create a simple table and insert 1,2,3 */62static void create_sample_table(sqlite3 *db){63 char *err = 0;64 int rc = sqlite3_exec(db, "CREATE TABLE t(x INTEGER);"65 "INSERT INTO t(x) VALUES(1),(2),(3);", 0, 0, &err);66 if( rc!=SQLITE_OK ){67 if( err ) sqlite3_free(err);68 }69 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);70}71 72/* Helper: run SELECT <fname>(x) FROM t and return single integer result */73static sqlite3_int64 run_aggregate_over_t(sqlite3 *db, const char *fname){74 char sql[256];75 sqlite3_stmt *stmt = 0;76 sqlite3_int64 result = 0;77 snprintf(sql, sizeof(sql), "SELECT %s(x) FROM t;", fname);78 int rc = sqlite3_prepare_v2(db, sql, -1, &stmt, 0);79 TEST_ASSERT_EQUAL_INT_MESSAGE(SQLITE_OK, rc, "prepare failed");80 rc = sqlite3_step(stmt);81 TEST_ASSERT_EQUAL_INT_MESSAGE(SQLITE_ROW, rc, "expected one row");82 result = sqlite3_column_int64(stmt, 0);83 rc = sqlite3_step(stmt);84 TEST_ASSERT_EQUAL_INT_MESSAGE(SQLITE_DONE, rc, "expected done");85 sqlite3_finalize(stmt);86 return result;87}88 89/* Test 1: Successful registration and use; destructor called on close */90static void test_sqlite3_create_window_function_success_and_use(void){91 sqlite3 *db = open_memory_db();92 create_sample_table(db);93 94 int *destroyCount = (int*)sqlite3_malloc(sizeof(int));95 TEST_ASSERT_NOT_NULL(destroyCount);96 *destroyCount = 0;97 98 int rc = sqlite3_create_window_function(99 db, "winsum", 1, SQLITE_UTF8, destroyCount,100 winSumStep, winSumFinal, winSumValue, winSumInverse, xDestroyCounter101 );102 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);103 104 /* Use as aggregate to verify registration worked */105 sqlite3_int64 agg = run_aggregate_over_t(db, "winsum");106 TEST_ASSERT_EQUAL_INT64(6, agg);107 108 /* xDestroy should not have been called yet */109 TEST_ASSERT_EQUAL_INT(0, *destroyCount);110 111 rc = sqlite3_close(db);112 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);113 114 /* xDestroy should be called once on db close */115 TEST_ASSERT_EQUAL_INT(1, *destroyCount);116 117 sqlite3_free(destroyCount);118}119 120/* Test 2: Re-registering same function name triggers old destructor immediately */121static void test_sqlite3_create_window_function_replace_triggers_destroy(void){122 sqlite3 *db = open_memory_db();123 124 int *destroyA = (int*)sqlite3_malloc(sizeof(int));125 int *destroyB = (int*)sqlite3_malloc(sizeof(int));126 TEST_ASSERT_NOT_NULL(destroyA);127 TEST_ASSERT_NOT_NULL(destroyB);128 *destroyA = 0;129 *destroyB = 0;130 131 int rc = sqlite3_create_window_function(132 db, "repfn", 1, SQLITE_UTF8, destroyA,133 winSumStep, winSumFinal, winSumValue, winSumInverse, xDestroyCounter134 );135 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);136 TEST_ASSERT_EQUAL_INT(0, *destroyA);137 138 /* Replace with a new registration under the same name */139 rc = sqlite3_create_window_function(140 db, "repfn", 1, SQLITE_UTF8, destroyB,141 winSumStep, winSumFinal, winSumValue, winSumInverse, xDestroyCounter142 );143 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);144 145 /* Old destructor should have been invoked exactly once */146 TEST_ASSERT_EQUAL_INT(1, *destroyA);147 TEST_ASSERT_EQUAL_INT(0, *destroyB);148 149 rc = sqlite3_close(db);150 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);151 152 /* New registration's destructor should be invoked at close */153 TEST_ASSERT_EQUAL_INT(1, *destroyB);154 155 sqlite3_free(destroyA);156 sqlite3_free(destroyB);157}158 159/* Test 3: Error path - missing xStep should fail and call xDestroy immediately */160static void test_sqlite3_create_window_function_error_calls_destroy(void){161 sqlite3 *db = open_memory_db();162 163 int *destroyCount = (int*)sqlite3_malloc(sizeof(int));164 TEST_ASSERT_NOT_NULL(destroyCount);165 *destroyCount = 0;166 167 /* Pass NULL xStep to force an error */168 int rc = sqlite3_create_window_function(169 db, "badwin", 1, SQLITE_UTF8, destroyCount,170 NULL, /* xStep = NULL -> invalid */171 winSumFinal,172 winSumValue,173 winSumInverse,174 xDestroyCounter175 );176 TEST_ASSERT(rc != SQLITE_OK);177 /* xDestroy should be called immediately on failure */178 TEST_ASSERT_EQUAL_INT(1, *destroyCount);179 180 rc = sqlite3_close(db);181 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);182 183 sqlite3_free(destroyCount);184}185 186/* Test 4: Varargs (-1) allows calling with extra args (ignored by our impl) */187static void test_sqlite3_create_window_function_varargs(void){188 sqlite3 *db = open_memory_db();189 create_sample_table(db);190 191 int rc = sqlite3_create_window_function(192 db, "winsum_var", -1, SQLITE_UTF8, NULL,193 winSumStep, winSumFinal, winSumValue, winSumInverse, NULL194 );195 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);196 197 /* Use with 2 args; our step ignores extras and sums first arg only */198 sqlite3_stmt *stmt = 0;199 const char *sql = "SELECT winsum_var(x, 99) FROM t;";200 rc = sqlite3_prepare_v2(db, sql, -1, &stmt, 0);201 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);202 203 rc = sqlite3_step(stmt);204 TEST_ASSERT_EQUAL_INT(SQLITE_ROW, rc);205 sqlite3_int64 v = sqlite3_column_int64(stmt, 0);206 TEST_ASSERT_EQUAL_INT64(6, v);207 rc = sqlite3_step(stmt);208 TEST_ASSERT_EQUAL_INT(SQLITE_DONE, rc);209 sqlite3_finalize(stmt);210 211 rc = sqlite3_close(db);212 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);213}214 215int main(void){216 UNITY_BEGIN();217 RUN_TEST(test_sqlite3_create_window_function_success_and_use);218 RUN_TEST(test_sqlite3_create_window_function_replace_triggers_destroy);219 RUN_TEST(test_sqlite3_create_window_function_error_calls_destroy);220 RUN_TEST(test_sqlite3_create_window_function_varargs);221 return UNITY_END();222}