Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 11 additions & 8 deletions src/js/internal/sql/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,13 @@ class SQLArrayParameter {
}

class SQLResultArray<T> extends PublicArray<T> {
public count!: number | null;
public command!: string | null;
public lastInsertRowid!: number | bigint | null;
public affectedRows!: number | bigint | null;
// `declare` so these are only types. A real class field would be defined as
// an enumerable own property as soon as super() returns, and the
// defineProperties call below would then leave it enumerable.
declare count: number | null;
declare command: string | null;
declare lastInsertRowid: number | bigint | null;
declare affectedRows: number | bigint | null;

static [Symbol.toStringTag] = "SQLResults";

Expand All @@ -91,10 +94,10 @@ class SQLResultArray<T> extends PublicArray<T> {
// match postgres's result array, in this way for in will not list the
// properties and .map will not return undefined command and count
Object.defineProperties(this, {
count: { value: null, writable: true },
command: { value: null, writable: true },
lastInsertRowid: { value: null, writable: true },
affectedRows: { value: null, writable: true },
count: { value: null, writable: true, configurable: true },
command: { value: null, writable: true, configurable: true },
lastInsertRowid: { value: null, writable: true, configurable: true },
affectedRows: { value: null, writable: true, configurable: true },
});
}

Expand Down
25 changes: 25 additions & 0 deletions test/js/sql/sql-mysql.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,31 @@ if (isDockerEnabled()) {
await sql`UPDATE ${sql(random_name)} SET name = "test2" WHERE id = ${lastInsertRowid}`;
expect(affectedRows).toBe(1);
});
test("result metadata properties are not enumerable", async () => {
await using db = new SQL({ ...getOptions(), max: 1, idleTimeout: 5 });
using sql = await db.reserve();
const t = "meta_" + randomUUIDv7("hex").replaceAll("-", "");
await sql`CREATE TEMPORARY TABLE ${sql(t)} (id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, name text)`;

const inserted = await sql`INSERT INTO ${sql(t)} (name) VALUES (${"test"})`;
expect(Object.keys(inserted)).toEqual([]);
expect(
["lastInsertRowid", "affectedRows"].map(key => Object.getOwnPropertyDescriptor(inserted, key)),
).toEqual([
{ value: 1, writable: true, enumerable: false, configurable: true },
{ value: 1, writable: true, enumerable: false, configurable: true },
]);

const selected = await sql`SELECT id, name FROM ${sql(t)}`;
expect(selected).toEqual([{ id: 1, name: "test" }]);
expect(Object.keys(selected)).toEqual(["0"]);
expect(Object.getOwnPropertyDescriptor(selected, "count")).toEqual({
value: 1,
writable: true,
enumerable: false,
configurable: true,
});
});
test("MEDIUMINT not in the last column reads following columns correctly", async () => {
// MySQL's binary protocol sends MYSQL_TYPE_INT24 as a fixed 4-byte
// field. Reading only 3 left the cursor 1 byte behind, silently
Expand Down
12 changes: 12 additions & 0 deletions test/js/sql/sql.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -976,6 +976,18 @@ if (isDockerEnabled()) {
expect((await sql`select 1`).command).toBe("SELECT");
});

test("Result metadata is not enumerable", async () => {
const result = await sql`select 1 as x`;
expect(Object.keys(result)).toEqual(["0"]);
const metadata = ["count", "command", "lastInsertRowid", "affectedRows"];
expect(metadata.map(key => Object.getOwnPropertyDescriptor(result, key))).toEqual([
{ value: 1, writable: true, enumerable: false, configurable: true },
{ value: "SELECT", writable: true, enumerable: false, configurable: true },
{ value: null, writable: true, enumerable: false, configurable: true },
{ value: null, writable: true, enumerable: false, configurable: true },
]);
});

test("Create table", async () => {
await sql`create table test(int int)`;
await sql`drop table test`;
Expand Down
32 changes: 32 additions & 0 deletions test/js/sql/sqlite-sql.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -872,6 +872,38 @@ describe("Query Execution", () => {
expect(result.command).toBe("DELETE");
});

test("result metadata properties are not enumerable", async () => {
await sql`CREATE TABLE result_metadata (id INTEGER PRIMARY KEY, name TEXT)`;
const inserted = await sql`INSERT INTO result_metadata (name) VALUES (${"a"}), (${"b"})`;
const selected = await sql`SELECT * FROM result_metadata ORDER BY id`;

// Like postgres.js, the metadata hangs off the array without showing up
// when the rows are enumerated.
expect(Object.keys(selected)).toEqual(["0", "1"]);
expect(Object.keys(inserted)).toEqual([]);
const forIn: string[] = [];
for (const key in selected) forIn.push(key);
expect(forIn).toEqual(["0", "1"]);
expect({ ...selected }).toEqual({
"0": { id: 1, name: "a" },
"1": { id: 2, name: "b" },
});

const metadata = ["count", "command", "lastInsertRowid", "affectedRows"];
expect(metadata.map(key => Object.getOwnPropertyDescriptor(selected, key))).toEqual([
{ value: 2, writable: true, enumerable: false, configurable: true },
{ value: "SELECT", writable: true, enumerable: false, configurable: true },
{ value: null, writable: true, enumerable: false, configurable: true },
{ value: null, writable: true, enumerable: false, configurable: true },
]);
expect(metadata.map(key => Object.getOwnPropertyDescriptor(inserted, key))).toEqual([
{ value: 2, writable: true, enumerable: false, configurable: true },
{ value: "INSERT", writable: true, enumerable: false, configurable: true },
{ value: 2, writable: true, enumerable: false, configurable: true },
{ value: null, writable: true, enumerable: false, configurable: true },
]);
});

test("SELECT with various clauses", async () => {
await sql`CREATE TABLE scores (id INTEGER, player TEXT, score INTEGER, team TEXT)`;
await sql`INSERT INTO scores VALUES
Expand Down