From d17c6e31d437aeef6d0fefe692125a50577d5f8e Mon Sep 17 00:00:00 2001 From: Jersey Date: Mon, 19 Aug 2024 23:55:02 -0400 Subject: [PATCH 1/3] modernize code --- .github/workflows/ci.yml | 20 +++--- .github/workflows/publish_jsr.yml | 17 +++-- Dockerfile | 3 +- README.md | 6 +- client/error.ts | 4 +- connection/auth.ts | 5 +- connection/connection.ts | 108 +++++++++++++++--------------- connection/connection_params.ts | 8 +-- connection/packet.ts | 2 +- connection/scram.ts | 16 ++--- deno.json | 16 +++-- deps.ts | 18 ----- docker-compose.yml | 2 - docs/index.html | 46 +++++++------ query/array_parser.ts | 2 +- query/decode.ts | 8 +-- query/decoders.ts | 23 ++++--- query/encode.ts | 24 ++++--- query/query.ts | 13 ++-- query/transaction.ts | 23 +++---- tests/README.md | 4 +- tests/auth_test.ts | 6 +- tests/config.ts | 2 +- tests/connection_params_test.ts | 3 +- tests/connection_test.ts | 19 +++--- tests/data_types_test.ts | 15 +++-- tests/decode_test.ts | 2 +- tests/encode_test.ts | 2 +- tests/helpers.ts | 2 +- tests/pool_test.ts | 3 +- tests/query_client_test.ts | 6 +- tests/test_deps.ts | 5 +- tests/utils_test.ts | 4 +- tools/convert_to_jsr.ts | 38 ----------- utils/deferred.ts | 4 +- utils/utils.ts | 9 +-- 36 files changed, 222 insertions(+), 266 deletions(-) delete mode 100644 deps.ts delete mode 100644 tools/convert_to_jsr.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f72fe3e3..5921e1e4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,6 +1,6 @@ name: ci -on: [ push, pull_request, release ] +on: [push, pull_request, release] jobs: code_quality: @@ -12,11 +12,11 @@ jobs: - name: Setup Deno uses: denoland/setup-deno@v1 with: - deno-version: v1.x - + deno-version: v2.x + - name: Format run: deno fmt --check - + - name: Lint run: deno lint @@ -30,16 +30,16 @@ jobs: uses: actions/checkout@master - name: Build tests container - run: docker-compose build tests - + run: docker compose build tests + - name: Run tests - run: docker-compose run tests + run: docker compose run tests - name: Run tests without typechecking id: no_typecheck uses: mathiasvr/command-output@v2.0.0 with: - run: docker-compose run no_check_tests + run: docker compose run no_check_tests continue-on-error: true - name: Report no typechecking tests status @@ -51,7 +51,7 @@ jobs: no_typecheck_status: ${{ steps.no_typecheck_status.outputs.status }} report_warnings: - needs: [ code_quality, test ] + needs: [code_quality, test] runs-on: ubuntu-latest steps: - name: Set no-typecheck fail comment @@ -70,4 +70,4 @@ jobs:

             ${{ needs.test.outputs.no_typecheck }}
               
- \ No newline at end of file + diff --git a/.github/workflows/publish_jsr.yml b/.github/workflows/publish_jsr.yml index 50285a66..d98bad65 100644 --- a/.github/workflows/publish_jsr.yml +++ b/.github/workflows/publish_jsr.yml @@ -22,16 +22,15 @@ jobs: - name: Set up Deno uses: denoland/setup-deno@v1 - + with: + deno-version: v2.x + - name: Check Format run: deno fmt --check - - name: Convert to JSR package - run: deno run -A tools/convert_to_jsr.ts - - - name: Format converted code + - name: Format run: deno fmt - + - name: Lint run: deno lint @@ -39,10 +38,10 @@ jobs: run: deno test --doc client.ts mod.ts pool.ts client/ connection/ query/ utils/ - name: Build tests container - run: docker-compose build tests - + run: docker compose build tests + - name: Run tests - run: docker-compose run tests + run: docker compose run tests - name: Publish (dry run) if: startsWith(github.ref, 'refs/tags/') == false diff --git a/Dockerfile b/Dockerfile index c3bcd7c1..a94a5e06 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM denoland/deno:alpine-1.40.3 +FROM denoland/deno:alpine-2.1.4 WORKDIR /app # Install wait utility @@ -11,7 +11,6 @@ USER deno # Cache external libraries # Test deps caches all main dependencies as well COPY tests/test_deps.ts tests/test_deps.ts -COPY deps.ts deps.ts RUN deno cache tests/test_deps.ts ADD . . diff --git a/README.md b/README.md index e480c2e1..aeb63820 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # deno-postgres -![Build Status](https://img.shields.io/github/workflow/status/denodrivers/postgres/ci?label=Build&logo=github&style=flat-square) +![Build Status](https://img.shields.io/github/actions/workflow/status/denodrivers/postgres/ci.yml?branch=main&label=Build&logo=github&style=flat-square) [![Discord server](https://img.shields.io/discord/768918486575480863?color=blue&label=Ask%20for%20help%20here&logo=discord&style=flat-square)](https://discord.gg/HEdTCvZUSf) [![Manual](https://img.shields.io/github/v/release/denodrivers/postgres?color=orange&label=Manual&logo=deno&style=flat-square)](https://deno-postgres.com) [![Documentation](https://img.shields.io/github/v/release/denodrivers/postgres?color=yellow&label=Documentation&logo=deno&style=flat-square)](https://doc.deno.land/https/deno.land/x/postgres/mod.ts) @@ -86,8 +86,8 @@ result assertions. To run the tests, run the following commands: -1. `docker-compose build tests` -2. `docker-compose run tests` +1. `docker compose build tests` +2. `docker compose run tests` The build step will check linting and formatting as well and report it to the command line diff --git a/client/error.ts b/client/error.ts index 7fc4cccd..fa759980 100644 --- a/client/error.ts +++ b/client/error.ts @@ -1,4 +1,4 @@ -import { type Notice } from "../connection/message.ts"; +import type { Notice } from "../connection/message.ts"; /** * A connection error @@ -20,7 +20,7 @@ export class ConnectionParamsError extends Error { /** * Create a new ConnectionParamsError */ - constructor(message: string, cause?: Error) { + constructor(message: string, cause?: unknown) { super(message, { cause }); this.name = "ConnectionParamsError"; } diff --git a/connection/auth.ts b/connection/auth.ts index c32e7b88..e77b8830 100644 --- a/connection/auth.ts +++ b/connection/auth.ts @@ -1,9 +1,10 @@ -import { crypto, hex } from "../deps.ts"; +import { crypto } from "@std/crypto/crypto"; +import { encodeHex } from "@std/encoding/hex"; const encoder = new TextEncoder(); async function md5(bytes: Uint8Array): Promise { - return hex.encodeHex(await crypto.subtle.digest("MD5", bytes)); + return encodeHex(await crypto.subtle.digest("MD5", bytes)); } // AuthenticationMD5Password diff --git a/connection/connection.ts b/connection/connection.ts index 7ce3d38d..e985da0b 100644 --- a/connection/connection.ts +++ b/connection/connection.ts @@ -26,15 +26,9 @@ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import { - bold, - BufReader, - BufWriter, - delay, - joinPath, - rgb24, - yellow, -} from "../deps.ts"; +import { join as joinPath } from "@std/path"; +import { bold, rgb24, yellow } from "@std/fmt/colors"; +import { delay } from "@std/async/delay"; import { DeferredStack } from "../utils/deferred.ts"; import { getSocketName, readUInt32BE } from "../utils/utils.ts"; import { PacketWriter } from "./packet.ts"; @@ -54,7 +48,7 @@ import { type QueryResult, ResultType, } from "../query/query.ts"; -import { type ClientConfiguration } from "./connection_params.ts"; +import type { ClientConfiguration } from "./connection_params.ts"; import * as scram from "./scram.ts"; import { ConnectionError, @@ -127,8 +121,6 @@ const encoder = new TextEncoder(); // - Refactor properties to not be lazily initialized // or to handle their undefined value export class Connection { - #bufReader!: BufReader; - #bufWriter!: BufWriter; #conn!: Deno.Conn; connected = false; #connection_params: ClientConfiguration; @@ -142,6 +134,7 @@ export class Connection { #secretKey?: number; #tls?: boolean; #transport?: "tcp" | "socket"; + #connWritable!: WritableStreamDefaultWriter; get pid(): number | undefined { return this.#pid; @@ -165,13 +158,32 @@ export class Connection { this.#onDisconnection = disconnection_callback; } + /** + * Read p.length bytes into the buffer + */ + async #readFull(p: Uint8Array): Promise { + let bytes_read = 0; + while (bytes_read < p.length) { + const read_result = await this.#conn.read(p.subarray(bytes_read)); + if (read_result === null) { + if (bytes_read === 0) { + return; + } else { + throw new ConnectionError("Failed to read bytes from socket"); + } + } + bytes_read += read_result; + } + } + /** * Read single message sent by backend */ async #readMessage(): Promise { // Clear buffer before reading the message type this.#message_header.fill(0); - await this.#bufReader.readFull(this.#message_header); + await this.#readFull(this.#message_header); + const type = decoder.decode(this.#message_header.slice(0, 1)); // TODO // Investigate if the ascii terminator is the best way to check for a broken @@ -187,7 +199,7 @@ export class Connection { } const length = readUInt32BE(this.#message_header, 1) - 4; const body = new Uint8Array(length); - await this.#bufReader.readFull(body); + await this.#readFull(body); return new Message(type, length, body); } @@ -197,8 +209,7 @@ export class Connection { writer.clear(); writer.addInt32(8).addInt32(80877103).join(); - await this.#bufWriter.write(writer.flush()); - await this.#bufWriter.flush(); + await this.#connWritable.write(writer.flush()); const response = new Uint8Array(1); await this.#conn.read(response); @@ -254,8 +265,7 @@ export class Connection { const finalBuffer = writer.addInt32(bodyLength).add(bodyBuffer).join(); - await this.#bufWriter.write(finalBuffer); - await this.#bufWriter.flush(); + await this.#connWritable.write(finalBuffer); return await this.#readMessage(); } @@ -264,8 +274,7 @@ export class Connection { // @ts-expect-error This will throw in runtime if the options passed to it are socket related and deno is running // on stable this.#conn = await Deno.connect(options); - this.#bufWriter = new BufWriter(this.#conn); - this.#bufReader = new BufReader(this.#conn); + this.#connWritable = this.#conn.writable.getWriter(); } async #openSocketConnection(path: string, port: number) { @@ -295,12 +304,11 @@ export class Connection { } async #openTlsConnection( - connection: Deno.Conn, + connection: Deno.TcpConn, options: { hostname: string; caCerts: string[] }, ) { this.#conn = await Deno.startTls(connection, options); - this.#bufWriter = new BufWriter(this.#conn); - this.#bufReader = new BufReader(this.#conn); + this.#connWritable = this.#conn.writable.getWriter(); } #resetConnectionMetadata() { @@ -338,7 +346,7 @@ export class Connection { this.#tls = undefined; this.#transport = "socket"; } else { - // A BufWriter needs to be available in order to check if the server accepts TLS connections + // A writer needs to be available in order to check if the server accepts TLS connections await this.#openConnection({ hostname, port, transport: "tcp" }); this.#tls = false; this.#transport = "tcp"; @@ -354,7 +362,7 @@ export class Connection { // https://www.postgresql.org/docs/14/protocol-flow.html#id-1.10.5.7.11 if (accepts_tls) { try { - await this.#openTlsConnection(this.#conn, { + await this.#openTlsConnection(this.#conn as Deno.TcpConn, { hostname, caCerts: caCertificates, }); @@ -363,7 +371,7 @@ export class Connection { if (!tls_enforced) { console.error( bold(yellow("TLS connection failed with message: ")) + - e.message + + (e as Error).message + "\n" + bold("Defaulting to non-encrypted connection"), ); @@ -393,7 +401,8 @@ export class Connection { if (e instanceof Deno.errors.InvalidData && tls_enabled) { if (tls_enforced) { throw new Error( - "The certificate used to secure the TLS connection is invalid.", + "The certificate used to secure the TLS connection is invalid: " + + e.message, ); } else { console.error( @@ -463,7 +472,7 @@ export class Connection { let reconnection_attempts = 0; const max_reconnections = this.#connection_params.connection.attempts; - let error: Error | undefined; + let error: unknown | undefined; // If no connection has been established and the reconnection attempts are // set to zero, attempt to connect at least once if (!is_reconnection && this.#connection_params.connection.attempts === 0) { @@ -561,8 +570,7 @@ export class Connection { const password = this.#connection_params.password || ""; const buffer = this.#packetWriter.addCString(password).flush(0x70); - await this.#bufWriter.write(buffer); - await this.#bufWriter.flush(); + await this.#connWritable.write(buffer); return this.#readMessage(); } @@ -583,8 +591,7 @@ export class Connection { ); const buffer = this.#packetWriter.addCString(password).flush(0x70); - await this.#bufWriter.write(buffer); - await this.#bufWriter.flush(); + await this.#connWritable.write(buffer); return this.#readMessage(); } @@ -611,8 +618,7 @@ export class Connection { this.#packetWriter.addCString("SCRAM-SHA-256"); this.#packetWriter.addInt32(clientFirstMessage.length); this.#packetWriter.addString(clientFirstMessage); - this.#bufWriter.write(this.#packetWriter.flush(0x70)); - this.#bufWriter.flush(); + this.#connWritable.write(this.#packetWriter.flush(0x70)); const maybe_sasl_continue = await this.#readMessage(); switch (maybe_sasl_continue.type) { @@ -639,8 +645,7 @@ export class Connection { this.#packetWriter.clear(); this.#packetWriter.addString(await client.composeResponse()); - this.#bufWriter.write(this.#packetWriter.flush(0x70)); - this.#bufWriter.flush(); + this.#connWritable.write(this.#packetWriter.flush(0x70)); const maybe_sasl_final = await this.#readMessage(); switch (maybe_sasl_final.type) { @@ -676,8 +681,7 @@ export class Connection { const buffer = this.#packetWriter.addCString(query.text).flush(0x51); - await this.#bufWriter.write(buffer); - await this.#bufWriter.flush(); + await this.#connWritable.write(buffer); let result; if (query.result_type === ResultType.ARRAY) { @@ -686,7 +690,7 @@ export class Connection { result = new QueryObjectResult(query); } - let error: Error | undefined; + let error: unknown | undefined; let current_message = await this.#readMessage(); // Process messages until ready signal is sent @@ -766,7 +770,7 @@ export class Connection { .addCString(query.text) .addInt16(0) .flush(0x50); - await this.#bufWriter.write(buffer); + await this.#connWritable.write(buffer); } async #appendArgumentsToMessage(query: Query) { @@ -783,16 +787,16 @@ export class Connection { if (hasBinaryArgs) { this.#packetWriter.addInt16(query.args.length); - query.args.forEach((arg) => { + for (const arg of query.args) { this.#packetWriter.addInt16(arg instanceof Uint8Array ? 1 : 0); - }); + } } else { this.#packetWriter.addInt16(0); } this.#packetWriter.addInt16(query.args.length); - query.args.forEach((arg) => { + for (const arg of query.args) { if (arg === null || typeof arg === "undefined") { this.#packetWriter.addInt32(-1); } else if (arg instanceof Uint8Array) { @@ -803,11 +807,11 @@ export class Connection { this.#packetWriter.addInt32(byteLength); this.#packetWriter.addString(arg); } - }); + } this.#packetWriter.addInt16(0); const buffer = this.#packetWriter.flush(0x42); - await this.#bufWriter.write(buffer); + await this.#connWritable.write(buffer); } /** @@ -818,7 +822,7 @@ export class Connection { this.#packetWriter.clear(); const buffer = this.#packetWriter.addCString("P").flush(0x44); - await this.#bufWriter.write(buffer); + await this.#connWritable.write(buffer); } async #appendExecuteToMessage() { @@ -828,14 +832,14 @@ export class Connection { .addCString("") // unnamed portal .addInt32(0) .flush(0x45); - await this.#bufWriter.write(buffer); + await this.#connWritable.write(buffer); } async #appendSyncToMessage() { this.#packetWriter.clear(); const buffer = this.#packetWriter.flush(0x53); - await this.#bufWriter.write(buffer); + await this.#connWritable.write(buffer); } // TODO @@ -873,8 +877,6 @@ export class Connection { // The execute response contains the portal in which the query will be run and how many rows should it return await this.#appendExecuteToMessage(); await this.#appendSyncToMessage(); - // send all messages to backend - await this.#bufWriter.flush(); let result; if (query.result_type === ResultType.ARRAY) { @@ -883,7 +885,7 @@ export class Connection { result = new QueryObjectResult(query); } - let error: Error | undefined; + let error: unknown | undefined; let current_message = await this.#readMessage(); while (current_message.type !== INCOMING_QUERY_MESSAGES.READY) { @@ -997,9 +999,9 @@ export class Connection { async end(): Promise { if (this.connected) { const terminationMessage = new Uint8Array([0x58, 0x00, 0x00, 0x00, 0x04]); - await this.#bufWriter.write(terminationMessage); + await this.#connWritable.write(terminationMessage); try { - await this.#bufWriter.flush(); + await this.#connWritable.ready; } catch (_e) { // This steps can fail if the underlying connection was closed ungracefully } finally { diff --git a/connection/connection_params.ts b/connection/connection_params.ts index ac4f650e..0d718c9a 100644 --- a/connection/connection_params.ts +++ b/connection/connection_params.ts @@ -1,9 +1,9 @@ import { parseConnectionUri } from "../utils/utils.ts"; import { ConnectionParamsError } from "../client/error.ts"; -import { fromFileUrl, isAbsolute } from "../deps.ts"; -import { OidType } from "../query/oid.ts"; -import { DebugControls } from "../debug.ts"; -import { ParseArrayFunction } from "../query/array_parser.ts"; +import { fromFileUrl, isAbsolute } from "@std/path"; +import type { OidType } from "../query/oid.ts"; +import type { DebugControls } from "../debug.ts"; +import type { ParseArrayFunction } from "../query/array_parser.ts"; /** * The connection string must match the following URI structure. All parameters but database and user are optional diff --git a/connection/packet.ts b/connection/packet.ts index 36abae18..2d93f695 100644 --- a/connection/packet.ts +++ b/connection/packet.ts @@ -25,7 +25,7 @@ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import { copy } from "../deps.ts"; +import { copy } from "@std/bytes/copy"; import { readInt16BE, readInt32BE } from "../utils/utils.ts"; export class PacketReader { diff --git a/connection/scram.ts b/connection/scram.ts index 1ef2661e..e4e18c32 100644 --- a/connection/scram.ts +++ b/connection/scram.ts @@ -1,4 +1,4 @@ -import { base64 } from "../deps.ts"; +import { decodeBase64, encodeBase64 } from "@std/encoding/base64"; /** Number of random bytes used to generate a nonce */ const defaultNonceSize = 16; @@ -132,7 +132,7 @@ function escape(str: string): string { } function generateRandomNonce(size: number): string { - return base64.encodeBase64(crypto.getRandomValues(new Uint8Array(size))); + return encodeBase64(crypto.getRandomValues(new Uint8Array(size))); } function parseScramAttributes(message: string): Record { @@ -144,10 +144,8 @@ function parseScramAttributes(message: string): Record { throw new Error(Reason.BadMessage); } - // TODO - // Replace with String.prototype.substring - const key = entry.substr(0, pos); - const value = entry.substr(pos + 1); + const key = entry.substring(0, pos); + const value = entry.slice(pos + 1); attrs[key] = value; } @@ -221,7 +219,7 @@ export class Client { throw new Error(Reason.BadSalt); } try { - salt = base64.decodeBase64(attrs.s); + salt = decodeBase64(attrs.s); } catch { throw new Error(Reason.BadSalt); } @@ -261,7 +259,7 @@ export class Client { this.#auth_message += "," + responseWithoutProof; - const proof = base64.encodeBase64( + const proof = encodeBase64( computeScramProof( await computeScramSignature( this.#auth_message, @@ -294,7 +292,7 @@ export class Client { throw new Error(attrs.e ?? Reason.Rejected); } - const verifier = base64.encodeBase64( + const verifier = encodeBase64( await computeScramSignature( this.#auth_message, this.#key_signatures.server, diff --git a/deno.json b/deno.json index f4697e7c..49a4d61b 100644 --- a/deno.json +++ b/deno.json @@ -1,6 +1,14 @@ { - "lock": false, - "name": "@bartlomieju/postgres", - "version": "0.19.3", - "exports": "./mod.ts" + "name": "@db/postgres", + "version": "0.19.4", + "exports": "./mod.ts", + "imports": { + "@std/async": "jsr:@std/async@^1.0.9", + "@std/bytes": "jsr:@std/bytes@^1.0.4", + "@std/crypto": "jsr:@std/crypto@^1.0.3", + "@std/encoding": "jsr:@std/encoding@^1.0.6", + "@std/fmt": "jsr:@std/fmt@^1.0.3", + "@std/path": "jsr:@std/path@^1.0.8" + }, + "lock": false } diff --git a/deps.ts b/deps.ts deleted file mode 100644 index 3d10e31c..00000000 --- a/deps.ts +++ /dev/null @@ -1,18 +0,0 @@ -export * as base64 from "https://deno.land/std@0.214.0/encoding/base64.ts"; -export * as hex from "https://deno.land/std@0.214.0/encoding/hex.ts"; -export { parse as parseDate } from "https://deno.land/std@0.214.0/datetime/parse.ts"; -export { BufReader } from "https://deno.land/std@0.214.0/io/buf_reader.ts"; -export { BufWriter } from "https://deno.land/std@0.214.0/io/buf_writer.ts"; -export { copy } from "https://deno.land/std@0.214.0/bytes/copy.ts"; -export { crypto } from "https://deno.land/std@0.214.0/crypto/crypto.ts"; -export { delay } from "https://deno.land/std@0.214.0/async/delay.ts"; -export { - bold, - rgb24, - yellow, -} from "https://deno.land/std@0.214.0/fmt/colors.ts"; -export { - fromFileUrl, - isAbsolute, - join as joinPath, -} from "https://deno.land/std@0.214.0/path/mod.ts"; diff --git a/docker-compose.yml b/docker-compose.yml index be919039..e49dc016 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,5 +1,3 @@ -version: '3.8' - x-database-env: &database-env POSTGRES_DB: "postgres" diff --git a/docs/index.html b/docs/index.html index 4ce33e9f..89c6b93a 100644 --- a/docs/index.html +++ b/docs/index.html @@ -1,22 +1,30 @@ - - - Deno Postgres - - - - - - -
- - - - + + + Deno Postgres + + + + + + +
+ + + + diff --git a/query/array_parser.ts b/query/array_parser.ts index 60e27a25..8ca9175f 100644 --- a/query/array_parser.ts +++ b/query/array_parser.ts @@ -89,7 +89,7 @@ class ArrayParser { this.dimension++; if (this.dimension > 1) { parser = new ArrayParser( - this.source.substr(this.position - 1), + this.source.substring(this.position - 1), this.transform, this.separator, ); diff --git a/query/decode.ts b/query/decode.ts index 38df157e..0543e86f 100644 --- a/query/decode.ts +++ b/query/decode.ts @@ -1,5 +1,5 @@ -import { Oid, OidType, OidTypes, OidValue } from "./oid.ts"; -import { bold, yellow } from "../deps.ts"; +import { Oid, type OidType, OidTypes, type OidValue } from "./oid.ts"; +import { bold, yellow } from "@std/fmt/colors"; import { decodeBigint, decodeBigintArray, @@ -35,7 +35,7 @@ import { decodeTid, decodeTidArray, } from "./decoders.ts"; -import { ClientControls } from "../connection/connection_params.ts"; +import type { ClientControls } from "../connection/connection_params.ts"; import { parseArray } from "./array_parser.ts"; export class Column { @@ -199,7 +199,7 @@ function decodeText(value: string, typeOid: number) { } catch (_e) { console.error( bold(yellow(`Error decoding type Oid ${typeOid} value`)) + - _e.message + + (_e as Error).message + "\n" + bold("Defaulting to null."), ); diff --git a/query/decoders.ts b/query/decoders.ts index 4edbb03a..434ecd7c 100644 --- a/query/decoders.ts +++ b/query/decoders.ts @@ -1,4 +1,3 @@ -import { parseDate } from "../deps.ts"; import { parseArray } from "./array_parser.ts"; import type { Box, @@ -64,7 +63,7 @@ export function decodeBox(value: string): Box { b: decodePoint(b), }; } catch (e) { - throw new Error(`Invalid Box: "${value}" : ${e.message}`); + throw new Error(`Invalid Box: "${value}" : ${(e as Error).message}`); } } @@ -93,8 +92,8 @@ function decodeByteaEscape(byteaStr: string): Uint8Array { bytes.push(byteaStr.charCodeAt(i)); ++i; } else { - if (/[0-7]{3}/.test(byteaStr.substr(i + 1, 3))) { - bytes.push(parseInt(byteaStr.substr(i + 1, 3), 8)); + if (/[0-7]{3}/.test(byteaStr.substring(i + 1, i + 4))) { + bytes.push(parseInt(byteaStr.substring(i + 1, i + 4), 8)); i += 4; } else { let backslashes = 1; @@ -140,7 +139,7 @@ export function decodeCircle(value: string): Circle { radius: radius, }; } catch (e) { - throw new Error(`Invalid Circle: "${value}" : ${e.message}`); + throw new Error(`Invalid Circle: "${value}" : ${(e as Error).message}`); } } @@ -157,7 +156,7 @@ export function decodeDate(dateStr: string): Date | number { return Number(-Infinity); } - return parseDate(dateStr, "yyyy-MM-dd"); + return new Date(dateStr); } export function decodeDateArray(value: string) { @@ -249,13 +248,13 @@ export function decodeLine(value: string): Line { ); } - equationConsts.forEach((c) => { + for (const c of equationConsts) { if (Number.isNaN(parseFloat(c))) { throw new Error( `Invalid Line: "${value}". Line constant "${c}" must be a valid number.`, ); } - }); + } const [a, b, c] = equationConsts; @@ -287,7 +286,9 @@ export function decodeLineSegment(value: string): LineSegment { b: decodePoint(b), }; } catch (e) { - throw new Error(`Invalid Line Segment: "${value}" : ${e.message}`); + throw new Error( + `Invalid Line Segment: "${value}" : ${(e as Error).message}`, + ); } } @@ -304,7 +305,7 @@ export function decodePath(value: string): Path { try { return decodePoint(point); } catch (e) { - throw new Error(`Invalid Path: "${value}" : ${e.message}`); + throw new Error(`Invalid Path: "${value}" : ${(e as Error).message}`); } }); } @@ -348,7 +349,7 @@ export function decodePolygon(value: string): Polygon { try { return decodePath(value); } catch (e) { - throw new Error(`Invalid Polygon: "${value}" : ${e.message}`); + throw new Error(`Invalid Polygon: "${value}" : ${(e as Error).message}`); } } diff --git a/query/encode.ts b/query/encode.ts index 36407bf2..94cf2b60 100644 --- a/query/encode.ts +++ b/query/encode.ts @@ -50,24 +50,23 @@ function escapeArrayElement(value: unknown): string { function encodeArray(array: Array): string { let encodedArray = "{"; - array.forEach((element, index) => { + for (let index = 0; index < array.length; index++) { if (index > 0) { encodedArray += ","; } + const element = array[index]; if (element === null || typeof element === "undefined") { encodedArray += "NULL"; } else if (Array.isArray(element)) { encodedArray += encodeArray(element); } else if (element instanceof Uint8Array) { - // TODO - // Should it be encoded as bytea? - throw new Error("Can't encode array of buffers."); + encodedArray += encodeBytes(element); } else { const encodedElement = encodeArgument(element); encodedArray += escapeArrayElement(encodedElement as string); } - }); + } encodedArray += "}"; return encodedArray; @@ -91,15 +90,18 @@ export type EncodedArg = null | string | Uint8Array; export function encodeArgument(value: unknown): EncodedArg { if (value === null || typeof value === "undefined") { return null; - } else if (value instanceof Uint8Array) { + } + if (value instanceof Uint8Array) { return encodeBytes(value); - } else if (value instanceof Date) { + } + if (value instanceof Date) { return encodeDate(value); - } else if (value instanceof Array) { + } + if (value instanceof Array) { return encodeArray(value); - } else if (value instanceof Object) { + } + if (value instanceof Object) { return JSON.stringify(value); - } else { - return String(value); } + return String(value); } diff --git a/query/query.ts b/query/query.ts index 58977459..3c2d888e 100644 --- a/query/query.ts +++ b/query/query.ts @@ -1,7 +1,7 @@ import { encodeArgument, type EncodedArg } from "./encode.ts"; import { type Column, decode } from "./decode.ts"; -import { type Notice } from "../connection/message.ts"; -import { type ClientControls } from "../connection/connection_params.ts"; +import type { Notice } from "../connection/message.ts"; +import type { ClientControls } from "../connection/connection_params.ts"; // TODO // Limit the type of parameters that can be passed @@ -38,7 +38,8 @@ export type CommandType = | "SELECT" | "MOVE" | "FETCH" - | "COPY"; + | "COPY" + | "CREATE"; /** Type of a query result */ export enum ResultType { @@ -154,7 +155,7 @@ export interface QueryObjectOptions extends QueryOptions { /** * This class is used to handle the result of a query */ -export class QueryResult { +export abstract class QueryResult { /** * Type of query executed for this result */ @@ -224,9 +225,7 @@ export class QueryResult { * * This function can throw on validation, so any errors must be handled in the message loop accordingly */ - insertRow(_row: Uint8Array[]): void { - throw new Error("No implementation for insertRow is defined"); - } + abstract insertRow(_row: Uint8Array[]): void; } /** diff --git a/query/transaction.ts b/query/transaction.ts index 3dadd33a..233ea487 100644 --- a/query/transaction.ts +++ b/query/transaction.ts @@ -1,4 +1,4 @@ -import { type QueryClient } from "../client.ts"; +import type { QueryClient } from "../client.ts"; import { Query, type QueryArguments, @@ -257,9 +257,8 @@ export class Transaction { } catch (e) { if (e instanceof PostgresError) { throw new TransactionError(this.name, e); - } else { - throw e; } + throw e; } this.#updateClientLock(this.name); @@ -312,9 +311,8 @@ export class Transaction { } catch (e) { if (e instanceof PostgresError) { throw new TransactionError(this.name, e); - } else { - throw e; } + throw e; } } @@ -467,9 +465,8 @@ export class Transaction { if (e instanceof PostgresError) { await this.commit(); throw new TransactionError(this.name, e); - } else { - throw e; } + throw e; } } @@ -565,9 +562,8 @@ export class Transaction { if (e instanceof PostgresError) { await this.commit(); throw new TransactionError(this.name, e); - } else { - throw e; } + throw e; } } @@ -701,9 +697,8 @@ export class Transaction { if (e instanceof PostgresError) { await this.commit(); throw new TransactionError(this.name, e); - } else { - throw e; } + throw e; } this.#resetTransaction(); @@ -792,9 +787,8 @@ export class Transaction { if (e instanceof PostgresError) { await this.commit(); throw new TransactionError(this.name, e); - } else { - throw e; } + throw e; } } else { savepoint = new Savepoint( @@ -813,9 +807,8 @@ export class Transaction { if (e instanceof PostgresError) { await this.commit(); throw new TransactionError(this.name, e); - } else { - throw e; } + throw e; } this.#savepoints.push(savepoint); } diff --git a/tests/README.md b/tests/README.md index c8c3e4e9..38cc8c41 100644 --- a/tests/README.md +++ b/tests/README.md @@ -16,8 +16,8 @@ From within the project directory, run: deno test --allow-read --allow-net --allow-env # run in docker container -docker-compose build --no-cache -docker-compose run tests +docker compose build --no-cache +docker compose run tests ``` ## Docker Configuration diff --git a/tests/auth_test.ts b/tests/auth_test.ts index f7ed38db..4b06120e 100644 --- a/tests/auth_test.ts +++ b/tests/auth_test.ts @@ -1,4 +1,8 @@ -import { assertEquals, assertNotEquals, assertRejects } from "./test_deps.ts"; +import { + assertEquals, + assertNotEquals, + assertRejects, +} from "jsr:@std/assert@1.0.10"; import { Client as ScramClient, Reason } from "../connection/scram.ts"; Deno.test("Scram client reproduces RFC 7677 example", async () => { diff --git a/tests/config.ts b/tests/config.ts index 17bf701c..4a6784cf 100644 --- a/tests/config.ts +++ b/tests/config.ts @@ -1,4 +1,4 @@ -import { +import type { ClientConfiguration, ClientOptions, } from "../connection/connection_params.ts"; diff --git a/tests/connection_params_test.ts b/tests/connection_params_test.ts index d5138784..94df4338 100644 --- a/tests/connection_params_test.ts +++ b/tests/connection_params_test.ts @@ -1,4 +1,5 @@ -import { assertEquals, assertThrows, fromFileUrl } from "./test_deps.ts"; +import { assertEquals, assertThrows } from "jsr:@std/assert@1.0.10"; +import { fromFileUrl } from "@std/path"; import { createParams } from "../connection/connection_params.ts"; import { ConnectionParamsError } from "../client/error.ts"; diff --git a/tests/connection_test.ts b/tests/connection_test.ts index 5cc85539..5b94d275 100644 --- a/tests/connection_test.ts +++ b/tests/connection_test.ts @@ -1,9 +1,5 @@ -import { - assertEquals, - assertRejects, - copyStream, - joinPath, -} from "./test_deps.ts"; +import { assertEquals, assertRejects } from "jsr:@std/assert@1.0.10"; +import { join as joinPath } from "@std/path"; import { getClearConfiguration, getClearSocketConfiguration, @@ -36,10 +32,8 @@ function createProxy( outbound.close(); aborted = true; }); - await Promise.all([ - copyStream(conn, outbound), - copyStream(outbound, conn), - ]).catch(() => {}); + await conn.readable.pipeTo(outbound.writable, { preventClose: true }); + await outbound.readable.pipeTo(conn.writable, { preventClose: true }); if (!aborted) { conn.close(); @@ -186,6 +180,7 @@ Deno.test("Skips TLS connection when TLS disabled", async () => { } }); +// TODO(jersey): fix the error on this test Deno.test("Aborts TLS connection when certificate is untrusted", async () => { // Force TLS but don't provide CA const client = new Client({ @@ -214,6 +209,7 @@ Deno.test("Aborts TLS connection when certificate is untrusted", async () => { } }); +// TODO(jersey): this should not fail or reject Deno.test("Defaults to unencrypted when certificate is invalid and TLS is not enforced", async () => { // Remove CA, request tls and disable enforce const client = new Client({ @@ -399,7 +395,7 @@ Deno.test("Closes connection on bad TLS availability verification", async functi await client.connect(); } catch (e) { if ( - e instanceof Error || + e instanceof Error && e.message.startsWith("Could not check if server accepts SSL connections") ) { bad_tls_availability_message = true; @@ -564,6 +560,7 @@ Deno.test("Attempts reconnection on disconnection", async function () { } }); +// TODO(jersey): fix error so that it's a connectionerror Deno.test("Attempts reconnection on socket disconnection", async () => { const client = new Client(getMd5SocketConfiguration()); await client.connect(); diff --git a/tests/data_types_test.ts b/tests/data_types_test.ts index d4d56103..1dc1c463 100644 --- a/tests/data_types_test.ts +++ b/tests/data_types_test.ts @@ -1,4 +1,5 @@ -import { assertEquals, base64, formatDate, parseDate } from "./test_deps.ts"; +import { assertEquals } from "jsr:@std/assert@1.0.10"; +import { decodeBase64, encodeBase64 } from "@std/encoding/base64"; import { getMainConfiguration } from "./config.ts"; import { generateSimpleClientTest } from "./helpers.ts"; import type { @@ -34,7 +35,7 @@ function generateRandomPoint(max_value = 100): Point { const CHARS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; function randomBase64(): string { - return base64.encodeBase64( + return encodeBase64( Array.from( { length: Math.ceil(Math.random() * 256) }, () => CHARS[Math.floor(Math.random() * CHARS.length)], @@ -671,7 +672,7 @@ Deno.test( `SELECT decode('${base64_string}','base64')`, ); - assertEquals(result.rows[0][0], base64.decodeBase64(base64_string)); + assertEquals(result.rows[0][0], decodeBase64(base64_string)); }), ); @@ -691,7 +692,7 @@ Deno.test( assertEquals( result.rows[0][0], - strings.map(base64.decodeBase64), + strings.map(decodeBase64), ); }), ); @@ -931,7 +932,7 @@ Deno.test( ); assertEquals(result.rows[0], [ - parseDate(date_text, "yyyy-MM-dd"), + new Date(date_text), Infinity, ]); }), @@ -941,7 +942,7 @@ Deno.test( "date array", testClient(async (client) => { await client.queryArray(`SET SESSION TIMEZONE TO '${timezone}'`); - const dates = ["2020-01-01", formatDate(new Date(), "yyyy-MM-dd")]; + const dates = ["2020-01-01", (new Date()).toISOString().split("T")[0]]; const { rows: result } = await client.queryArray<[[Date, Date]]>( "SELECT ARRAY[$1::DATE, $2]", @@ -950,7 +951,7 @@ Deno.test( assertEquals( result[0][0], - dates.map((d) => parseDate(d, "yyyy-MM-dd")), + dates.map((d) => new Date(d)), ); }), ); diff --git a/tests/decode_test.ts b/tests/decode_test.ts index 06512911..b2f0657f 100644 --- a/tests/decode_test.ts +++ b/tests/decode_test.ts @@ -17,7 +17,7 @@ import { decodePoint, decodeTid, } from "../query/decoders.ts"; -import { assertEquals, assertThrows } from "./test_deps.ts"; +import { assertEquals, assertThrows } from "jsr:@std/assert@1.0.10"; import { Oid } from "../query/oid.ts"; Deno.test("decodeBigint", function () { diff --git a/tests/encode_test.ts b/tests/encode_test.ts index 784fdaab..eab21868 100644 --- a/tests/encode_test.ts +++ b/tests/encode_test.ts @@ -1,4 +1,4 @@ -import { assertEquals } from "./test_deps.ts"; +import { assertEquals } from "jsr:@std/assert@1.0.10"; import { encodeArgument } from "../query/encode.ts"; // internally `encodeArguments` uses `getTimezoneOffset` to encode Date diff --git a/tests/helpers.ts b/tests/helpers.ts index d1630d3e..e26a7f27 100644 --- a/tests/helpers.ts +++ b/tests/helpers.ts @@ -1,6 +1,6 @@ import { Client } from "../client.ts"; import { Pool } from "../pool.ts"; -import { type ClientOptions } from "../connection/connection_params.ts"; +import type { ClientOptions } from "../connection/connection_params.ts"; export function generateSimpleClientTest( client_options: ClientOptions, diff --git a/tests/pool_test.ts b/tests/pool_test.ts index c8ecac91..d5a99818 100644 --- a/tests/pool_test.ts +++ b/tests/pool_test.ts @@ -1,4 +1,5 @@ -import { assertEquals, delay } from "./test_deps.ts"; +import { assertEquals } from "jsr:@std/assert@1.0.10"; +import { delay } from "@std/async/delay"; import { getMainConfiguration } from "./config.ts"; import { generatePoolClientTest } from "./helpers.ts"; diff --git a/tests/query_client_test.ts b/tests/query_client_test.ts index c096049a..26966de4 100644 --- a/tests/query_client_test.ts +++ b/tests/query_client_test.ts @@ -12,10 +12,10 @@ import { assertObjectMatch, assertRejects, assertThrows, -} from "./test_deps.ts"; +} from "jsr:@std/assert@1.0.10"; import { getMainConfiguration } from "./config.ts"; -import { PoolClient, QueryClient } from "../client.ts"; -import { ClientOptions } from "../connection/connection_params.ts"; +import type { PoolClient, QueryClient } from "../client.ts"; +import type { ClientOptions } from "../connection/connection_params.ts"; import { Oid } from "../query/oid.ts"; function withClient( diff --git a/tests/test_deps.ts b/tests/test_deps.ts index 3ec05aaa..cb56ee54 100644 --- a/tests/test_deps.ts +++ b/tests/test_deps.ts @@ -1,4 +1,3 @@ -export * from "../deps.ts"; export { assert, assertEquals, @@ -7,6 +6,4 @@ export { assertObjectMatch, assertRejects, assertThrows, -} from "https://deno.land/std@0.214.0/assert/mod.ts"; -export { format as formatDate } from "https://deno.land/std@0.214.0/datetime/format.ts"; -export { copy as copyStream } from "https://deno.land/std@0.214.0/io/copy.ts"; +} from "jsr:@std/assert@1.0.10"; diff --git a/tests/utils_test.ts b/tests/utils_test.ts index d5e418d3..40542ea7 100644 --- a/tests/utils_test.ts +++ b/tests/utils_test.ts @@ -1,5 +1,5 @@ -import { assertEquals, assertThrows } from "./test_deps.ts"; -import { parseConnectionUri, Uri } from "../utils/utils.ts"; +import { assertEquals, assertThrows } from "jsr:@std/assert@1.0.10"; +import { parseConnectionUri, type Uri } from "../utils/utils.ts"; import { DeferredAccessStack, DeferredStack } from "../utils/deferred.ts"; class LazilyInitializedObject { diff --git a/tools/convert_to_jsr.ts b/tools/convert_to_jsr.ts deleted file mode 100644 index 9843f572..00000000 --- a/tools/convert_to_jsr.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { walk } from "https://deno.land/std@0.214.0/fs/walk.ts"; -import denoConfig from "../deno.json" with { type: "json" }; - -const STD_SPECIFIER_REGEX = - /https:\/\/deno\.land\/std@(\d+\.\d+\.\d+)\/(\w+)\/(.+)\.ts/g; -const POSTGRES_X_SPECIFIER = "https://deno.land/x/postgres/mod.ts"; -const POSTGRES_JSR_SPECIFIER = `jsr:${denoConfig.name}`; - -function toStdJsrSpecifier( - _full: string, - _version: string, - module: string, - path: string, -): string { - /** - * @todo(iuioiua) Restore the dynamic use of the `version` argument - * once 0.214.0 is released. - */ - const version = "0.213.1"; - return path === "mod" - ? `jsr:@std/${module}@${version}` - : `jsr:@std/${module}@${version}/${path}`; -} - -for await ( - const entry of walk(".", { - includeDirs: false, - exts: [".ts", ".md"], - skip: [/docker/, /.github/, /tools/], - followSymlinks: false, - }) -) { - const text = await Deno.readTextFile(entry.path); - const newText = text - .replaceAll(STD_SPECIFIER_REGEX, toStdJsrSpecifier) - .replaceAll(POSTGRES_X_SPECIFIER, POSTGRES_JSR_SPECIFIER); - await Deno.writeTextFile(entry.path, newText); -} diff --git a/utils/deferred.ts b/utils/deferred.ts index f4b4c10a..9d650d90 100644 --- a/utils/deferred.ts +++ b/utils/deferred.ts @@ -22,7 +22,9 @@ export class DeferredStack { async pop(): Promise { if (this.#elements.length > 0) { return this.#elements.pop()!; - } else if (this.#size < this.#max_size && this.#creator) { + } + + if (this.#size < this.#max_size && this.#creator) { this.#size++; return await this.#creator(); } diff --git a/utils/utils.ts b/utils/utils.ts index ae7ccee8..f0280fb7 100644 --- a/utils/utils.ts +++ b/utils/utils.ts @@ -1,4 +1,4 @@ -import { bold, yellow } from "../deps.ts"; +import { bold, yellow } from "@std/fmt/colors"; export function readInt16BE(buffer: Uint8Array, offset: number): number { offset = offset >>> 0; @@ -93,7 +93,7 @@ export function parseConnectionUri(uri: string): Uri { } } catch (_e) { console.error( - bold(yellow("Failed to decode URL host") + "\nDefaulting to raw host"), + bold(`${yellow("Failed to decode URL host")}\nDefaulting to raw host`), ); } @@ -108,8 +108,9 @@ export function parseConnectionUri(uri: string): Uri { } catch (_e) { console.error( bold( - yellow("Failed to decode URL password") + - "\nDefaulting to raw password", + `${ + yellow("Failed to decode URL password") + }\nDefaulting to raw password`, ), ); } From a444c1a46a4272faf479b1c866e4f4e8ec38ef41 Mon Sep 17 00:00:00 2001 From: Jersey Date: Sat, 1 Feb 2025 21:18:58 -0500 Subject: [PATCH 2/3] remove error type assertions --- connection/connection.ts | 2 +- query/decode.ts | 4 ++-- query/decoders.ts | 20 +++++++++++++++----- 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/connection/connection.ts b/connection/connection.ts index 69a9615c..780fa6ca 100644 --- a/connection/connection.ts +++ b/connection/connection.ts @@ -373,7 +373,7 @@ export class Connection { if (!tls_enforced) { console.error( bold(yellow("TLS connection failed with message: ")) + - (e as Error).message + + (e instanceof Error ? e.message : e) + "\n" + bold("Defaulting to non-encrypted connection"), ); diff --git a/query/decode.ts b/query/decode.ts index 0543e86f..c0311910 100644 --- a/query/decode.ts +++ b/query/decode.ts @@ -196,10 +196,10 @@ function decodeText(value: string, typeOid: number) { // them as they see fit return value; } - } catch (_e) { + } catch (e) { console.error( bold(yellow(`Error decoding type Oid ${typeOid} value`)) + - (_e as Error).message + + (e instanceof Error ? e.message : e) + "\n" + bold("Defaulting to null."), ); diff --git a/query/decoders.ts b/query/decoders.ts index 434ecd7c..58356d76 100644 --- a/query/decoders.ts +++ b/query/decoders.ts @@ -63,7 +63,9 @@ export function decodeBox(value: string): Box { b: decodePoint(b), }; } catch (e) { - throw new Error(`Invalid Box: "${value}" : ${(e as Error).message}`); + throw new Error( + `Invalid Box: "${value}" : ${(e instanceof Error ? e.message : e)}`, + ); } } @@ -139,7 +141,9 @@ export function decodeCircle(value: string): Circle { radius: radius, }; } catch (e) { - throw new Error(`Invalid Circle: "${value}" : ${(e as Error).message}`); + throw new Error( + `Invalid Circle: "${value}" : ${(e instanceof Error ? e.message : e)}`, + ); } } @@ -287,7 +291,9 @@ export function decodeLineSegment(value: string): LineSegment { }; } catch (e) { throw new Error( - `Invalid Line Segment: "${value}" : ${(e as Error).message}`, + `Invalid Line Segment: "${value}" : ${(e instanceof Error + ? e.message + : e)}`, ); } } @@ -305,7 +311,9 @@ export function decodePath(value: string): Path { try { return decodePoint(point); } catch (e) { - throw new Error(`Invalid Path: "${value}" : ${(e as Error).message}`); + throw new Error( + `Invalid Path: "${value}" : ${(e instanceof Error ? e.message : e)}`, + ); } }); } @@ -349,7 +357,9 @@ export function decodePolygon(value: string): Polygon { try { return decodePath(value); } catch (e) { - throw new Error(`Invalid Polygon: "${value}" : ${(e as Error).message}`); + throw new Error( + `Invalid Polygon: "${value}" : ${(e instanceof Error ? e.message : e)}`, + ); } } From 4955809760c2e5e5590146f3cbed2ff36f8ba44a Mon Sep 17 00:00:00 2001 From: Jersey Date: Sat, 1 Feb 2025 21:22:22 -0500 Subject: [PATCH 3/3] bump dependency versions --- deno.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/deno.json b/deno.json index 49a4d61b..57336d0f 100644 --- a/deno.json +++ b/deno.json @@ -3,11 +3,11 @@ "version": "0.19.4", "exports": "./mod.ts", "imports": { - "@std/async": "jsr:@std/async@^1.0.9", - "@std/bytes": "jsr:@std/bytes@^1.0.4", - "@std/crypto": "jsr:@std/crypto@^1.0.3", - "@std/encoding": "jsr:@std/encoding@^1.0.6", - "@std/fmt": "jsr:@std/fmt@^1.0.3", + "@std/async": "jsr:@std/async@^1.0.10", + "@std/bytes": "jsr:@std/bytes@^1.0.5", + "@std/crypto": "jsr:@std/crypto@^1.0.4", + "@std/encoding": "jsr:@std/encoding@^1.0.7", + "@std/fmt": "jsr:@std/fmt@^1.0.5", "@std/path": "jsr:@std/path@^1.0.8" }, "lock": false