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/* External wrapper for the static function under test */8extern u32 test_jsonCreateEditSubstructure(JsonParse *pParse, JsonParse *pIns, const char *zTail);9 10/* Some edit-mode constants should be available via sqliteInt.h.11 We rely on JEDIT_INS to allow insertion of substructure. */12 13/* Global DB handle for allocator context */14static sqlite3 *gDb = NULL;15 16/* Minimal JSONB utilities for the tests, based on the documented encoding:17 - Type is in low 4 bits of first byte.18 - Upper 4 bits select payload size or length-encoding scheme. */19 20/* Return header size and set *pPayloadSize to payload size, for element starting at a[i] */21static u32 jsonb_hdrsize_and_psz(const unsigned char *a, u32 i, u32 *pPayloadSize){22 unsigned char x = a[i];23 unsigned char hi = (unsigned char)(x >> 4);24 if( hi <= 11 ){25 *pPayloadSize = hi;26 return 1;27 }else if( hi == 12 ){28 *pPayloadSize = a[i+1];29 return 2;30 }else if( hi == 13 ){31 *pPayloadSize = ((u32)a[i+1] << 8) | (u32)a[i+2];32 return 3;33 }else if( hi == 14 ){34 *pPayloadSize = ((u32)a[i+1] << 24) | ((u32)a[i+2] << 16)35 | ((u32)a[i+3] << 8) | (u32)a[i+4];36 return 5;37 }else{38 /* 8-byte big-endian length */39 u64 v = 0;40 for(int k=1; k<=8; k++){41 v = (v<<8) | a[i+k];42 }43 if( v > 0xffffffffu ){ /* clamp to 32-bit for test use */44 *pPayloadSize = 0xffffffffu;45 }else{46 *pPayloadSize = (u32)v;47 }48 return 9;49 }50}51 52/* Build a minimal JSONB TEXT element for a small ASCII string s (len <= 11).53 Writes header+payload to buf. Returns total size. */54static u32 build_jsonb_text_small(const char *s, unsigned char *buf, u32 cap){55 u32 n = (u32)strlen(s);56 TEST_ASSERT_MESSAGE(n <= 11, "Test helper only supports text length <= 11");57 TEST_ASSERT_MESSAGE(cap >= 1+n, "Insufficient buffer capacity for JSONB text");58 buf[0] = (unsigned char)((n << 4) | (JSONB_TEXT & 0x0f));59 memcpy(&buf[1], s, n);60 return 1 + n;61}62 63/* Helpers to parse first label/value pair of an object:64 On entry, a points to the object header at index 0.65 Outputs:66 - label text pointer/length67 - value index (index into a[] at start of value element header)68 - returns 0 on success, non-zero on error.69*/70static int parse_single_kv_from_object(const unsigned char *a, u32 nBlob,71 const char **pzLabel, u32 *pnLabel,72 u32 *pIdxValue){73 u32 szPayload = 0;74 u32 hsz = jsonb_hdrsize_and_psz(a, 0, &szPayload);75 if( (a[0] & 0x0f) != JSONB_OBJECT ) return 1;76 u32 j = hsz;77 u32 iEnd = j + szPayload;78 if( j >= nBlob || j >= iEnd ) return 2;79 /* Label element */80 unsigned char ltype = a[j] & 0x0f;81 if( ltype < JSONB_TEXT || ltype > JSONB_TEXTRAW ) return 3;82 u32 lpsz = 0;83 u32 lhsz = jsonb_hdrsize_and_psz(a, j, &lpsz);84 u32 k = j + lhsz;85 if( k + lpsz > nBlob || k + lpsz > iEnd ) return 4;86 *pzLabel = (const char*)&a[k];87 *pnLabel = lpsz;88 /* Value element immediately after label payload */89 u32 v = k + lpsz;90 if( v >= nBlob || v >= iEnd ) return 5;91 *pIdxValue = v;92 return 0;93}94 95/* Parse first element of array at a[0], output index of element header into *pIdxEl0 */96static int parse_first_elem_from_array(const unsigned char *a, u32 nBlob, u32 *pIdxEl0){97 u32 szPayload = 0;98 u32 hsz = jsonb_hdrsize_and_psz(a, 0, &szPayload);99 if( (a[0] & 0x0f) != JSONB_ARRAY ) return 1;100 u32 j = hsz;101 u32 iEnd = j + szPayload;102 if( j >= nBlob ) return 2;103 if( j == iEnd ){104 /* empty array */105 return 3;106 }107 *pIdxEl0 = j;108 return 0;109}110 111void setUp(void) {112 if( gDb==NULL ){113 int rc = sqlite3_open(":memory:", &gDb);114 TEST_ASSERT_EQUAL_INT_MESSAGE(SQLITE_OK, rc, "sqlite3_open failed");115 }116}117 118void tearDown(void) {119 if( gDb ){120 sqlite3_close(gDb);121 gDb = NULL;122 }123}124 125/* Test: empty zTail returns the provided insertion blob directly */126void test_jsonCreateEditSubstructure_empty_tail_returns_given_blob(void) {127 JsonParse orig, ins;128 memset(&orig, 0, sizeof(orig));129 memset(&ins, 0, sizeof(ins));130 orig.db = gDb;131 132 unsigned char val[8];133 u32 vlen = build_jsonb_text_small("Z", val, sizeof(val));134 orig.aIns = val;135 orig.nIns = vlen;136 orig.eEdit = JEDIT_INS; /* value doesn't matter for empty tail */137 138 u32 rc = test_jsonCreateEditSubstructure(&orig, &ins, "");139 TEST_ASSERT_EQUAL_UINT32(0u, rc);140 TEST_ASSERT_EQUAL_PTR(orig.aIns, ins.aBlob);141 TEST_ASSERT_EQUAL_UINT32(orig.nIns, ins.nBlob);142 TEST_ASSERT_EQUAL_PTR(gDb, ins.db);143 144 /* Do not call jsonParseReset(&ins) here since ins.aBlob aliases orig.aIns */145}146 147/* Test: simple object path ".k" builds {"k": <val>} */148void test_jsonCreateEditSubstructure_creates_object_with_key_and_value(void) {149 JsonParse orig, sub;150 memset(&orig, 0, sizeof(orig));151 memset(&sub, 0, sizeof(sub));152 orig.db = gDb;153 154 unsigned char val[8];155 u32 vlen = build_jsonb_text_small("V", val, sizeof(val));156 orig.aIns = val;157 orig.nIns = vlen;158 orig.eEdit = JEDIT_INS;159 160 u32 rc = test_jsonCreateEditSubstructure(&orig, &sub, ".k");161 TEST_ASSERT_MESSAGE(rc < JSON_LOOKUP_PATHERROR, "Expected success (no JSON_LOOKUP_* error)");162 163 /* Validate structure: object with one label "k" and value "V" */164 TEST_ASSERT_EQUAL_UINT8(JSONB_OBJECT, sub.aBlob[0] & 0x0f);165 166 const char *zLabel = NULL;167 u32 nLabel = 0;168 u32 idxValue = 0;169 int prc = parse_single_kv_from_object(sub.aBlob, sub.nBlob, &zLabel, &nLabel, &idxValue);170 TEST_ASSERT_EQUAL_INT_MESSAGE(0, prc, "Failed to parse object key/value");171 172 TEST_ASSERT_EQUAL_UINT32(1u, nLabel);173 TEST_ASSERT_EQUAL_CHAR('k', zLabel[0]);174 175 /* Check value is TEXT "V" */176 TEST_ASSERT_EQUAL_UINT8(JSONB_TEXT, sub.aBlob[idxValue] & 0x0f);177 u32 psz = 0;178 u32 hsz = jsonb_hdrsize_and_psz(sub.aBlob, idxValue, &psz);179 TEST_ASSERT_EQUAL_UINT32(1u, psz);180 TEST_ASSERT_EQUAL_CHAR('V', (char)sub.aBlob[idxValue + hsz]);181 182 jsonParseReset(&sub);183}184 185/* Test: simple array path "[0]" builds [ <val> ] */186void test_jsonCreateEditSubstructure_creates_array_and_inserts_element(void) {187 JsonParse orig, sub;188 memset(&orig, 0, sizeof(orig));189 memset(&sub, 0, sizeof(sub));190 orig.db = gDb;191 192 unsigned char val[8];193 u32 vlen = build_jsonb_text_small("V", val, sizeof(val));194 orig.aIns = val;195 orig.nIns = vlen;196 orig.eEdit = JEDIT_INS;197 198 u32 rc = test_jsonCreateEditSubstructure(&orig, &sub, "[0]");199 TEST_ASSERT_MESSAGE(rc < JSON_LOOKUP_PATHERROR, "Expected success (no JSON_LOOKUP_* error)");200 201 TEST_ASSERT_EQUAL_UINT8(JSONB_ARRAY, sub.aBlob[0] & 0x0f);202 203 u32 idxEl0 = 0;204 int prc = parse_first_elem_from_array(sub.aBlob, sub.nBlob, &idxEl0);205 TEST_ASSERT_EQUAL_INT_MESSAGE(0, prc, "Failed to parse first array element");206 207 TEST_ASSERT_EQUAL_UINT8(JSONB_TEXT, sub.aBlob[idxEl0] & 0x0f);208 u32 psz = 0;209 u32 hsz = jsonb_hdrsize_and_psz(sub.aBlob, idxEl0, &psz);210 TEST_ASSERT_EQUAL_UINT32(1u, psz);211 TEST_ASSERT_EQUAL_CHAR('V', (char)sub.aBlob[idxEl0 + hsz]);212 213 jsonParseReset(&sub);214}215 216/* Test: deep mixed path ".a.b[0]" builds {"a":{"b":[<val>]}} */217void test_jsonCreateEditSubstructure_creates_nested_structure_mixed(void) {218 JsonParse orig, sub;219 memset(&orig, 0, sizeof(orig));220 memset(&sub, 0, sizeof(sub));221 orig.db = gDb;222 223 unsigned char val[8];224 u32 vlen = build_jsonb_text_small("V", val, sizeof(val));225 orig.aIns = val;226 orig.nIns = vlen;227 orig.eEdit = JEDIT_INS;228 229 u32 rc = test_jsonCreateEditSubstructure(&orig, &sub, ".a.b[0]");230 TEST_ASSERT_MESSAGE(rc < JSON_LOOKUP_PATHERROR, "Expected success (no JSON_LOOKUP_* error)");231 232 /* Root: object -> key "a" -> object */233 TEST_ASSERT_EQUAL_UINT8(JSONB_OBJECT, sub.aBlob[0] & 0x0f);234 const char *zA = NULL; u32 nA = 0; u32 idxAVal = 0;235 int prc = parse_single_kv_from_object(sub.aBlob, sub.nBlob, &zA, &nA, &idxAVal);236 TEST_ASSERT_EQUAL_INT(0, prc);237 TEST_ASSERT_EQUAL_UINT32(1u, nA);238 TEST_ASSERT_EQUAL_CHAR('a', zA[0]);239 240 TEST_ASSERT_EQUAL_UINT8(JSONB_OBJECT, sub.aBlob[idxAVal] & 0x0f);241 /* Parse inner object at idxAVal: key "b" -> array */242 u32 inner_psz = 0, inner_hsz = jsonb_hdrsize_and_psz(sub.aBlob, idxAVal, &inner_psz);243 const unsigned char *inner = &sub.aBlob[idxAVal];244 const char *zB = NULL; u32 nB = 0; u32 idxBVal = 0;245 int prc2 = parse_single_kv_from_object(inner, sub.nBlob - idxAVal, &zB, &nB, &idxBVal);246 TEST_ASSERT_EQUAL_INT(0, prc2);247 TEST_ASSERT_EQUAL_UINT32(1u, nB);248 TEST_ASSERT_EQUAL_CHAR('b', zB[0]);249 250 /* idxBVal is relative to inner; convert to absolute index */251 u32 idxBValAbs = idxAVal + idxBVal;252 TEST_ASSERT_EQUAL_UINT8(JSONB_ARRAY, sub.aBlob[idxBValAbs] & 0x0f);253 254 /* Parse first element of array at idxBValAbs */255 u32 arr_psz = 0, arr_hsz = jsonb_hdrsize_and_psz(sub.aBlob, idxBValAbs, &arr_psz);256 const unsigned char *arr = &sub.aBlob[idxBValAbs];257 u32 idxEl0Rel = 0;258 int prc3 = parse_first_elem_from_array(arr, sub.nBlob - idxBValAbs, &idxEl0Rel);259 TEST_ASSERT_EQUAL_INT(0, prc3);260 u32 idxEl0Abs = idxBValAbs + idxEl0Rel;261 262 TEST_ASSERT_EQUAL_UINT8(JSONB_TEXT, sub.aBlob[idxEl0Abs] & 0x0f);263 u32 psz = 0, hsz = jsonb_hdrsize_and_psz(sub.aBlob, idxEl0Abs, &psz);264 TEST_ASSERT_EQUAL_UINT32(1u, psz);265 TEST_ASSERT_EQUAL_CHAR('V', (char)sub.aBlob[idxEl0Abs + hsz]);266 267 jsonParseReset(&sub);268}269 270/* Test: invalid tail returns PATHERROR */271void test_jsonCreateEditSubstructure_patherror_for_invalid_tail(void) {272 JsonParse orig, sub;273 memset(&orig, 0, sizeof(orig));274 memset(&sub, 0, sizeof(sub));275 orig.db = gDb;276 277 unsigned char val[8];278 u32 vlen = build_jsonb_text_small("V", val, sizeof(val));279 orig.aIns = val;280 orig.nIns = vlen;281 orig.eEdit = JEDIT_INS;282 283 u32 rc1 = test_jsonCreateEditSubstructure(&orig, &sub, "invalid");284 TEST_ASSERT_EQUAL_UINT32(JSON_LOOKUP_PATHERROR, rc1);285 /* No allocation expected to validate, but reset if anything happened */286 if( sub.aBlob && sub.aBlob != orig.aIns ){287 jsonParseReset(&sub);288 }289 290 memset(&sub, 0, sizeof(sub));291 u32 rc2 = test_jsonCreateEditSubstructure(&orig, &sub, "[x]");292 TEST_ASSERT_EQUAL_UINT32(JSON_LOOKUP_PATHERROR, rc2);293 if( sub.aBlob && sub.aBlob != orig.aIns ){294 jsonParseReset(&sub);295 }296}297 298/* Test: array index out of range returns NOTFOUND (no insertion at [1] in empty array) */299void test_jsonCreateEditSubstructure_notfound_for_array_index_out_of_range(void) {300 JsonParse orig, sub;301 memset(&orig, 0, sizeof(orig));302 memset(&sub, 0, sizeof(sub));303 orig.db = gDb;304 305 unsigned char val[8];306 u32 vlen = build_jsonb_text_small("V", val, sizeof(val));307 orig.aIns = val;308 orig.nIns = vlen;309 orig.eEdit = JEDIT_INS;310 311 u32 rc = test_jsonCreateEditSubstructure(&orig, &sub, "[1]");312 TEST_ASSERT_EQUAL_UINT32(JSON_LOOKUP_NOTFOUND, rc);313 if( sub.aBlob && sub.aBlob != orig.aIns ){314 jsonParseReset(&sub);315 }316}317 318int main(void) {319 UNITY_BEGIN();320 RUN_TEST(test_jsonCreateEditSubstructure_empty_tail_returns_given_blob);321 RUN_TEST(test_jsonCreateEditSubstructure_creates_object_with_key_and_value);322 RUN_TEST(test_jsonCreateEditSubstructure_creates_array_and_inserts_element);323 RUN_TEST(test_jsonCreateEditSubstructure_creates_nested_structure_mixed);324 RUN_TEST(test_jsonCreateEditSubstructure_patherror_for_invalid_tail);325 RUN_TEST(test_jsonCreateEditSubstructure_notfound_for_array_index_out_of_range);326 return UNITY_END();327}