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/* The target under test is static, use the provided wrapper */8void test_jsonReturnString(JsonString *p, JsonParse *pParse, sqlite3_context *ctx);9 10/* Global database handle for tests */11static sqlite3 *gDb = NULL;12 13/* Helper: Prepare, step once, and finalize statement. Returns step rc. */14static int step_sql(const char *zSql, sqlite3_stmt **ppStmt){15 sqlite3_stmt *pStmt = NULL;16 int rc = sqlite3_prepare_v2(gDb, zSql, -1, &pStmt, 0);17 if( rc!=SQLITE_OK ){18 if( ppStmt ) *ppStmt = NULL;19 return rc;20 }21 rc = sqlite3_step(pStmt);22 if( ppStmt ){23 *ppStmt = pStmt;24 }else{25 sqlite3_finalize(pStmt);26 }27 return rc;28}29 30/* UDF: Return small text using static buffer path (bStatic=1), no caching. */31static void ut_jsonReturnString_text_small(sqlite3_context *ctx, int argc, sqlite3_value **argv){32 (void)argc;33 const unsigned char *zArg = sqlite3_value_text(argv[0]);34 int nArg = sqlite3_value_bytes(argv[0]);35 /* Construct JsonString using static buffer */36 JsonString js;37 memset(&js, 0, sizeof(js));38 js.pCtx = ctx;39 js.eErr = 0;40 js.bStatic = 1;41 js.zBuf = js.zSpace;42 js.nAlloc = (u32)sizeof(js.zSpace);43 if( (u32)nArg > js.nAlloc ){44 sqlite3_result_error(ctx, "argument too large for static buffer test", -1);45 return;46 }47 memcpy(js.zSpace, zArg, (size_t)nArg);48 js.nUsed = (u32)nArg;49 50 /* Per jsonReturnString assert: pass pParse==NULL => ctx==NULL */51 test_jsonReturnString(&js, NULL, NULL);52 /* Result already set by jsonReturnString */53}54 55/* UDF: Return large text using dynamic buffer path (bStatic=0), no caching. */56static void ut_jsonReturnString_text_large(sqlite3_context *ctx, int argc, sqlite3_value **argv){57 (void)argc;58 int n = 1000; /* ensure dynamic path */59 const char fill = 'x';60 char *zDyn = (char*)sqlite3RCStrNew((u64)n+1);61 if( zDyn==0 ){62 sqlite3_result_error_nomem(ctx);63 return;64 }65 memset(zDyn, fill, (size_t)n);66 zDyn[n] = 0;67 68 JsonString js;69 memset(&js, 0, sizeof(js));70 js.pCtx = ctx;71 js.eErr = 0;72 js.bStatic = 0;73 js.zBuf = zDyn;74 js.nAlloc = (u32)(n+1);75 js.nUsed = (u32)n;76 77 /* pParse==NULL => ctx==NULL (for assertion) */78 test_jsonReturnString(&js, NULL, NULL);79 /* Result already set */80}81 82/* UDF: JSON_BLOB flag set: convert valid text JSON input to JSONB blob. */83static void ut_jsonReturnString_blob(sqlite3_context *ctx, int argc, sqlite3_value **argv){84 (void)argc;85 const unsigned char *zArg = sqlite3_value_text(argv[0]);86 int nArg = sqlite3_value_bytes(argv[0]);87 88 /* Construct using dynamic buffer (not necessary, but fine) */89 char *zDyn = (char*)sqlite3RCStrNew((u64)nArg+1);90 if( zDyn==0 ){91 sqlite3_result_error_nomem(ctx);92 return;93 }94 memcpy(zDyn, zArg, (size_t)nArg);95 zDyn[nArg] = 0;96 97 JsonString js;98 memset(&js, 0, sizeof(js));99 js.pCtx = ctx;100 js.eErr = 0;101 js.bStatic = 0;102 js.zBuf = zDyn;103 js.nAlloc = (u32)(nArg+1);104 js.nUsed = (u32)nArg;105 106 /* The JSON_BLOB flag is taken from sqlite3_user_data(js.pCtx).107 ** Here we simply call; the flag is supplied when registering the SQL function.108 ** pParse==NULL => ctx==NULL per assertion. */109 test_jsonReturnString(&js, NULL, NULL);110}111 112/* UDF: Exercise caching branch: pParse!=NULL and ctx!=NULL and bStatic==0. 113** Returns 1 if cache insert and fields look correct; else returns 0. */114static void ut_jsonReturnString_cachecheck(sqlite3_context *ctx, int argc, sqlite3_value **argv){115 (void)argc;116 /* Create a long string to force dynamic buffer */117 int n = 400;118 char *zDyn = (char*)sqlite3RCStrNew((u64)n+1);119 if( zDyn==0 ){120 sqlite3_result_error_nomem(ctx);121 return;122 }123 for(int i=0;i<n;i++) zDyn[i] = 'A' + (i%26);124 zDyn[n] = 0;125 126 JsonString js;127 memset(&js, 0, sizeof(js));128 js.pCtx = ctx;129 js.eErr = 0;130 js.bStatic = 0;131 js.zBuf = zDyn;132 js.nAlloc = (u32)(n+1);133 js.nUsed = (u32)n;134 135 JsonParse px;136 memset(&px, 0, sizeof(px));137 px.nBlobAlloc = 1; /* non-zero to enable caching path */138 139 /* Now call with pParse!=NULL and ctx!=NULL for caching */140 test_jsonReturnString(&js, &px, ctx);141 142 /* Verify px got updated and cache was inserted, and js reset */143 int ok = 1;144 if( px.bJsonIsRCStr==0 ) ok = 0;145 if( px.zJson==0 ) ok = 0;146 if( (int)px.nJson != n ) ok = 0;147 148 JsonCache *pCache = (JsonCache*)sqlite3_get_auxdata(ctx, JSON_CACHE_ID);149 if( pCache==0 ) ok = 0;150 else{151 if( pCache->nUsed==0 ) ok = 0;152 else if( pCache->a[pCache->nUsed-1] != &px ) ok = 0;153 }154 155 /* jsonReturnString should have reset the JsonString */156 if( js.bStatic!=1 || js.nUsed!=0 || js.zBuf!=js.zSpace ) ok = 0;157 158 sqlite3_result_int(ctx, ok);159}160 161/* UDF: Force malformed error path. */162static void ut_jsonReturnString_errMalformed(sqlite3_context *ctx, int argc, sqlite3_value **argv){163 (void)argc; (void)argv;164 JsonString js;165 memset(&js, 0, sizeof(js));166 js.pCtx = ctx;167 js.eErr = JSTRING_MALFORMED;168 js.bStatic = 1;169 js.zBuf = js.zSpace;170 js.nAlloc = (u32)sizeof(js.zSpace);171 js.nUsed = 0;172 /* pParse==NULL, ctx==NULL for assertion */173 test_jsonReturnString(&js, NULL, NULL);174}175 176/* UDF: Force OOM error path. */177static void ut_jsonReturnString_errOOM(sqlite3_context *ctx, int argc, sqlite3_value **argv){178 (void)argc; (void)argv;179 JsonString js;180 memset(&js, 0, sizeof(js));181 js.pCtx = ctx;182 js.eErr = JSTRING_OOM;183 js.bStatic = 1;184 js.zBuf = js.zSpace;185 js.nAlloc = (u32)sizeof(js.zSpace);186 js.nUsed = 0;187 /* pParse==NULL, ctx==NULL for assertion */188 test_jsonReturnString(&js, NULL, NULL);189}190 191void setUp(void) {192 int rc = sqlite3_open(":memory:", &gDb);193 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);194 195 /* Register UDFs. Note: pApp sets sqlite3_user_data, used for flags in jsonReturnString. */196 rc = sqlite3_create_function(gDb, "ut_jsonReturnString_text_small", 1, SQLITE_UTF8, NULL,197 ut_jsonReturnString_text_small, NULL, NULL);198 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);199 200 rc = sqlite3_create_function(gDb, "ut_jsonReturnString_text_large", 0, SQLITE_UTF8, NULL,201 ut_jsonReturnString_text_large, NULL, NULL);202 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);203 204 /* Register blob version with JSON_BLOB flag in user_data */205 rc = sqlite3_create_function(gDb, "ut_jsonReturnString_blob", 1, SQLITE_UTF8,206 (void*)(uintptr_t)JSON_BLOB,207 ut_jsonReturnString_blob, NULL, NULL);208 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);209 210 rc = sqlite3_create_function(gDb, "ut_jsonReturnString_cachecheck", 0, SQLITE_UTF8, NULL,211 ut_jsonReturnString_cachecheck, NULL, NULL);212 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);213 214 rc = sqlite3_create_function(gDb, "ut_jsonReturnString_errMalformed", 0, SQLITE_UTF8, NULL,215 ut_jsonReturnString_errMalformed, NULL, NULL);216 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);217 218 rc = sqlite3_create_function(gDb, "ut_jsonReturnString_errOOM", 0, SQLITE_UTF8, NULL,219 ut_jsonReturnString_errOOM, NULL, NULL);220 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);221}222 223void tearDown(void) {224 if( gDb ){225 sqlite3_close(gDb);226 gDb = NULL;227 }228}229 230/* Test: static buffer path returns correct text. */231static void test_jsonReturnString_text_small_static(void){232 sqlite3_stmt *pStmt = NULL;233 int rc = sqlite3_prepare_v2(gDb, "SELECT ut_jsonReturnString_text_small(?)", -1, &pStmt, 0);234 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);235 rc = sqlite3_bind_text(pStmt, 1, "hello", -1, SQLITE_STATIC);236 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);237 rc = sqlite3_step(pStmt);238 TEST_ASSERT_EQUAL_INT(SQLITE_ROW, rc);239 TEST_ASSERT_EQUAL_INT(SQLITE_TEXT, sqlite3_column_type(pStmt, 0));240 const unsigned char *z = sqlite3_column_text(pStmt, 0);241 TEST_ASSERT_NOT_NULL(z);242 TEST_ASSERT_EQUAL_STRING("hello", (const char*)z);243 sqlite3_finalize(pStmt);244}245 246/* Test: dynamic buffer path returns correct large text length. */247static void test_jsonReturnString_text_large_dynamic(void){248 sqlite3_stmt *pStmt = NULL;249 int rc = sqlite3_prepare_v2(gDb, "SELECT ut_jsonReturnString_text_large()", -1, &pStmt, 0);250 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);251 rc = sqlite3_step(pStmt);252 TEST_ASSERT_EQUAL_INT(SQLITE_ROW, rc);253 TEST_ASSERT_EQUAL_INT(SQLITE_TEXT, sqlite3_column_type(pStmt, 0));254 const unsigned char *z = sqlite3_column_text(pStmt, 0);255 int n = sqlite3_column_bytes(pStmt, 0);256 TEST_ASSERT_NOT_NULL(z);257 TEST_ASSERT_EQUAL_INT(1000, n);258 /* Optional: ensure it's the expected pattern */259 TEST_ASSERT_EQUAL_INT('x', z[0]);260 TEST_ASSERT_EQUAL_INT('x', z[999]);261 sqlite3_finalize(pStmt);262}263 264/* Test: JSON_BLOB flag produces a BLOB result. */265static void test_jsonReturnString_blob_flag(void){266 sqlite3_stmt *pStmt = NULL;267 int rc = sqlite3_prepare_v2(gDb, "SELECT ut_jsonReturnString_blob('[1,2,3]')", -1, &pStmt, 0);268 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);269 rc = sqlite3_step(pStmt);270 TEST_ASSERT_EQUAL_INT(SQLITE_ROW, rc);271 TEST_ASSERT_EQUAL_INT(SQLITE_BLOB, sqlite3_column_type(pStmt, 0));272 const void *pBlob = sqlite3_column_blob(pStmt, 0);273 int nBlob = sqlite3_column_bytes(pStmt, 0);274 TEST_ASSERT_NOT_NULL(pBlob);275 TEST_ASSERT_MESSAGE(nBlob>0, "JSONB blob should be non-empty");276 sqlite3_finalize(pStmt);277}278 279/* Test: caching branch sets auxdata and updates JsonParse; function returns 1. */280static void test_jsonReturnString_cache_insert(void){281 sqlite3_stmt *pStmt = NULL;282 int rc = sqlite3_prepare_v2(gDb, "SELECT ut_jsonReturnString_cachecheck()", -1, &pStmt, 0);283 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);284 rc = sqlite3_step(pStmt);285 TEST_ASSERT_EQUAL_INT(SQLITE_ROW, rc);286 TEST_ASSERT_EQUAL_INT(SQLITE_INTEGER, sqlite3_column_type(pStmt, 0));287 int ok = sqlite3_column_int(pStmt, 0);288 TEST_ASSERT_EQUAL_INT(1, ok);289 sqlite3_finalize(pStmt);290}291 292/* Test: malformed error path returns SQLITE_ERROR with expected message. */293static void test_jsonReturnString_error_malformed(void){294 sqlite3_stmt *pStmt = NULL;295 int rc = sqlite3_prepare_v2(gDb, "SELECT ut_jsonReturnString_errMalformed()", -1, &pStmt, 0);296 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);297 rc = sqlite3_step(pStmt);298 TEST_ASSERT_EQUAL_INT(SQLITE_ERROR, rc);299 const char *zErr = sqlite3_errmsg(gDb);300 TEST_ASSERT_NOT_NULL(zErr);301 TEST_ASSERT_MESSAGE(strstr(zErr, "malformed JSON")!=NULL, "Expected 'malformed JSON' in error message");302 sqlite3_finalize(pStmt);303}304 305/* Test: OOM error path sets NOMEM error code. */306static void test_jsonReturnString_error_oom(void){307 sqlite3_stmt *pStmt = NULL;308 int rc = sqlite3_prepare_v2(gDb, "SELECT ut_jsonReturnString_errOOM()", -1, &pStmt, 0);309 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);310 rc = sqlite3_step(pStmt);311 /* step generally returns SQLITE_ERROR, but db errcode should be SQLITE_NOMEM */312 TEST_ASSERT_EQUAL_INT(SQLITE_ERROR, rc);313 int ec = sqlite3_errcode(gDb);314 TEST_ASSERT_EQUAL_INT(SQLITE_NOMEM, ec);315 sqlite3_finalize(pStmt);316}317 318int main(void){319 UNITY_BEGIN();320 RUN_TEST(test_jsonReturnString_text_small_static);321 RUN_TEST(test_jsonReturnString_text_large_dynamic);322 RUN_TEST(test_jsonReturnString_blob_flag);323 RUN_TEST(test_jsonReturnString_cache_insert);324 RUN_TEST(test_jsonReturnString_error_malformed);325 RUN_TEST(test_jsonReturnString_error_oom);326 return UNITY_END();327}