From 1673652242cf352dfe85d9d72b9d5a745016cf69 Mon Sep 17 00:00:00 2001 From: Yury Selivanov Date: Mon, 27 Jan 2025 22:31:34 -0800 Subject: [PATCH] Flip the default - make client.querySQL() return an object --- docs/reference.rst | 19 ++++++++++++++++++ packages/driver/src/codecs/record.ts | 4 ++-- packages/driver/src/options.ts | 12 +++++------ packages/driver/test/client.test.ts | 30 ++++++++++++++-------------- 4 files changed, 42 insertions(+), 23 deletions(-) diff --git a/docs/reference.rst b/docs/reference.rst index 97ae6de1f..3f2895bd5 100644 --- a/docs/reference.rst +++ b/docs/reference.rst @@ -343,8 +343,27 @@ Client Run a SQL query and return the results as an array. This method **always** returns an array. + The array will contain the returned rows. By default, rows are + ``Objects`` with columns addressable by name. + + This can controlled with ``client.withSQLRowMode('array' | 'object')`` + API. + This method takes optional query arguments. + Example: + + .. code-block:: js + + let vals = await client.querySQL(`SELECT 1 as foo`) + console.log(vals); // [{'foo': 1}] + + vals = await client + .withSQLRowMode('array') + .querySQL(`SELECT 1 as foo`); + + console.log(vals); // [[1]] + .. js:method:: transaction( \ action: (tx: Transaction) => Promise \ ): Promise diff --git a/packages/driver/src/codecs/record.ts b/packages/driver/src/codecs/record.ts index fe22f09e2..49cb80011 100644 --- a/packages/driver/src/codecs/record.ts +++ b/packages/driver/src/codecs/record.ts @@ -77,7 +77,7 @@ export class RecordCodec extends Codec implements ICodec { const elemBuf = ReadBuffer.alloc(); const overload = ctx.getContainerOverload("_private_sql_row"); - if (overload !== SQLRowObjectCodec) { + if (overload != null && overload !== SQLRowObjectCodec) { const result: any[] = new Array(els); for (let i = 0; i < els; i++) { buf.discard(4); // reserved @@ -91,7 +91,7 @@ export class RecordCodec extends Codec implements ICodec { result[i] = val; } - if (overload != null) { + if (overload !== SQLRowArrayCodec) { return overload.fromDatabase(result, { names: this.names }); } diff --git a/packages/driver/src/options.ts b/packages/driver/src/options.ts index bf1226de4..2f2825564 100644 --- a/packages/driver/src/options.ts +++ b/packages/driver/src/options.ts @@ -2,7 +2,7 @@ import * as errors from "./errors/index"; import { utf8Encoder } from "./primitives/buffer"; import type { Mutable } from "./typeutil"; import type { Codecs } from "./codecs/codecs"; -import { SQLRowModeObject } from "./codecs/record"; +import { SQLRowModeArray } from "./codecs/record"; import type { ReadonlyCodecMap, MutableCodecMap } from "./codecs/context"; import { CodecContext, NOOP_CODEC_CONTEXT } from "./codecs/context"; @@ -280,9 +280,9 @@ export class Options { if (mergeOptions._dropSQLRowCodec && clone.codecs.has("sql_row")) { // This is an optimization -- if "sql_row" is the only codec defined - // and it's set to "array mode", the we want the codec mapping to be + // and it's set to "object mode", the we want the codec mapping to be // empty instead. Why? Empty codec mapping short circuits a lot of - // custom codec code, and array is the default behavior anyway. + // custom codec code, and object is the default behavior anyway. (clone.codecs as MutableCodecMap).delete("sql_row"); } @@ -334,10 +334,10 @@ export class Options { } withSQLRowMode(mode: "array" | "object"): Options { - if (mode === "array") { + if (mode === "object") { return this._cloneWith({ _dropSQLRowCodec: true }); - } else if (mode === "object") { - return this._cloneWith({ codecs: SQLRowModeObject }); + } else if (mode === "array") { + return this._cloneWith({ codecs: SQLRowModeArray }); } else { throw new errors.InterfaceError(`invalid mode=${mode}`); } diff --git a/packages/driver/test/client.test.ts b/packages/driver/test/client.test.ts index 37f40d3d9..5c6c0bd81 100644 --- a/packages/driver/test/client.test.ts +++ b/packages/driver/test/client.test.ts @@ -2586,10 +2586,10 @@ if (getEdgeDBVersion().major >= 5) { if (getEdgeDBVersion().major >= 6) { test("querySQL", async () => { - let client = getClient(); + let client = getClient().withSQLRowMode("array"); try { - let res = await client.querySQL("select 1"); + let res = await client.querySQL("select 1 as c"); expect(JSON.stringify(res)).toEqual("[[1]]"); res = await client.querySQL("select 1 AS foo, 2 AS bar"); @@ -2606,8 +2606,8 @@ if (getEdgeDBVersion().major >= 6) { let client = getClient(); try { - let res = await client.querySQL("select 1"); - expect(JSON.stringify(res)).toEqual("[[1]]"); + let res = await client.querySQL("select 1 as A"); + expect(JSON.stringify(res)).toEqual('[{"a":1}]'); for (let i = 0; i < 2; i++) { res = await client @@ -2638,8 +2638,8 @@ if (getEdgeDBVersion().major >= 6) { .querySQL("select 1 AS foo, 2 AS bar"); expect(JSON.stringify(res)).toEqual('[{"foo":1,"bar":2}]'); - res = await client.querySQL("select 1 + $1::int8", [41]); - expect(JSON.stringify(res)).toEqual("[[42]]"); + res = await client.querySQL('select 1 + $1::int8 as "B"', [41]); + expect(JSON.stringify(res)).toEqual('[{"B":42}]'); } } finally { await client.close(); @@ -2664,21 +2664,21 @@ if (getEdgeDBVersion().major >= 6) { try { await client.transaction(async (tx) => { await tx.execute(` - CREATE TYPE ${typename} { - CREATE REQUIRED PROPERTY prop1 -> std::str; - }; - `); + CREATE TYPE ${typename} { + CREATE REQUIRED PROPERTY prop1 -> std::str; + }; + `); await tx.executeSQL(` - INSERT INTO "${typename}" (prop1) VALUES (123); - `); + INSERT INTO "${typename}" (prop1) VALUES (123); + `); let res = await tx.querySingle(query); expect(res).toBe("123"); await tx.querySQL(` - UPDATE "${typename}" SET prop1 = '345'; - `); + UPDATE "${typename}" SET prop1 = '345'; + `); res = await tx.querySingle(query); expect(res).toBe("345"); @@ -2711,7 +2711,7 @@ if (getEdgeDBVersion().major >= 6) { `select $1::${typename} as "val"`, [val], ); - expect(JSON.stringify(res[0][0])).toEqual(JSON.stringify(val)); + expect(JSON.stringify(res[0].val)).toEqual(JSON.stringify(val)); } } finally { await client.close();