AryaWu/sqlite
0
1#include "sqliteInt.h"2#include "unity.h"3#include <stdlib.h>4#include <string.h>5#include <stdio.h>6 7/* The step wrapper provided in the module under test */8extern void test_jsonObjectStep(sqlite3_context *ctx, int argc, sqlite3_value **argv);9 10/* Global DB handle for tests */11static sqlite3 *gDb = NULL;12 13/* Finalizer for the test aggregate that uses the aggregate context created by14** test_jsonObjectStep and returns a complete JSON object string.15*/16static void test_jsonObjectFinal(sqlite3_context *ctx){17 JsonString *pStr = (JsonString*)sqlite3_aggregate_context(ctx, 0);18 if( pStr==0 || pStr->zBuf==0 ){19 /* No rows or never initialized: return empty object */20 sqlite3_result_text(ctx, "{}", -1, SQLITE_TRANSIENT);21 return;22 }23 if( pStr->eErr ){24 /* An error should already have been reported by the step function. */25 return;26 }27 /* Build result by copying current buffer and appending '}' */28 int n = (int)pStr->nUsed;29 char *zOut = (char*)sqlite3_malloc64((sqlite3_uint64)n + 2);30 if( zOut==0 ){31 sqlite3_result_error_nomem(ctx);32 return;33 }34 memcpy(zOut, pStr->zBuf, (size_t)n);35 zOut[n] = '}';36 zOut[n+1] = 0;37 sqlite3_result_text(ctx, zOut, -1, sqlite3_free);38}39 40/* Helper: register the aggregate function using the target step */41static void register_test_agg(sqlite3 *db, const char *zName){42 int rc = sqlite3_create_function(db, zName, 2, SQLITE_UTF8, 0,43 0, /* xFunc */44 test_jsonObjectStep, /* xStep */45 test_jsonObjectFinal /* xFinal */);46 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);47}48 49/* Helper: evaluate a single-row/single-column SQL and return text result.50 The returned string is allocated by sqlite3 and must be freed using sqlite3_free(). */51static char* eval_sql_text(sqlite3 *db, const char *zSql){52 sqlite3_stmt *pStmt = 0;53 int rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);54 TEST_ASSERT_EQUAL_INT_MESSAGE(SQLITE_OK, rc, sqlite3_errmsg(db));55 rc = sqlite3_step(pStmt);56 TEST_ASSERT_EQUAL_INT_MESSAGE(SQLITE_ROW, rc, sqlite3_errmsg(db));57 const unsigned char *z = sqlite3_column_text(pStmt, 0);58 const char *zC = (const char*)z;59 char *zRet = zC ? sqlite3_mprintf("%s", zC) : sqlite3_mprintf("");60 sqlite3_finalize(pStmt);61 return zRet;62}63 64/* Helper: execute SQL without expecting a result row */65static void exec_sql(sqlite3 *db, const char *zSql){66 char *zErr = 0;67 int rc = sqlite3_exec(db, zSql, 0, 0, &zErr);68 if( rc!=SQLITE_OK ){69 const char *msg = zErr ? zErr : sqlite3_errmsg(db);70 TEST_FAIL_MESSAGE(msg);71 }72}73 74void setUp(void) {75 int rc = sqlite3_open(":memory:", &gDb);76 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);77 register_test_agg(gDb, "tjo"); /* test json object aggregate */78}79 80void tearDown(void) {81 if( gDb ){82 sqlite3_close(gDb);83 gDb = NULL;84 }85}86 87/* Basic: two simple name/value pairs */88void test_jsonObjectStep_basic_two_pairs(void){89 exec_sql(gDb, "CREATE TABLE t(name TEXT, val);"90 "INSERT INTO t(rowid,name,val) VALUES "91 "(1,'a',1),(2,'b',2)");92 char *z = eval_sql_text(gDb,93 "SELECT tjo(name, val ORDER BY rowid) FROM t");94 TEST_ASSERT_NOT_NULL(z);95 TEST_ASSERT_EQUAL_STRING("{\"a\":1,\"b\":2}", z);96 sqlite3_free(z);97}98 99/* Skip rows where name is NULL */100void test_jsonObjectStep_skip_null_name(void){101 exec_sql(gDb, "CREATE TABLE t2(name TEXT, val);"102 "INSERT INTO t2(rowid,name,val) VALUES "103 "(1,NULL,99),(2,'x',3)");104 char *z = eval_sql_text(gDb,105 "SELECT tjo(name, val ORDER BY rowid) FROM t2");106 TEST_ASSERT_NOT_NULL(z);107 TEST_ASSERT_EQUAL_STRING("{\"x\":3}", z);108 sqlite3_free(z);109}110 111/* Properly escape quotes and backslashes in the name */112void test_jsonObjectStep_escapes_name_quotes_and_backslash(void){113 exec_sql(gDb, "CREATE TABLE t3(name TEXT, val);"114 "INSERT INTO t3(rowid,name,val) VALUES "115 "(1,'a\"b\\\\c','hi')");116 char *z = eval_sql_text(gDb,117 "SELECT tjo(name, val ORDER BY rowid) FROM t3");118 TEST_ASSERT_NOT_NULL(z);119 TEST_ASSERT_EQUAL_STRING("{\"a\\\"b\\\\c\":\"hi\"}", z);120 sqlite3_free(z);121}122 123/* Value handling: NULL, TEXT (quoted), and JSON-typed (raw) */124void test_jsonObjectStep_value_types_text_null_and_json_subtype(void){125 exec_sql(gDb, "CREATE TABLE t4(name TEXT, val);"126 "INSERT INTO t4(rowid,name,val) VALUES "127 "(1,'n',NULL),"128 "(2,'s','str'),"129 "(3,'j',json('[1,2]'))");130 char *z = eval_sql_text(gDb,131 "SELECT tjo(name, val ORDER BY rowid) FROM t4");132 TEST_ASSERT_NOT_NULL(z);133 TEST_ASSERT_EQUAL_STRING("{\"n\":null,\"s\":\"str\",\"j\":[1,2]}", z);134 sqlite3_free(z);135}136 137/* Error on BLOB values */138void test_jsonObjectStep_error_on_blob_value(void){139 sqlite3_stmt *pStmt = 0;140 int rc = sqlite3_prepare_v2(gDb, "SELECT tjo('x', X'AB')", -1, &pStmt, 0);141 TEST_ASSERT_EQUAL_INT_MESSAGE(SQLITE_OK, rc, sqlite3_errmsg(gDb));142 rc = sqlite3_step(pStmt);143 TEST_ASSERT_EQUAL_INT(SQLITE_ERROR, rc);144 const char *zErr = sqlite3_errmsg(gDb);145 TEST_ASSERT_NOT_NULL(zErr);146 /* The error should mention that JSON cannot hold BLOB values */147 TEST_ASSERT_NOT_NULL(strstr(zErr, "JSON cannot hold BLOB values"));148 sqlite3_finalize(pStmt);149}150 151/* Empty input should produce an empty object {} */152void test_jsonObjectStep_empty_input_returns_empty_object(void){153 char *z = eval_sql_text(gDb, "SELECT tjo(name, val) FROM (SELECT 1 WHERE 0)");154 /* The subselect returns no rows; aggregate finalizer should return "{}" */155 TEST_ASSERT_NOT_NULL(z);156 TEST_ASSERT_EQUAL_STRING("{}", z);157 sqlite3_free(z);158}159 160int main(void){161 UNITY_BEGIN();162 RUN_TEST(test_jsonObjectStep_basic_two_pairs);163 RUN_TEST(test_jsonObjectStep_skip_null_name);164 RUN_TEST(test_jsonObjectStep_escapes_name_quotes_and_backslash);165 RUN_TEST(test_jsonObjectStep_value_types_text_null_and_json_subtype);166 RUN_TEST(test_jsonObjectStep_error_on_blob_value);167 RUN_TEST(test_jsonObjectStep_empty_input_returns_empty_object);168 return UNITY_END();169}