fix(sql) fix state being set to prepared too soon (#17732)

This commit is contained in:
Ciro Spaciari
2025-02-26 16:02:51 -08:00
committed by GitHub
parent 1060558456
commit 5c6e20aeb4
6 changed files with 125 additions and 8 deletions

View File

@@ -1295,7 +1295,6 @@ function doCreateQuery(strings, values, allowUnsafeTransaction, poolSize, bigint
}
}
}
return createQuery(sqlString, final_values, new SQLResultArray(), undefined, !!bigint, !!simple);
}

View File

@@ -853,6 +853,7 @@ pub const PostgresSQLQuery = struct {
connection.prepared_statement_id += 1;
stmt.* = .{ .signature = signature, .ref_count = 2, .status = if (can_execute) .parsing else .pending };
this.statement = stmt;
entry_value.* = stmt;
} else {
stmt.* = .{ .signature = signature, .ref_count = 1, .status = if (can_execute) .parsing else .pending };
@@ -956,9 +957,21 @@ pub const PostgresRequest = struct {
iter.to(0);
var i: usize = 0;
while (iter.next()) |value| : (i += 1) {
const parameter_field = parameter_fields[i];
const is_custom_type = std.math.maxInt(short) < parameter_field;
const tag: types.Tag = if (is_custom_type) .text else @enumFromInt(@as(short, @intCast(parameter_field)));
const tag: types.Tag = brk: {
if (i >= len) {
// parameter in array but not in parameter_fields
// this is probably a bug a bug in bun lets return .text here so the server will send a error 08P01
// with will describe better the error saying exactly how many parameters are missing and are expected
// Example:
// SQL error: PostgresError: bind message supplies 0 parameters, but prepared statement "PSELECT * FROM test_table WHERE id=$1 .in$0" requires 1
// errno: "08P01",
// code: "ERR_POSTGRES_SERVER_ERROR"
break :brk .text;
}
const parameter_field = parameter_fields[i];
const is_custom_type = std.math.maxInt(short) < parameter_field;
break :brk if (is_custom_type) .text else @enumFromInt(@as(short, @intCast(parameter_field)));
};
if (value.isEmptyOrUndefinedOrNull()) {
debug(" -> NULL", .{});
// As a special case, -1 indicates a
@@ -3598,7 +3611,8 @@ pub const PostgresSQLConnection = struct {
try reader.eatMessage(protocol.ParseComplete);
const request = this.current() orelse return error.ExpectedRequest;
if (request.statement) |statement| {
if (statement.status == .parsing) {
// if we have params wait for parameter description
if (statement.status == .parsing and statement.signature.fields.len == 0) {
statement.status = .prepared;
}
}
@@ -3609,6 +3623,9 @@ pub const PostgresSQLConnection = struct {
const request = this.current() orelse return error.ExpectedRequest;
var statement = request.statement orelse return error.ExpectedStatement;
statement.parameters = description.parameters;
if (statement.status == .parsing) {
statement.status = .prepared;
}
},
.RowDescription => {
var description: protocol.RowDescription = undefined;

View File

@@ -6,7 +6,7 @@
"dependencies": {
"@azure/service-bus": "7.9.4",
"@duckdb/node-api": "1.1.3-alpha.7",
"@electric-sql/pglite": "0.2.16",
"@electric-sql/pglite": "0.2.17",
"@grpc/grpc-js": "1.12.0",
"@grpc/proto-loader": "0.7.10",
"@happy-dom/global-registrator": "17.0.3",
@@ -52,6 +52,7 @@
"nodemailer": "6.9.3",
"pg": "8.11.1",
"pg-connection-string": "2.6.1",
"pg-gateway": "^0.3.0-beta.4",
"pino": "9.4.0",
"pino-pretty": "11.2.2",
"postgres": "3.3.5",
@@ -171,7 +172,7 @@
"@duckdb/node-bindings-win32-x64": ["@duckdb/node-bindings-win32-x64@1.1.3-alpha.7", "", { "os": "win32", "cpu": "x64" }, "sha512-5OqjpYRFdQATbniL0o8gF8Z92bBuINlXOve0o+qgM6W+nlIRp/cUHk6vWwYySZnF0AIHZ5JG7mngzJOLmA/kPQ=="],
"@electric-sql/pglite": ["@electric-sql/pglite@0.2.16", "", {}, "sha512-dCSHpoOKuTxecaYhWDRp2yFTN3XWcMPMrBVl5yOR8VZEUprz4+R3iuU7BipmlsqBnBDO/6l9H/C2ZwJdunkWyw=="],
"@electric-sql/pglite": ["@electric-sql/pglite@0.2.17", "", {}, "sha512-qEpKRT2oUaWDH6tjRxLHjdzMqRUGYDnGZlKrnL4dJ77JVMcP2Hpo3NYnOSPKdZdeec57B6QPprCUFg0picx5Pw=="],
"@emnapi/runtime": ["@emnapi/runtime@0.44.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-ZX/etZEZw8DR7zAB1eVQT40lNo0jeqpb6dCgOvctB6FIQ5PoXfMuNY8+ayQfu8tNQbAB8gQWSSJupR8NxeiZXw=="],
@@ -1665,6 +1666,8 @@
"pg-connection-string": ["pg-connection-string@2.6.1", "", {}, "sha512-w6ZzNu6oMmIzEAYVw+RLK0+nqHPt8K3ZnknKi+g48Ak2pr3dtljJW3o+D/n2zzCG07Zoe9VOX3aiKpj+BN0pjg=="],
"pg-gateway": ["pg-gateway@0.3.0-beta.4", "", {}, "sha512-CTjsM7Z+0Nx2/dyZ6r8zRsc3f9FScoD5UAOlfUx1Fdv/JOIWvRbF7gou6l6vP+uypXQVoYPgw8xZDXgMGvBa4Q=="],
"pg-int8": ["pg-int8@1.0.1", "", {}, "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw=="],
"pg-pool": ["pg-pool@3.6.2", "", { "peerDependencies": { "pg": ">=8.0" } }, "sha512-Htjbg8BlwXqSBQ9V8Vjtc+vzf/6fVUuak/3/XXKA9oxZprwW3IMDQTGHP+KDmVL7rtd+R1QjbnCFPuTHm3G4hg=="],

View File

@@ -0,0 +1,7 @@
{
"name": "pg-gateway",
"dependencies": {
"@electric-sql/pglite": "0.2.17",
"pg-gateway": "0.3.0-beta.4"
}
}

View File

@@ -0,0 +1,90 @@
import { PGlite } from "@electric-sql/pglite";
import { SQL, randomUUIDv7 } from "bun";
import net, { AddressInfo } from "node:net";
import { fromNodeSocket } from "pg-gateway/node";
import { expect, test } from "bun:test";
import { once } from "events";
test("pglite should be able to query using pg-gateway and Bun.SQL", async () => {
const name = "test_" + randomUUIDv7("hex").replaceAll("-", "");
const dataDir = `memory://${name}`;
const db = new PGlite(dataDir);
// Wait for the database to initialize
await db.waitReady;
// Create a simple test table
await db.exec(`
CREATE TABLE IF NOT EXISTS test_table (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL
);
INSERT INTO test_table (name) VALUES ('Test 1'), ('Test 2'), ('Test 3');
`);
// Create a simple server using pg-gateway
const server = net.createServer(async socket => {
await fromNodeSocket(socket, {
serverVersion: "16.3",
auth: {
method: "trust",
},
async onStartup() {
// Wait for PGlite to be ready before further processing
await db.waitReady;
},
async onMessage(data, { isAuthenticated }: { isAuthenticated: boolean }) {
// Only forward messages to PGlite after authentication
if (!isAuthenticated) {
return;
}
return await db.execProtocolRaw(data);
},
});
});
// Start listening
await once(server.listen(0), "listening");
const port = (server.address() as AddressInfo).port;
await using sql = new SQL({
hostname: "localhost",
port: port,
database: name,
max: 1,
});
{
// prepared statement without parameters
const result = await sql`SELECT * FROM test_table WHERE id = 1`;
expect(result).toBeDefined();
expect(result.length).toBe(1);
expect(result[0]).toEqual({ id: 1, name: "Test 1" });
}
{
// using prepared statement
const result = await sql`SELECT * FROM test_table WHERE id = ${1}`;
expect(result).toBeDefined();
expect(result.length).toBe(1);
expect(result[0]).toEqual({ id: 1, name: "Test 1" });
}
{
// using simple query
const result = await sql`SELECT * FROM test_table WHERE id = 1`.simple();
expect(result).toBeDefined();
expect(result.length).toBe(1);
expect(result[0]).toEqual({ id: 1, name: "Test 1" });
}
{
// using unsafe with parameters
const result = await sql.unsafe("SELECT * FROM test_table WHERE id = $1", [1]);
expect(result).toBeDefined();
expect(result.length).toBe(1);
expect(result[0]).toEqual({ id: 1, name: "Test 1" });
}
});

View File

@@ -11,7 +11,7 @@
"dependencies": {
"@azure/service-bus": "7.9.4",
"@duckdb/node-api": "1.1.3-alpha.7",
"@electric-sql/pglite": "0.2.16",
"@electric-sql/pglite": "0.2.17",
"@grpc/grpc-js": "1.12.0",
"@grpc/proto-loader": "0.7.10",
"@happy-dom/global-registrator": "17.0.3",
@@ -57,6 +57,7 @@
"nodemailer": "6.9.3",
"pg": "8.11.1",
"pg-connection-string": "2.6.1",
"pg-gateway": "0.3.0-beta.4",
"pino": "9.4.0",
"pino-pretty": "11.2.2",
"postgres": "3.3.5",