AryaWu/sqlite
0
1#include "sqliteInt.h"2#include "unity.h"3#include <string.h>4#include <stdlib.h>5#include <stdio.h>6 7/* Global database handle for tests */8static sqlite3 *gDb = NULL;9 10/* Forward declaration of the wrapper provided in the module */11void test_jsonArrayStep(sqlite3_context *ctx, int argc, sqlite3_value **argv);12 13/* Aggregate step: delegate to the target function wrapper */14static void tja_step(sqlite3_context *ctx, int argc, sqlite3_value **argv){15 test_jsonArrayStep(ctx, argc, argv);16}17 18/* Finalizer that returns the raw accumulated buffer (without closing bracket) */19static void tja_final_raw(sqlite3_context *ctx){20 JsonString *pStr = (JsonString*)sqlite3_aggregate_context(ctx, (int)sizeof(JsonString));21 if( pStr==0 || pStr->zBuf==0 ){22 sqlite3_result_null(ctx);23 return;24 }25 if( pStr->eErr ){26 /* Preserve any prior error set by the step function */27 return;28 }29 /* Return exactly the accumulated bytes as text */30 sqlite3_result_text(ctx, pStr->zBuf, (int)pStr->nUsed, SQLITE_TRANSIENT);31}32 33/* Finalizer that appends a closing ']' and returns complete JSON text */34static void tja_final_json(sqlite3_context *ctx){35 JsonString *pStr = (JsonString*)sqlite3_aggregate_context(ctx, (int)sizeof(JsonString));36 if( pStr==0 || pStr->zBuf==0 ){37 sqlite3_result_null(ctx);38 return;39 }40 if( pStr->eErr ){41 /* Preserve any prior error set by the step function */42 return;43 }44 /* Build a copy with a closing bracket */45 size_t n = (size_t)pStr->nUsed;46 char *zOut = (char*)sqlite3_malloc64(n + 2);47 if( zOut==0 ){48 sqlite3_result_error_nomem(ctx);49 return;50 }51 memcpy(zOut, pStr->zBuf, n);52 zOut[n] = ']';53 zOut[n+1] = 0;54 sqlite3_result_text(ctx, zOut, -1, sqlite3_free);55}56 57/* Helpers to run scalar queries and fetch a single text result */58static void assert_query_text_eq(const char *sql, const char *expected){59 sqlite3_stmt *stmt = 0;60 int rc = sqlite3_prepare_v2(gDb, sql, -1, &stmt, 0);61 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);62 rc = sqlite3_step(stmt);63 TEST_ASSERT_EQUAL_INT(SQLITE_ROW, rc);64 int t = sqlite3_column_type(stmt, 0);65 TEST_ASSERT_NOT_EQUAL_MESSAGE(SQLITE_NULL, t, "Expected a text result, got NULL");66 const unsigned char *z = sqlite3_column_text(stmt, 0);67 TEST_ASSERT_NOT_NULL(z);68 TEST_ASSERT_EQUAL_STRING(expected, (const char*)z);69 rc = sqlite3_step(stmt);70 TEST_ASSERT_EQUAL_INT(SQLITE_DONE, rc);71 sqlite3_finalize(stmt);72}73 74static void assert_query_is_null(const char *sql){75 sqlite3_stmt *stmt = 0;76 int rc = sqlite3_prepare_v2(gDb, sql, -1, &stmt, 0);77 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);78 rc = sqlite3_step(stmt);79 TEST_ASSERT_EQUAL_INT(SQLITE_ROW, rc);80 TEST_ASSERT_EQUAL_INT(SQLITE_NULL, sqlite3_column_type(stmt, 0));81 rc = sqlite3_step(stmt);82 TEST_ASSERT_EQUAL_INT(SQLITE_DONE, rc);83 sqlite3_finalize(stmt);84}85 86static void assert_query_error_contains(const char *sql, const char *substr){87 sqlite3_stmt *stmt = 0;88 int rc = sqlite3_prepare_v2(gDb, sql, -1, &stmt, 0);89 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);90 rc = sqlite3_step(stmt);91 TEST_ASSERT_EQUAL_INT(SQLITE_ERROR, rc);92 const char *zErr = sqlite3_errmsg(gDb);93 TEST_ASSERT_NOT_NULL(zErr);94 TEST_ASSERT_NOT_NULL_MESSAGE(strstr(zErr, substr), "Error message does not contain expected substring");95 sqlite3_finalize(stmt);96}97 98/* Unity setUp/tearDown */99void setUp(void) {100 int rc = sqlite3_open(":memory:", &gDb);101 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);102 103 /* Register our test aggregates */104 rc = sqlite3_create_function(gDb, "tja_raw", 1, SQLITE_UTF8, 0, 0, tja_step, tja_final_raw);105 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);106 rc = sqlite3_create_function(gDb, "tja_json", 1, SQLITE_UTF8, 0, 0, tja_step, tja_final_json);107 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);108}109void tearDown(void) {110 if( gDb ){111 sqlite3_close(gDb);112 gDb = NULL;113 }114}115 116/* Tests */117 118void test_jsonArrayStep_single_integer(void){119 /* First step should insert '[' then the integer text */120 assert_query_text_eq("SELECT tja_raw(x) FROM (VALUES(5)) AS t(x);", "[5");121}122 123void test_jsonArrayStep_two_integers_comma_inserted(void){124 /* After first element, subsequent steps should insert commas */125 assert_query_text_eq("SELECT tja_raw(x) FROM (VALUES(1),(2)) AS t(x);", "[1,2");126}127 128void test_jsonArrayStep_mixed_types_complete_json(void){129 /* Verify string quoting, null, and float formatting with closing bracket */130 assert_query_text_eq(131 "SELECT tja_json(x) FROM (VALUES(1),('a'),(NULL),(1.25)) AS t(x);",132 "[1,\"a\",null,1.25]"133 );134}135 136void test_jsonArrayStep_json_subtype_passthrough(void){137 /* json() returns text with JSON_SUBTYPE; it should be appended without extra quotes */138 assert_query_text_eq(139 "SELECT tja_json(x) FROM (SELECT json('{\"x\":1}') AS x);",140 "[{\"x\":1}]"141 );142}143 144void test_jsonArrayStep_jsonb_blob_supported(void){145 /* jsonb() returns a JSONB blob; it should be translated to text and appended */146 assert_query_text_eq(147 "SELECT tja_json(x) FROM (SELECT jsonb('[1,2]') AS x);",148 "[[1,2]]"149 );150}151 152void test_jsonArrayStep_blob_error_raises(void){153 /* Non-JSON blob should cause an error: "JSON cannot hold BLOB values" */154 assert_query_error_contains(155 "SELECT tja_json(x) FROM (VALUES(zeroblob(2))) AS t(x);",156 "JSON cannot hold BLOB values"157 );158}159 160void test_jsonArrayStep_empty_input_yields_null(void){161 /* Aggregating over zero rows should return NULL */162 assert_query_is_null("SELECT tja_json(x) FROM (SELECT 1 WHERE 0) AS t(x);");163}164 165void test_jsonArrayStep_single_null(void){166 /* Single NULL should produce [null] when closed */167 assert_query_text_eq("SELECT tja_json(x) FROM (VALUES(NULL)) AS t(x);", "[null]");168}169 170/* main runner */171int main(void){172 UNITY_BEGIN();173 RUN_TEST(test_jsonArrayStep_single_integer);174 RUN_TEST(test_jsonArrayStep_two_integers_comma_inserted);175 RUN_TEST(test_jsonArrayStep_mixed_types_complete_json);176 RUN_TEST(test_jsonArrayStep_json_subtype_passthrough);177 RUN_TEST(test_jsonArrayStep_jsonb_blob_supported);178 RUN_TEST(test_jsonArrayStep_blob_error_raises);179 RUN_TEST(test_jsonArrayStep_empty_input_yields_null);180 RUN_TEST(test_jsonArrayStep_single_null);181 return UNITY_END();182}