Skip to content

Commit e5efa5d

Browse files
[wrangler] Fix rendering of nested objects in R2 SQL query results (#12506)
Co-authored-by: Pete Bacon Darwin <pbacondarwin@cloudflare.com>
1 parent 1d5f66c commit e5efa5d

File tree

3 files changed

+97
-1
lines changed

3 files changed

+97
-1
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"wrangler": patch
3+
---
4+
5+
Fix `wrangler r2 sql query` displaying `[object Object]` for nested values
6+
7+
SQL functions that return complex types such as arrays of objects (e.g. `approx_top_k`) were rendered as `[object Object]` in the table output because `String()` was called directly on non-primitive values. These values are now serialized with `JSON.stringify` so they display as readable JSON strings.

packages/wrangler/src/__tests__/r2/sql.test.ts

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,83 @@ describe("r2 sql", () => {
223223
).rejects.toThrow("Received a malformed response from the API");
224224
});
225225

226+
it("should handle nested objects (as JSON with null converted to '') in query results", async () => {
227+
const mockResponse = {
228+
success: true,
229+
errors: [],
230+
messages: [],
231+
result: {
232+
request_id: "dqe-prod-test",
233+
schema: [
234+
{
235+
name: "approx_top_k(value, Int64(3))",
236+
descriptor: {
237+
type: {
238+
name: "list",
239+
item: {
240+
type: {
241+
name: "struct",
242+
fields: [
243+
{
244+
type: { name: "int64" },
245+
nullable: true,
246+
name: "value",
247+
},
248+
{
249+
type: { name: "uint64" },
250+
nullable: false,
251+
name: "count",
252+
},
253+
],
254+
},
255+
nullable: true,
256+
},
257+
},
258+
nullable: true,
259+
},
260+
},
261+
],
262+
rows: [
263+
{
264+
"approx_top_k(value, Int64(3))": [
265+
{ value: 0, count: 961 },
266+
{ value: 1, count: 485 },
267+
{ value: 2, count: null },
268+
],
269+
},
270+
],
271+
metrics: {
272+
r2_requests_count: 6,
273+
files_scanned: 3,
274+
bytes_scanned: 62878,
275+
},
276+
},
277+
};
278+
279+
msw.use(
280+
http.post(
281+
"https://api.sql.cloudflarestorage.com/api/v1/accounts/:accountId/r2-sql/query/:bucketName",
282+
async () => {
283+
return HttpResponse.json(mockResponse);
284+
},
285+
{ once: true }
286+
)
287+
);
288+
289+
await runWrangler(`r2 sql query ${mockWarehouse} "${mockQuery}"`);
290+
291+
const startOfTable = std.out.indexOf("┌");
292+
const endOfTable = std.out.indexOf("┘") + 1;
293+
294+
expect(std.out.slice(startOfTable, endOfTable)).toMatchInlineSnapshot(`
295+
"┌─┐
296+
│ approx_top_k(value, Int64(3)) │
297+
├─┤
298+
│ [{\\"value\\":0,\\"count\\":961},{\\"value\\":1,\\"count\\":485},{\\"value\\":2,\\"count\\":\\"\\"}] │
299+
└─┘"
300+
`);
301+
});
302+
226303
it("should handle null values in query results", async () => {
227304
const mockResponse = {
228305
success: true,

packages/wrangler/src/r2/sql.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,19 @@ function formatSqlResults(data: SqlQueryResponse, duration: number): void {
3737
logger.table(
3838
rows.map((row) =>
3939
Object.fromEntries(
40-
column_order.map((column) => [column, String(row[column] ?? "")])
40+
column_order.map((column) => {
41+
const value = row[column];
42+
if (value === null || value === undefined) {
43+
return [column, ""];
44+
}
45+
if (typeof value === "object") {
46+
return [
47+
column,
48+
JSON.stringify(value, (_k, v) => (v === null ? "" : v)),
49+
];
50+
}
51+
return [column, String(value)];
52+
})
4153
)
4254
),
4355
{ wordWrap: true, head: column_order }

0 commit comments

Comments
 (0)