Created
March 30, 2024 17:02
-
-
Save kamilogorek/fdba0412e4f1c0b582da99c511cc0a42 to your computer and use it in GitHub Desktop.
pglite + postgres wire protocol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { createServer } from "node:net"; | |
import { PGlite } from "@electric-sql/pglite"; | |
const PORT = 5432; | |
const db = new PGlite(); | |
await db.exec(` | |
CREATE TABLE IF NOT EXISTS test ( | |
id SERIAL PRIMARY KEY, | |
name TEXT | |
); | |
INSERT INTO test (name) VALUES ('test'); | |
`); | |
function isSSLRequest(buffer) { | |
return ( | |
buffer.at(4) === 0x04 && | |
buffer.at(5) === 0xd2 && | |
buffer.at(6) === 0x16 && | |
buffer.at(7) === 0x2f | |
); | |
} | |
function isStartupMessage(buffer) { | |
return ( | |
buffer.at(4) === 0x00 && | |
buffer.at(5) === 0x03 && | |
buffer.at(6) === 0x00 && | |
buffer.at(7) === 0x00 | |
); | |
} | |
function isExitMessage(buffer) { | |
return buffer.at(0) === 0x58; // 'X' | |
} | |
function isQueryMessage(buffer) { | |
return buffer.at(0) === 0x51; // 'Q' | |
} | |
const server = createServer(); | |
server.on("connection", function (socket) { | |
const clientAddr = `${socket.remoteAddress}:${socket.remotePort}`; | |
console.log(`Client connected: ${clientAddr}`); | |
// https://www.postgresql.org/docs/current/protocol-message-formats.html | |
socket.on("data", async (data) => { | |
if (isSSLRequest(data)) { | |
// SSL negotiation | |
const sslNegotiation = Buffer.alloc(1); | |
sslNegotiation.write("N"); | |
socket.write(sslNegotiation); | |
} else if (isStartupMessage(data)) { | |
// AuthenticationOk | |
const authOk = Buffer.alloc(9); | |
authOk.write("R"); // 'R' for AuthenticationOk | |
authOk.writeInt8(8, 4); // Length | |
authOk.writeInt8(0, 7); // AuthenticationOk | |
// BackendKeyData | |
const backendKeyData = Buffer.alloc(13); | |
backendKeyData.write("K"); // Message type | |
backendKeyData.writeInt8(12, 4); // Message length | |
backendKeyData.writeInt16BE(1234, 7); // Process ID | |
backendKeyData.writeInt16BE(5679, 11); // Secret key | |
// ReadyForQuery | |
const readyForQuery = Buffer.alloc(6); | |
readyForQuery.write("Z"); // 'Z' for ReadyForQuery | |
readyForQuery.writeInt8(5, 4); // Length | |
readyForQuery.write("I", 5); // Transaction status indicator, 'I' for idle | |
socket.write(Buffer.concat([authOk, backendKeyData, readyForQuery])); | |
} else if (isExitMessage(data)) { | |
socket.end(); | |
} else if (isQueryMessage(data)) { | |
const result = await db.execProtocol(data); | |
socket.write(Buffer.concat(result.map(([_, buffer]) => buffer))); | |
} else { | |
console.log("Unknown message:", data); | |
} | |
}); | |
socket.on("end", () => { | |
console.log(`Client disconnected: ${clientAddr}`); | |
}); | |
socket.on("error", (err) => { | |
console.log(`Client ${clientAddr} error:`, err); | |
socket.end(); | |
}); | |
}); | |
server.on("error", (err) => { | |
console.log(`Server error:`, err); | |
}); | |
server.listen(PORT, () => { | |
console.log(`Server bound to port ${PORT}`); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment