Skip to content

Commit 0cd109f

Browse files
committed
feate: add encoding strategy control
1 parent c5f5b2d commit 0cd109f

File tree

4 files changed

+105
-67
lines changed

4 files changed

+105
-67
lines changed

connection/connection.ts

Lines changed: 33 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ export class Connection {
139139

140140
constructor(
141141
connection_params: ClientConfiguration,
142-
disconnection_callback: () => Promise<void>,
142+
disconnection_callback: () => Promise<void>
143143
) {
144144
this.#connection_params = connection_params;
145145
this.#onDisconnection = disconnection_callback;
@@ -190,7 +190,7 @@ export class Connection {
190190
return false;
191191
default:
192192
throw new Error(
193-
`Could not check if server accepts SSL connections, server responded with: ${response}`,
193+
`Could not check if server accepts SSL connections, server responded with: ${response}`
194194
);
195195
}
196196
}
@@ -220,7 +220,7 @@ export class Connection {
220220
.addCString(
221221
connection_options
222222
.map(([key, value]) => `--${key}=${value}`)
223-
.join(" "),
223+
.join(" ")
224224
);
225225
}
226226

@@ -266,7 +266,7 @@ export class Connection {
266266
} catch (e) {
267267
if (e instanceof Deno.errors.NotFound) {
268268
throw new ConnectionError(
269-
`Could not open socket in path "${socket_guess}"`,
269+
`Could not open socket in path "${socket_guess}"`
270270
);
271271
}
272272
throw e;
@@ -276,7 +276,7 @@ export class Connection {
276276

277277
async #openTlsConnection(
278278
connection: Deno.Conn,
279-
options: { hostname: string; caCerts: string[] },
279+
options: { hostname: string; caCerts: string[] }
280280
) {
281281
this.#conn = await Deno.startTls(connection, options);
282282
this.#bufWriter = new BufWriter(this.#conn);
@@ -345,7 +345,7 @@ export class Connection {
345345
bold(yellow("TLS connection failed with message: ")) +
346346
e.message +
347347
"\n" +
348-
bold("Defaulting to non-encrypted connection"),
348+
bold("Defaulting to non-encrypted connection")
349349
);
350350
await this.#openConnection({ hostname, port, transport: "tcp" });
351351
this.#tls = false;
@@ -357,7 +357,7 @@ export class Connection {
357357
// Make sure to close the connection before erroring
358358
this.#closeConnection();
359359
throw new Error(
360-
"The server isn't accepting TLS connections. Change the client configuration so TLS configuration isn't required to connect",
360+
"The server isn't accepting TLS connections. Change the client configuration so TLS configuration isn't required to connect"
361361
);
362362
}
363363
}
@@ -373,14 +373,14 @@ export class Connection {
373373
if (e instanceof Deno.errors.InvalidData && tls_enabled) {
374374
if (tls_enforced) {
375375
throw new Error(
376-
"The certificate used to secure the TLS connection is invalid.",
376+
"The certificate used to secure the TLS connection is invalid."
377377
);
378378
} else {
379379
console.error(
380380
bold(yellow("TLS connection failed with message: ")) +
381381
e.message +
382382
"\n" +
383-
bold("Defaulting to non-encrypted connection"),
383+
bold("Defaulting to non-encrypted connection")
384384
);
385385
await this.#openConnection({ hostname, port, transport: "tcp" });
386386
this.#tls = false;
@@ -436,7 +436,7 @@ export class Connection {
436436
async startup(is_reconnection: boolean) {
437437
if (is_reconnection && this.#connection_params.connection.attempts === 0) {
438438
throw new Error(
439-
"The client has been disconnected from the database. Enable reconnection in the client to attempt reconnection after failure",
439+
"The client has been disconnected from the database. Enable reconnection in the client to attempt reconnection after failure"
440440
);
441441
}
442442

@@ -512,19 +512,19 @@ export class Connection {
512512
}
513513
case AUTHENTICATION_TYPE.SCM:
514514
throw new Error(
515-
"Database server expected SCM authentication, which is not supported at the moment",
515+
"Database server expected SCM authentication, which is not supported at the moment"
516516
);
517517
case AUTHENTICATION_TYPE.GSS_STARTUP:
518518
throw new Error(
519-
"Database server expected GSS authentication, which is not supported at the moment",
519+
"Database server expected GSS authentication, which is not supported at the moment"
520520
);
521521
case AUTHENTICATION_TYPE.GSS_CONTINUE:
522522
throw new Error(
523-
"Database server expected GSS authentication, which is not supported at the moment",
523+
"Database server expected GSS authentication, which is not supported at the moment"
524524
);
525525
case AUTHENTICATION_TYPE.SSPI:
526526
throw new Error(
527-
"Database server expected SSPI authentication, which is not supported at the moment",
527+
"Database server expected SSPI authentication, which is not supported at the moment"
528528
);
529529
case AUTHENTICATION_TYPE.SASL_STARTUP:
530530
authentication_result = await this.#authenticateWithSasl();
@@ -552,14 +552,14 @@ export class Connection {
552552

553553
if (!this.#connection_params.password) {
554554
throw new ConnectionParamsError(
555-
"Attempting MD5 authentication with unset password",
555+
"Attempting MD5 authentication with unset password"
556556
);
557557
}
558558

559559
const password = await hashMd5Password(
560560
this.#connection_params.password,
561561
this.#connection_params.user,
562-
salt,
562+
salt
563563
);
564564
const buffer = this.#packetWriter.addCString(password).flush(0x70);
565565

@@ -575,13 +575,13 @@ export class Connection {
575575
async #authenticateWithSasl(): Promise<Message> {
576576
if (!this.#connection_params.password) {
577577
throw new ConnectionParamsError(
578-
"Attempting SASL auth with unset password",
578+
"Attempting SASL auth with unset password"
579579
);
580580
}
581581

582582
const client = new scram.Client(
583583
this.#connection_params.user,
584-
this.#connection_params.password,
584+
this.#connection_params.password
585585
);
586586
const utf8 = new TextDecoder("utf-8");
587587

@@ -600,7 +600,7 @@ export class Connection {
600600
const authentication_type = maybe_sasl_continue.reader.readInt32();
601601
if (authentication_type !== AUTHENTICATION_TYPE.SASL_CONTINUE) {
602602
throw new Error(
603-
`Unexpected authentication type in SASL negotiation: ${authentication_type}`,
603+
`Unexpected authentication type in SASL negotiation: ${authentication_type}`
604604
);
605605
}
606606
break;
@@ -609,11 +609,11 @@ export class Connection {
609609
throw new PostgresError(parseNoticeMessage(maybe_sasl_continue));
610610
default:
611611
throw new Error(
612-
`Unexpected message in SASL negotiation: ${maybe_sasl_continue.type}`,
612+
`Unexpected message in SASL negotiation: ${maybe_sasl_continue.type}`
613613
);
614614
}
615615
const sasl_continue = utf8.decode(
616-
maybe_sasl_continue.reader.readAllBytes(),
616+
maybe_sasl_continue.reader.readAllBytes()
617617
);
618618
await client.receiveChallenge(sasl_continue);
619619

@@ -628,7 +628,7 @@ export class Connection {
628628
const authentication_type = maybe_sasl_final.reader.readInt32();
629629
if (authentication_type !== AUTHENTICATION_TYPE.SASL_FINAL) {
630630
throw new Error(
631-
`Unexpected authentication type in SASL finalization: ${authentication_type}`,
631+
`Unexpected authentication type in SASL finalization: ${authentication_type}`
632632
);
633633
}
634634
break;
@@ -637,7 +637,7 @@ export class Connection {
637637
throw new PostgresError(parseNoticeMessage(maybe_sasl_final));
638638
default:
639639
throw new Error(
640-
`Unexpected message in SASL finalization: ${maybe_sasl_continue.type}`,
640+
`Unexpected message in SASL finalization: ${maybe_sasl_continue.type}`
641641
);
642642
}
643643
const sasl_final = utf8.decode(maybe_sasl_final.reader.readAllBytes());
@@ -649,7 +649,7 @@ export class Connection {
649649

650650
async #simpleQuery(query: Query<ResultType.ARRAY>): Promise<QueryArrayResult>;
651651
async #simpleQuery(
652-
query: Query<ResultType.OBJECT>,
652+
query: Query<ResultType.OBJECT>
653653
): Promise<QueryObjectResult>;
654654
async #simpleQuery(query: Query<ResultType>): Promise<QueryResult> {
655655
this.#packetWriter.clear();
@@ -678,14 +678,14 @@ export class Connection {
678678
break;
679679
case INCOMING_QUERY_MESSAGES.COMMAND_COMPLETE: {
680680
result.handleCommandComplete(
681-
parseCommandCompleteMessage(current_message),
681+
parseCommandCompleteMessage(current_message)
682682
);
683683
break;
684684
}
685685
case INCOMING_QUERY_MESSAGES.DATA_ROW: {
686686
const row_data = parseRowDataMessage(current_message);
687687
try {
688-
result.insertRow(row_data);
688+
result.insertRow(row_data, this.#connection_params.controls);
689689
} catch (e) {
690690
error = e;
691691
}
@@ -705,13 +705,13 @@ export class Connection {
705705
break;
706706
case INCOMING_QUERY_MESSAGES.ROW_DESCRIPTION: {
707707
result.loadColumnDescriptions(
708-
parseRowDescriptionMessage(current_message),
708+
parseRowDescriptionMessage(current_message)
709709
);
710710
break;
711711
}
712712
default:
713713
throw new Error(
714-
`Unexpected simple query message: ${current_message.type}`,
714+
`Unexpected simple query message: ${current_message.type}`
715715
);
716716
}
717717

@@ -820,7 +820,7 @@ export class Connection {
820820
* https://www.postgresql.org/docs/14/protocol-flow.html#PROTOCOL-FLOW-EXT-QUERY
821821
*/
822822
async #preparedQuery<T extends ResultType>(
823-
query: Query<T>,
823+
query: Query<T>
824824
): Promise<QueryResult> {
825825
// The parse messages declares the statement, query arguments and the cursor used in the transaction
826826
// The database will respond with a parse response
@@ -855,14 +855,14 @@ export class Connection {
855855
break;
856856
case INCOMING_QUERY_MESSAGES.COMMAND_COMPLETE: {
857857
result.handleCommandComplete(
858-
parseCommandCompleteMessage(current_message),
858+
parseCommandCompleteMessage(current_message)
859859
);
860860
break;
861861
}
862862
case INCOMING_QUERY_MESSAGES.DATA_ROW: {
863863
const row_data = parseRowDataMessage(current_message);
864864
try {
865-
result.insertRow(row_data);
865+
result.insertRow(row_data, this.#connection_params.controls);
866866
} catch (e) {
867867
error = e;
868868
}
@@ -884,13 +884,13 @@ export class Connection {
884884
break;
885885
case INCOMING_QUERY_MESSAGES.ROW_DESCRIPTION: {
886886
result.loadColumnDescriptions(
887-
parseRowDescriptionMessage(current_message),
887+
parseRowDescriptionMessage(current_message)
888888
);
889889
break;
890890
}
891891
default:
892892
throw new Error(
893-
`Unexpected prepared query message: ${current_message.type}`,
893+
`Unexpected prepared query message: ${current_message.type}`
894894
);
895895
}
896896

connection/connection_params.ts

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -91,19 +91,43 @@ export interface TLSOptions {
9191
caCertificates: string[];
9292
}
9393

94+
/**
95+
* Control the behavior for the client instance
96+
*/
97+
export type ClientControls = {
98+
/**
99+
* The strategy to use when decoding binary fields
100+
*
101+
* `string` : all values are returned as string, and the user has to take care of parsing
102+
* `auto` : deno-postgres parses the data into JS objects (as many as possible implemented, non-implemented parsers would still return strings)
103+
*
104+
* Default: `auto`
105+
*
106+
* Future strategies might include:
107+
* - `strict` : deno-postgres parses the data into JS objects, and if a parser is not implemented, it throws an error
108+
* - `raw` : the data is returned as Uint8Array
109+
*/
110+
decode_strategy?: "string" | "auto";
111+
};
112+
94113
/** The Client database connection options */
95114
export type ClientOptions = {
96115
/** Name of the application connecing to the database */
97116
applicationName?: string;
98117
/** Additional connection options */
99118
connection?: Partial<ConnectionOptions>;
119+
/** Control the client behavior */
120+
controls?: ClientControls;
100121
/** The database name */
101122
database?: string;
102123
/** The name of the host */
103124
hostname?: string;
104125
/** The type of host connection */
105126
host_type?: "tcp" | "socket";
106-
/** Additional client options */
127+
/**
128+
* Additional connection URI options
129+
* https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-PARAMKEYWORDS
130+
*/
107131
options?: string | Record<string, string>;
108132
/** The database user password */
109133
password?: string;
@@ -118,14 +142,18 @@ export type ClientOptions = {
118142
/** The configuration options required to set up a Client instance */
119143
export type ClientConfiguration =
120144
& Required<
121-
Omit<ClientOptions, "password" | "port" | "tls" | "connection" | "options">
145+
Omit<
146+
ClientOptions,
147+
"password" | "port" | "tls" | "connection" | "options" | "controls"
148+
>
122149
>
123150
& {
151+
connection: ConnectionOptions;
152+
controls?: ClientControls;
153+
options: Record<string, string>;
124154
password?: string;
125155
port: number;
126156
tls: TLSOptions;
127-
connection: ConnectionOptions;
128-
options: Record<string, string>;
129157
};
130158

131159
function formatMissingParams(missingParams: string[]) {
@@ -168,7 +196,7 @@ function assertRequiredOptions(
168196

169197
// TODO
170198
// Support more options from the spec
171-
/** options from URI per https://www.postgresql.org/docs/14/libpq-connect.html#LIBPQ-CONNSTRING */
199+
/** options from URI per https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING */
172200
interface PostgresUri {
173201
application_name?: string;
174202
dbname?: string;
@@ -447,6 +475,7 @@ export function createParams(
447475
caCertificates: params?.tls?.caCertificates ?? [],
448476
},
449477
user: params.user ?? pgEnv.user,
478+
controls: params.controls,
450479
};
451480

452481
assertRequiredOptions(

0 commit comments

Comments
 (0)