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/* Wrapper declared in module to access static function */8u32 test_jsonLookupStep(JsonParse *pParse, u32 iRoot, const char *zPath, u32 iLabel);9 10/* Local copies of JSONB type-nibble values (not redefining internal macros) */11static const u8 T_NULL = 0;12static const u8 T_TRUE = 1;13static const u8 T_FALSE = 2;14static const u8 T_INT = 3;15static const u8 T_INT5 = 4;16static const u8 T_FLOAT = 5;17static const u8 T_FLOAT5 = 6;18static const u8 T_TEXT = 7;19static const u8 T_TEXTJ = 8;20static const u8 T_TEXT5 = 9;21static const u8 T_TEXTRAW = 10;22static const u8 T_ARRAY = 11;23static const u8 T_OBJECT = 12;24 25/* JSON_LOOKUP error code numeric values (avoid using internal macros) */26static const u32 JL_ERROR = 0xffffffffu;27static const u32 JL_NOTFOUND = 0xfffffffeu;28static const u32 JL_PATHERROR = 0xfffffffdu;29 30/* Guessed edit-mode numeric values typically used by SQLite json code.31 These are not macro names from the core; we just set eEdit integers. */32static const int EEDIT_DEL = 1;33static const int EEDIT_REPL = 2;34static const int EEDIT_INS = 3;35static const int EEDIT_SET = 4;36 37/* Emit a JSONB header for the given element type and payload size.38 Returns the new index after writing the header at buf[idx]. */39static int emit_hdr(u8 *buf, int idx, u8 eType, u32 szPayload){40 if( szPayload <= 11 ){41 buf[idx++] = (u8)(eType | (szPayload<<4));42 }else if( szPayload <= 0xff ){43 buf[idx++] = (u8)(eType | 0xc0);44 buf[idx++] = (u8)(szPayload & 0xff);45 }else if( szPayload <= 0xffff ){46 buf[idx++] = (u8)(eType | 0xd0);47 buf[idx++] = (u8)((szPayload>>8) & 0xff);48 buf[idx++] = (u8)(szPayload & 0xff);49 }else{50 buf[idx++] = (u8)(eType | 0xe0);51 buf[idx++] = (u8)((szPayload>>24) & 0xff);52 buf[idx++] = (u8)((szPayload>>16) & 0xff);53 buf[idx++] = (u8)((szPayload>>8) & 0xff);54 buf[idx++] = (u8)(szPayload & 0xff);55 }56 return idx;57}58 59/* Build an OBJECT with a single key/value pair: { key: (valueType)valueText }60 Returns total length in *pLen. All sizes are small to keep single-byte headers. */61static void build_object_kv(const char *key, u8 valueType, const char *valText,62 u8 *out, u32 *pLen){63 int idx = 0;64 u32 keyLen = (u32)strlen(key);65 u32 valLen = (u32)strlen(valText);66 u32 labelElemLen = 1 + keyLen; /* 1-byte header + key payload */67 u32 valueElemLen = 1 + valLen; /* 1-byte header + value payload */68 u32 payloadLen = labelElemLen + valueElemLen;69 70 idx = emit_hdr(out, idx, T_OBJECT, payloadLen);71 /* label element: TEXT */72 idx = emit_hdr(out, idx, T_TEXT, keyLen);73 memcpy(&out[idx], key, keyLen); idx += keyLen;74 /* value element: valueType with valText payload */75 idx = emit_hdr(out, idx, valueType, valLen);76 memcpy(&out[idx], valText, valLen); idx += valLen;77 78 *pLen = (u32)idx;79}80 81/* Build an ARRAY of INT elements given N number-strings values[].82 Returns total length in *pLen. */83static void build_array_ints(const char **values, int N, u8 *out, u32 *pLen){84 int idx = 0;85 int i;86 /* Compute total payload length: sum over elements (1-byte header + payload) */87 u32 payloadLen = 0;88 for(i=0;i<N;i++){89 payloadLen += 1 + (u32)strlen(values[i]);90 }91 idx = emit_hdr(out, idx, T_ARRAY, payloadLen);92 for(i=0;i<N;i++){93 u32 vlen = (u32)strlen(values[i]);94 idx = emit_hdr(out, idx, T_INT, vlen);95 memcpy(&out[idx], values[i], vlen); idx += vlen;96 }97 *pLen = (u32)idx;98}99 100static sqlite3 *gDb = NULL;101 102void setUp(void) {103 int rc = sqlite3_open(":memory:", &gDb);104 TEST_ASSERT_EQUAL_INT(0, rc);105 TEST_ASSERT_NOT_NULL(gDb);106}107void tearDown(void) {108 if( gDb ){109 sqlite3_close(gDb);110 gDb = NULL;111 }112}113 114/* Test: object key lookup success: {"a":123}, path ".a" -> index 3, iLabel==1 */115void test_jsonLookupStep_object_key_found(void){116 u8 blob[32];117 u32 n;118 build_object_kv("a", T_INT, "123", blob, &n);119 /* Layout expectation:120 [0]: OBJECT hdr payload=6 -> 0x6c121 [1]: TEXT hdr payload=1 -> 0x17122 [2]: 'a'123 [3]: INT hdr payload=3 -> 0x33124 [4..6]: '1''2''3'125 */126 TEST_ASSERT_EQUAL_UINT8(0x6c, blob[0]);127 TEST_ASSERT_EQUAL_UINT8(0x17, blob[1]);128 TEST_ASSERT_EQUAL_UINT8('a', blob[2]);129 TEST_ASSERT_EQUAL_UINT8(0x33, blob[3]);130 131 JsonParse p;132 memset(&p, 0, sizeof(p));133 p.aBlob = blob;134 p.nBlob = n;135 p.nBlobAlloc = 0; /* read-only buffer; no edits expected */136 p.bReadOnly = 0; /* allow edits if attempted */137 p.db = gDb;138 139 u32 rc = test_jsonLookupStep(&p, 0, ".a", 0);140 TEST_ASSERT_EQUAL_UINT32(3u, rc);141 TEST_ASSERT_EQUAL_UINT32(1u, p.iLabel);142}143 144/* Test: object key lookup not found: {"a":123}, path ".b" -> NOTFOUND */145void test_jsonLookupStep_object_key_not_found(void){146 u8 blob[32];147 u32 n;148 build_object_kv("a", T_INT, "123", blob, &n);149 150 JsonParse p;151 memset(&p, 0, sizeof(p));152 p.aBlob = blob;153 p.nBlob = n;154 p.bReadOnly = 0;155 156 u32 rc = test_jsonLookupStep(&p, 0, ".b", 0);157 TEST_ASSERT_EQUAL_HEX32(JL_NOTFOUND, rc);158}159 160/* Test: path syntax error for "." alone */161void test_jsonLookupStep_path_error_dot(void){162 u8 blob[32];163 u32 n;164 build_object_kv("a", T_INT, "1", blob, &n);165 166 JsonParse p;167 memset(&p, 0, sizeof(p));168 p.aBlob = blob; p.nBlob = n;169 170 u32 rc = test_jsonLookupStep(&p, 0, ".", 0);171 TEST_ASSERT_EQUAL_HEX32(JL_PATHERROR, rc);172}173 174/* Test: quoted key with space: {"a b":7}, path ".\"a b\"" -> found */175void test_jsonLookupStep_object_quoted_key(void){176 u8 blob[32];177 u32 n;178 build_object_kv("a b", T_INT, "7", blob, &n);179 180 JsonParse p;181 memset(&p, 0, sizeof(p));182 p.aBlob = blob; p.nBlob = n; p.db = gDb;183 184 u32 rc = test_jsonLookupStep(&p, 0, ".\"a b\"", 0);185 /* Value element starts at index:186 OBJECT hdr (1) + label elem header (1) + label payload (3) = 5 */187 TEST_ASSERT_EQUAL_UINT32(5u, rc);188 TEST_ASSERT_EQUAL_UINT32(1u, p.iLabel);189}190 191/* Test: array index lookup: [10,20], path "[1]" -> index of second element */192void test_jsonLookupStep_array_index_found(void){193 const char *vals[] = { "10", "20" };194 u8 blob[32];195 u32 n;196 build_array_ints(vals, 2, blob, &n);197 /* Expect:198 [0]: ARRAY hdr payload=6 -> 0x6b199 First element at [1] (len 3), second at [4]200 */201 TEST_ASSERT_EQUAL_UINT8(0x6b, blob[0]);202 203 JsonParse p;204 memset(&p, 0, sizeof(p));205 p.aBlob = blob; p.nBlob = n;206 207 u32 rc = test_jsonLookupStep(&p, 0, "[1]", 0);208 TEST_ASSERT_EQUAL_UINT32(4u, rc);209}210 211/* Test: array last element via "[#-1]" */212void test_jsonLookupStep_array_last_element(void){213 const char *vals[] = { "10", "20" };214 u8 blob[32];215 u32 n;216 build_array_ints(vals, 2, blob, &n);217 218 JsonParse p;219 memset(&p, 0, sizeof(p));220 p.aBlob = blob; p.nBlob = n;221 222 u32 rc = test_jsonLookupStep(&p, 0, "[#-1]", 0);223 TEST_ASSERT_EQUAL_UINT32(4u, rc);224}225 226/* Test: array index out of range and path syntax error */227void test_jsonLookupStep_array_errors(void){228 const char *vals[] = { "10", "20" };229 u8 blob[32];230 u32 n;231 build_array_ints(vals, 2, blob, &n);232 233 JsonParse p;234 memset(&p, 0, sizeof(p));235 p.aBlob = blob; p.nBlob = n;236 237 u32 rc1 = test_jsonLookupStep(&p, 0, "[3]", 0);238 TEST_ASSERT_EQUAL_HEX32(JL_NOTFOUND, rc1);239 240 u32 rc2 = test_jsonLookupStep(&p, 0, "[#-3]", 0);241 TEST_ASSERT_EQUAL_HEX32(JL_NOTFOUND, rc2);242 243 u32 rc3 = test_jsonLookupStep(&p, 0, "[a]", 0);244 TEST_ASSERT_EQUAL_HEX32(JL_PATHERROR, rc3);245 246 u32 rc4 = test_jsonLookupStep(&p, 0, "[1", 0);247 TEST_ASSERT_EQUAL_HEX32(JL_PATHERROR, rc4);248}249 250/* Test: NOTFOUND when using object path on array */251void test_jsonLookupStep_wrong_type_notfound(void){252 const char *vals[] = { "10" };253 u8 blob[16]; u32 n;254 build_array_ints(vals, 1, blob, &n);255 256 JsonParse p;257 memset(&p, 0, sizeof(p));258 p.aBlob = blob; p.nBlob = n;259 260 u32 rc = test_jsonLookupStep(&p, 0, ".x", 0);261 TEST_ASSERT_EQUAL_HEX32(JL_NOTFOUND, rc);262}263 264/* Test: JEDIT_INS - insert new key/value into object.265 Start: {"a":123}. Insert .b := 5. Verify updated blob bytes. */266void test_jsonLookupStep_insert_new_key_value(void){267 u8 blob[32]; u32 n;268 build_object_kv("a", T_INT, "123", blob, &n);269 270 /* Value to insert: INT '5' -> header (size=1) + payload */271 u8 ins[4]; int idx=0;272 idx = emit_hdr(ins, idx, T_INT, 1);273 ins[idx++] = (u8)'5';274 u32 nIns = (u32)idx;275 276 JsonParse p;277 memset(&p, 0, sizeof(p));278 p.aBlob = blob; p.nBlob = n; p.nBlobAlloc = 0;279 p.db = gDb; p.bReadOnly = 0;280 p.eEdit = EEDIT_INS; /* trigger insertion when missing */281 p.aIns = ins; p.nIns = nIns;282 283 u32 rc = test_jsonLookupStep(&p, 0, ".b", 0);284 TEST_ASSERT_NOT_EQUAL_HEX32(JL_ERROR, rc);285 TEST_ASSERT_NOT_EQUAL_HEX32(JL_PATHERROR, rc);286 287 /* Expected final blob:288 OBJECT payload = 10:289 label "a", value 123, label "b", value 5290 Sequence of bytes:291 obj hdr (0x0c | (10<<4) = 0xac),292 label "a": 0x17 'a',293 value 123: 0x33 '1''2''3',294 label "b": Textraw header used by insertion path (size=1) -> 0x1a, 'b'295 value 5: 0x13 '5'296 */297 u8 expected[32]; int e=0;298 e = emit_hdr(expected, e, T_OBJECT, 10);299 e = emit_hdr(expected, e, T_TEXT, 1); expected[e++] = (u8)'a';300 e = emit_hdr(expected, e, T_INT, 3); expected[e++] = (u8)'1'; expected[e++] = (u8)'2'; expected[e++] = (u8)'3';301 /* inserted label uses TEXTRAW per code path (rawKey true) */302 e = emit_hdr(expected, e, T_TEXTRAW, 1); expected[e++] = (u8)'b';303 e = emit_hdr(expected, e, T_INT, 1); expected[e++] = (u8)'5';304 305 TEST_ASSERT_EQUAL_UINT32((u32)e, p.nBlob);306 TEST_ASSERT_EQUAL_MEMORY(expected, p.aBlob, e);307}308 309/* Test: JEDIT_DEL - delete existing key (and its label) from object.310 Start: {"a":123}. Delete ".a". Final: {} (OBJECT with payload 0). */311void test_jsonLookupStep_delete_key(void){312 u8 blob[32]; u32 n;313 build_object_kv("a", T_INT, "123", blob, &n);314 315 JsonParse p;316 memset(&p, 0, sizeof(p));317 p.aBlob = blob; p.nBlob = n; p.nBlobAlloc = 0;318 p.db = gDb; p.bReadOnly = 0;319 p.eEdit = EEDIT_DEL;320 321 u32 rc = test_jsonLookupStep(&p, 0, ".a", 0);322 TEST_ASSERT_NOT_EQUAL_HEX32(JL_ERROR, rc);323 TEST_ASSERT_NOT_EQUAL_HEX32(JL_PATHERROR, rc);324 /* Expect OBJECT hdr with payload 0 -> single byte 0x0c */325 TEST_ASSERT_EQUAL_UINT32(1u, p.nBlob);326 TEST_ASSERT_EQUAL_UINT8((u8)(T_OBJECT | (0<<4)), p.aBlob[0]);327}328 329/* Test: JEDIT_SET - replace existing value.330 Start: {"a":123}. Set ".a" := 7. Final: {"a":7}. */331void test_jsonLookupStep_set_existing_value(void){332 u8 blob[32]; u32 n;333 build_object_kv("a", T_INT, "123", blob, &n);334 335 u8 ins[4]; int idx=0;336 idx = emit_hdr(ins, idx, T_INT, 1);337 ins[idx++] = (u8)'7';338 u32 nIns = (u32)idx;339 340 JsonParse p;341 memset(&p, 0, sizeof(p));342 p.aBlob = blob; p.nBlob = n; p.nBlobAlloc = 0;343 p.db = gDb; p.bReadOnly = 0;344 p.eEdit = EEDIT_SET;345 p.aIns = ins; p.nIns = nIns;346 347 u32 rc = test_jsonLookupStep(&p, 0, ".a", 0);348 TEST_ASSERT_NOT_EQUAL_HEX32(JL_ERROR, rc);349 TEST_ASSERT_NOT_EQUAL_HEX32(JL_PATHERROR, rc);350 351 /* Expected final blob: OBJECT payload=4:352 hdr: 0x4c, label: 0x17 'a', value: 0x13 '7'353 */354 u8 expected[16]; int e=0;355 e = emit_hdr(expected, e, T_OBJECT, 4);356 e = emit_hdr(expected, e, T_TEXT, 1); expected[e++] = (u8)'a';357 e = emit_hdr(expected, e, T_INT, 1); expected[e++] = (u8)'7';358 359 TEST_ASSERT_EQUAL_UINT32((u32)e, p.nBlob);360 TEST_ASSERT_EQUAL_MEMORY(expected, p.aBlob, e);361}362 363int main(void){364 UNITY_BEGIN();365 RUN_TEST(test_jsonLookupStep_object_key_found);366 RUN_TEST(test_jsonLookupStep_object_key_not_found);367 RUN_TEST(test_jsonLookupStep_path_error_dot);368 RUN_TEST(test_jsonLookupStep_object_quoted_key);369 RUN_TEST(test_jsonLookupStep_array_index_found);370 RUN_TEST(test_jsonLookupStep_array_last_element);371 RUN_TEST(test_jsonLookupStep_array_errors);372 RUN_TEST(test_jsonLookupStep_wrong_type_notfound);373 RUN_TEST(test_jsonLookupStep_insert_new_key_value);374 RUN_TEST(test_jsonLookupStep_delete_key);375 RUN_TEST(test_jsonLookupStep_set_existing_value);376 return UNITY_END();377}