Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Overview
Comment: | Add the json_pretty() SQL function. |
---|---|
Downloads: | Tarball | ZIP archive |
Timelines: | family | ancestors | descendants | both | trunk |
Files: | files | file ages | folders |
SHA3-256: |
ceb51c1cc36dfb78db8f2be042a0d74a |
User & Date: | drh 2024-03-06 20:49:05.359 |
Context
2024-03-07
| ||
12:34 | Do not allow the query planner to be tricked into thinking that an index on a constant expression might be useful for something. Problem reported on forum post ecdfc02339. This is a follow-up to the fixes at [44200596aa943963] and [2d2b91cc0f6fed8c]. (check-in: 720ce06d93 user: drh tags: trunk) | |
2024-03-06
| ||
20:49 | Add the json_pretty() SQL function. (check-in: ceb51c1cc3 user: drh tags: trunk) | |
20:38 | Add test cases for json_pretty(). (Closed-Leaf check-in: 6448b90708 user: drh tags: json-pretty) | |
20:24 | Work around obscure floating point issue seen with older versions of MSVC. (check-in: 42d39f9140 user: mistachkin tags: trunk) | |
Changes
Changes to ext/wasm/fiddle/fiddle.js.
︙ | ︙ | |||
757 758 759 760 761 762 763 | " SELECT max(iter), cx, cy FROM m GROUP BY cx, cy\n", " ),\n", " a(t) AS (\n", " SELECT group_concat( substr(' .+*#', 1+min(iter/7,4), 1), '') \n", " FROM m2 GROUP BY cy\n", " )\n", "SELECT group_concat(rtrim(t),x'0a') as Mandelbrot FROM a;\n", | | > > > > > > | 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 | " SELECT max(iter), cx, cy FROM m GROUP BY cx, cy\n", " ),\n", " a(t) AS (\n", " SELECT group_concat( substr(' .+*#', 1+min(iter/7,4), 1), '') \n", " FROM m2 GROUP BY cy\n", " )\n", "SELECT group_concat(rtrim(t),x'0a') as Mandelbrot FROM a;\n", ]}, {name: "JSON pretty-print", sql: "select json_pretty(json_object('ex',json('[52,3.14159]')))" }, {name: "JSON pretty-print (with tabs)", sql: "select json_pretty(json_object('ex',json('[52,3.14159]')),char(0x09))" } ]; const newOpt = function(lbl,val){ const o = document.createElement('option'); if(Array.isArray(val)) val = val.join(''); o.value = val; if(!val) o.setAttribute('disabled',true); o.appendChild(document.createTextNode(lbl)); |
︙ | ︙ |
Changes to src/json.c.
︙ | ︙ | |||
559 560 561 562 563 564 565 | jsonStringExpandAndAppend(p,zIn,N); }else{ memcpy(p->zBuf+p->nUsed, zIn, N); p->nUsed += N; } } | < | 559 560 561 562 563 564 565 566 567 568 569 570 571 572 | jsonStringExpandAndAppend(p,zIn,N); }else{ memcpy(p->zBuf+p->nUsed, zIn, N); p->nUsed += N; } } /* Append formatted text (not to exceed N bytes) to the JsonString. */ static void jsonPrintf(int N, JsonString *p, const char *zFormat, ...){ va_list ap; if( (p->nUsed + N >= p->nAlloc) && jsonStringGrow(p, N) ) return; va_start(ap, zFormat); sqlite3_vsnprintf(N, p->zBuf+p->nUsed, zFormat, ap); |
︙ | ︙ | |||
2331 2332 2333 2334 2335 2336 2337 2338 2339 2340 2341 2342 2343 2344 | malformed_jsonb: pOut->eErr |= JSTRING_MALFORMED; break; } } return i+n+sz; } /* Return true if the input pJson ** ** For performance reasons, this routine does not do a detailed check of the ** input BLOB to ensure that it is well-formed. Hence, false positives are ** possible. False negatives should never occur, however. */ | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 2330 2331 2332 2333 2334 2335 2336 2337 2338 2339 2340 2341 2342 2343 2344 2345 2346 2347 2348 2349 2350 2351 2352 2353 2354 2355 2356 2357 2358 2359 2360 2361 2362 2363 2364 2365 2366 2367 2368 2369 2370 2371 2372 2373 2374 2375 2376 2377 2378 2379 2380 2381 2382 2383 2384 2385 2386 2387 2388 2389 2390 2391 2392 2393 2394 2395 2396 2397 2398 2399 2400 2401 2402 2403 2404 2405 2406 2407 2408 2409 2410 2411 2412 2413 2414 2415 2416 2417 2418 2419 2420 2421 2422 2423 2424 2425 2426 2427 2428 2429 2430 2431 2432 2433 2434 2435 2436 2437 2438 2439 2440 2441 2442 2443 2444 2445 2446 2447 2448 2449 | malformed_jsonb: pOut->eErr |= JSTRING_MALFORMED; break; } } return i+n+sz; } /* Context for recursion of json_pretty() */ typedef struct JsonPretty JsonPretty; struct JsonPretty { JsonParse *pParse; /* The BLOB being rendered */ JsonString *pOut; /* Generate pretty output into this string */ const char *zIndent; /* Use this text for indentation */ u32 szIndent; /* Bytes in zIndent[] */ u32 nIndent; /* Current level of indentation */ }; /* Append indentation to the pretty JSON under construction */ static void jsonPrettyIndent(JsonPretty *pPretty){ u32 jj; for(jj=0; jj<pPretty->nIndent; jj++){ jsonAppendRaw(pPretty->pOut, pPretty->zIndent, pPretty->szIndent); } } /* ** Translate the binary JSONB representation of JSON beginning at ** pParse->aBlob[i] into a JSON text string. Append the JSON ** text onto the end of pOut. Return the index in pParse->aBlob[] ** of the first byte past the end of the element that is translated. ** ** This is a variant of jsonTranslateBlobToText() that "pretty-prints" ** the output. Extra whitespace is inserted to make the JSON easier ** for humans to read. ** ** If an error is detected in the BLOB input, the pOut->eErr flag ** might get set to JSTRING_MALFORMED. But not all BLOB input errors ** are detected. So a malformed JSONB input might either result ** in an error, or in incorrect JSON. ** ** The pOut->eErr JSTRING_OOM flag is set on a OOM. */ static u32 jsonTranslateBlobToPrettyText( JsonPretty *pPretty, /* Pretty-printing context */ u32 i /* Start rendering at this index */ ){ u32 sz, n, j, iEnd; const JsonParse *pParse = pPretty->pParse; JsonString *pOut = pPretty->pOut; n = jsonbPayloadSize(pParse, i, &sz); if( n==0 ){ pOut->eErr |= JSTRING_MALFORMED; return pParse->nBlob+1; } switch( pParse->aBlob[i] & 0x0f ){ case JSONB_ARRAY: { j = i+n; iEnd = j+sz; jsonAppendChar(pOut, '['); if( j<iEnd ){ jsonAppendChar(pOut, '\n'); pPretty->nIndent++; while( pOut->eErr==0 ){ jsonPrettyIndent(pPretty); j = jsonTranslateBlobToPrettyText(pPretty, j); if( j>=iEnd ) break; jsonAppendRawNZ(pOut, ",\n", 2); } jsonAppendChar(pOut, '\n'); pPretty->nIndent--; jsonPrettyIndent(pPretty); } jsonAppendChar(pOut, ']'); i = iEnd; break; } case JSONB_OBJECT: { j = i+n; iEnd = j+sz; jsonAppendChar(pOut, '{'); if( j<iEnd ){ jsonAppendChar(pOut, '\n'); pPretty->nIndent++; while( pOut->eErr==0 ){ jsonPrettyIndent(pPretty); j = jsonTranslateBlobToText(pParse, j, pOut); if( j>iEnd ){ pOut->eErr |= JSTRING_MALFORMED; break; } jsonAppendRawNZ(pOut, ": ", 2); j = jsonTranslateBlobToPrettyText(pPretty, j); if( j>=iEnd ) break; jsonAppendRawNZ(pOut, ",\n", 2); } jsonAppendChar(pOut, '\n'); pPretty->nIndent--; jsonPrettyIndent(pPretty); } jsonAppendChar(pOut, '}'); i = iEnd; break; } default: { i = jsonTranslateBlobToText(pParse, i, pOut); break; } } return i; } /* Return true if the input pJson ** ** For performance reasons, this routine does not do a detailed check of the ** input BLOB to ensure that it is well-formed. Hence, false positives are ** possible. False negatives should never occur, however. */ |
︙ | ︙ | |||
4250 4251 4252 4253 4254 4255 4256 4257 4258 4259 4260 4261 4262 4263 | }else{ i = 0; } sqlite3_result_text(ctx, jsonbType[p->aBlob[i]&0x0f], -1, SQLITE_STATIC); json_type_done: jsonParseFree(p); } /* ** json_valid(JSON) ** json_valid(JSON, FLAGS) ** ** Check the JSON argument to see if it is well-formed. The FLAGS argument ** encodes the various constraints on what is meant by "well-formed": | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 4355 4356 4357 4358 4359 4360 4361 4362 4363 4364 4365 4366 4367 4368 4369 4370 4371 4372 4373 4374 4375 4376 4377 4378 4379 4380 4381 4382 4383 4384 4385 4386 4387 4388 4389 4390 4391 4392 4393 4394 4395 4396 4397 4398 4399 4400 4401 4402 | }else{ i = 0; } sqlite3_result_text(ctx, jsonbType[p->aBlob[i]&0x0f], -1, SQLITE_STATIC); json_type_done: jsonParseFree(p); } /* ** json_pretty(JSON) ** json_pretty(JSON, INDENT) ** ** Return text that is a pretty-printed rendering of the input JSON. ** If the argument is not valid JSON, return NULL. ** ** The INDENT argument is text that is used for indentation. If omitted, ** it defaults to four spaces (the same as PostgreSQL). */ static void jsonPrettyFunc( sqlite3_context *ctx, int argc, sqlite3_value **argv ){ JsonString s; /* The output string */ JsonPretty x; /* Pretty printing context */ memset(&x, 0, sizeof(x)); x.pParse = jsonParseFuncArg(ctx, argv[0], 0); if( x.pParse==0 ) return; x.pOut = &s; jsonStringInit(&s, ctx); if( argc==1 || (x.zIndent = (const char*)sqlite3_value_text(argv[1]))==0 ){ x.zIndent = " "; x.szIndent = 4; }else{ x.szIndent = (u32)strlen(x.zIndent); } jsonTranslateBlobToPrettyText(&x, 0); jsonReturnString(&s, 0, 0); jsonParseFree(x.pParse); } /* ** json_valid(JSON) ** json_valid(JSON, FLAGS) ** ** Check the JSON argument to see if it is well-formed. The FLAGS argument ** encodes the various constraints on what is meant by "well-formed": |
︙ | ︙ | |||
5265 5266 5267 5268 5269 5270 5271 5272 5273 5274 5275 5276 5277 5278 | JFUNCTION(->>, 2,1,0, 0,0,JSON_SQL, jsonExtractFunc), JFUNCTION(json_insert, -1,1,1, 1,0,0, jsonSetFunc), JFUNCTION(jsonb_insert, -1,1,0, 1,1,0, jsonSetFunc), JFUNCTION(json_object, -1,0,1, 1,0,0, jsonObjectFunc), JFUNCTION(jsonb_object, -1,0,1, 1,1,0, jsonObjectFunc), JFUNCTION(json_patch, 2,1,1, 0,0,0, jsonPatchFunc), JFUNCTION(jsonb_patch, 2,1,0, 0,1,0, jsonPatchFunc), JFUNCTION(json_quote, 1,0,1, 1,0,0, jsonQuoteFunc), JFUNCTION(json_remove, -1,1,1, 0,0,0, jsonRemoveFunc), JFUNCTION(jsonb_remove, -1,1,0, 0,1,0, jsonRemoveFunc), JFUNCTION(json_replace, -1,1,1, 1,0,0, jsonReplaceFunc), JFUNCTION(jsonb_replace, -1,1,0, 1,1,0, jsonReplaceFunc), JFUNCTION(json_set, -1,1,1, 1,0,JSON_ISSET, jsonSetFunc), JFUNCTION(jsonb_set, -1,1,0, 1,1,JSON_ISSET, jsonSetFunc), | > > | 5404 5405 5406 5407 5408 5409 5410 5411 5412 5413 5414 5415 5416 5417 5418 5419 | JFUNCTION(->>, 2,1,0, 0,0,JSON_SQL, jsonExtractFunc), JFUNCTION(json_insert, -1,1,1, 1,0,0, jsonSetFunc), JFUNCTION(jsonb_insert, -1,1,0, 1,1,0, jsonSetFunc), JFUNCTION(json_object, -1,0,1, 1,0,0, jsonObjectFunc), JFUNCTION(jsonb_object, -1,0,1, 1,1,0, jsonObjectFunc), JFUNCTION(json_patch, 2,1,1, 0,0,0, jsonPatchFunc), JFUNCTION(jsonb_patch, 2,1,0, 0,1,0, jsonPatchFunc), JFUNCTION(json_pretty, 1,1,0, 0,0,0, jsonPrettyFunc), JFUNCTION(json_pretty, 2,1,0, 0,0,0, jsonPrettyFunc), JFUNCTION(json_quote, 1,0,1, 1,0,0, jsonQuoteFunc), JFUNCTION(json_remove, -1,1,1, 0,0,0, jsonRemoveFunc), JFUNCTION(jsonb_remove, -1,1,0, 0,1,0, jsonRemoveFunc), JFUNCTION(json_replace, -1,1,1, 1,0,0, jsonReplaceFunc), JFUNCTION(jsonb_replace, -1,1,0, 1,1,0, jsonReplaceFunc), JFUNCTION(json_set, -1,1,1, 1,0,JSON_ISSET, jsonSetFunc), JFUNCTION(jsonb_set, -1,1,0, 1,1,JSON_ISSET, jsonSetFunc), |
︙ | ︙ |
Changes to test/json106.test.
︙ | ︙ | |||
63 64 65 66 67 68 69 70 71 72 73 | } 0 do_execsql_test $ii.7 { UPDATE t1 SET p=json_patch(j0,j5); SELECT count(*) FROM t1, kv WHERE p->>key IS NOT val } 0 } finish_test | > > > > > > | 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 | } 0 do_execsql_test $ii.7 { UPDATE t1 SET p=json_patch(j0,j5); SELECT count(*) FROM t1, kv WHERE p->>key IS NOT val } 0 do_execsql_test $ii.8 { SELECT j0 FROM t1 WHERE json(j0)!=json(json_pretty(j0)); } {} do_execsql_test $ii.9 { SELECT j5 FROM t1 WHERE json(j5)!=json(json_pretty(j5)); } {} } finish_test |
Added test/json108.test.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | # 2024-03-06 # # The author disclaims copyright to this source code. In place of # a legal notice, here is a blessing: # # May you do good and not evil. # May you find forgiveness for yourself and forgive others. # May you share freely, never taking more than you give. # #*********************************************************************** # Invariant tests for JSON built around the randomjson extension # set testdir [file dirname $argv0] source $testdir/tester.tcl set testprefix json108 # These tests require virtual table "json_tree" to run. ifcapable !vtab { finish_test ; return } load_static_extension db randomjson db eval { CREATE TEMP TABLE t1(j0,j5); WITH RECURSIVE c(n) AS (VALUES(0) UNION ALL SELECT n+1 FROM c WHERE n<9) INSERT INTO t1 SELECT random_json(n), random_json5(n) FROM c; } do_execsql_test 1.1 { SELECT count(*) FROM t1 WHERE json(j0)==json(json_pretty(j0,NULL)); } 10 do_execsql_test 1.2 { SELECT count(*) FROM t1 WHERE json(j0)==json(json_pretty(j0,NULL)); } 10 do_execsql_test 1.3 { SELECT count(*) FROM t1 WHERE json(j0)==json(json_pretty(j0,'')); } 10 do_execsql_test 1.4 { SELECT count(*) FROM t1 WHERE json(j0)==json(json_pretty(j0,char(9))); } 10 do_execsql_test 1.5 { SELECT count(*) FROM t1 WHERE json(j0)==json(json_pretty(j0,'/*hello*/')); } 10 finish_test |