Skip to content

Commit 44dfcf0

Browse files
authored
feat: Process options argument (denodrivers#396)
1 parent 33ca49d commit 44dfcf0

File tree

6 files changed

+335
-45
lines changed

6 files changed

+335
-45
lines changed

connection/connection.ts

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -196,21 +196,29 @@ export class Connection {
196196
}
197197
}
198198

199+
/** https://www.postgresql.org/docs/14/protocol-flow.html#id-1.10.5.7.3 */
199200
async #sendStartupMessage(): Promise<Message> {
200201
const writer = this.#packetWriter;
201202
writer.clear();
203+
202204
// protocol version - 3.0, written as
203205
writer.addInt16(3).addInt16(0);
204-
const connParams = this.#connection_params;
206+
// explicitly set utf-8 encoding
207+
writer.addCString("client_encoding").addCString("'utf-8'");
208+
205209
// TODO: recognize other parameters
206-
writer.addCString("user").addCString(connParams.user);
207-
writer.addCString("database").addCString(connParams.database);
210+
writer.addCString("user").addCString(this.#connection_params.user);
211+
writer.addCString("database").addCString(this.#connection_params.database);
208212
writer.addCString("application_name").addCString(
209-
connParams.applicationName,
213+
this.#connection_params.applicationName,
214+
);
215+
// The database expects options in the --key=value
216+
writer.addCString("options").addCString(
217+
Object.entries(this.#connection_params.options).map(([key, value]) =>
218+
`--${key}=${value}`
219+
).join(" "),
210220
);
211221

212-
// eplicitly set utf-8 encoding
213-
writer.addCString("client_encoding").addCString("'utf-8'");
214222
// terminator after all parameters were writter
215223
writer.addCString("");
216224

connection/connection_params.ts

Lines changed: 82 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { fromFileUrl, isAbsolute } from "../deps.ts";
1212
* - application_name
1313
* - dbname
1414
* - host
15+
* - options
1516
* - password
1617
* - port
1718
* - sslmode
@@ -27,12 +28,13 @@ export type ConnectionString = string;
2728
*/
2829
function getPgEnv(): ClientOptions {
2930
return {
31+
applicationName: Deno.env.get("PGAPPNAME"),
3032
database: Deno.env.get("PGDATABASE"),
3133
hostname: Deno.env.get("PGHOST"),
34+
options: Deno.env.get("PGOPTIONS"),
35+
password: Deno.env.get("PGPASSWORD"),
3236
port: Deno.env.get("PGPORT"),
3337
user: Deno.env.get("PGUSER"),
34-
password: Deno.env.get("PGPASSWORD"),
35-
applicationName: Deno.env.get("PGAPPNAME"),
3638
};
3739
}
3840

@@ -84,6 +86,7 @@ export interface ClientOptions {
8486
database?: string;
8587
hostname?: string;
8688
host_type?: "tcp" | "socket";
89+
options?: string | Record<string, string>;
8790
password?: string;
8891
port?: string | number;
8992
tls?: Partial<TLSOptions>;
@@ -96,6 +99,7 @@ export interface ClientConfiguration {
9699
database: string;
97100
hostname: string;
98101
host_type: "tcp" | "socket";
102+
options: Record<string, string>;
99103
password?: string;
100104
port: number;
101105
tls: TLSOptions;
@@ -152,21 +156,67 @@ interface PostgresUri {
152156
dbname?: string;
153157
driver: string;
154158
host?: string;
159+
options?: string;
155160
password?: string;
156161
port?: string;
157162
sslmode?: TLSModes;
158163
user?: string;
159164
}
160165

161-
function parseOptionsFromUri(connString: string): ClientOptions {
166+
function parseOptionsArgument(options: string): Record<string, string> {
167+
const args = options.split(" ");
168+
169+
const transformed_args = [];
170+
for (let x = 0; x < args.length; x++) {
171+
if (/^-\w/.test(args[x])) {
172+
if (args[x] === "-c") {
173+
if (args[x + 1] === undefined) {
174+
throw new Error(
175+
`No provided value for "${args[x]}" in options parameter`,
176+
);
177+
}
178+
179+
// Skip next iteration
180+
transformed_args.push(args[x + 1]);
181+
x++;
182+
} else {
183+
throw new Error(
184+
`Argument "${args[x]}" is not supported in options parameter`,
185+
);
186+
}
187+
} else if (/^--\w/.test(args[x])) {
188+
transformed_args.push(args[x].slice(2));
189+
} else {
190+
throw new Error(
191+
`Value "${args[x]}" is not a valid options argument`,
192+
);
193+
}
194+
}
195+
196+
return transformed_args.reduce((options, x) => {
197+
if (!/.+=.+/.test(x)) {
198+
throw new Error(`Value "${x}" is not a valid options argument`);
199+
}
200+
201+
const key = x.slice(0, x.indexOf("="));
202+
const value = x.slice(x.indexOf("=") + 1);
203+
204+
options[key] = value;
205+
206+
return options;
207+
}, {} as Record<string, string>);
208+
}
209+
210+
function parseOptionsFromUri(connection_string: string): ClientOptions {
162211
let postgres_uri: PostgresUri;
163212
try {
164-
const uri = parseConnectionUri(connString);
213+
const uri = parseConnectionUri(connection_string);
165214
postgres_uri = {
166215
application_name: uri.params.application_name,
167216
dbname: uri.path || uri.params.dbname,
168217
driver: uri.driver,
169218
host: uri.host || uri.params.host,
219+
options: uri.params.options,
170220
password: uri.password || uri.params.password,
171221
port: uri.port || uri.params.port,
172222
// Compatibility with JDBC, not standard
@@ -194,6 +244,10 @@ function parseOptionsFromUri(connString: string): ClientOptions {
194244
? (isAbsolute(postgres_uri.host) ? "socket" : "tcp")
195245
: "socket";
196246

247+
const options = postgres_uri.options
248+
? parseOptionsArgument(postgres_uri.options)
249+
: {};
250+
197251
let tls: TLSOptions | undefined;
198252
switch (postgres_uri.sslmode) {
199253
case undefined: {
@@ -223,6 +277,7 @@ function parseOptionsFromUri(connString: string): ClientOptions {
223277
database: postgres_uri.dbname,
224278
hostname: postgres_uri.host,
225279
host_type,
280+
options,
226281
password: postgres_uri.password,
227282
port: postgres_uri.port,
228283
tls,
@@ -240,6 +295,7 @@ const DEFAULT_OPTIONS:
240295
host: "127.0.0.1",
241296
socket: "/tmp",
242297
host_type: "socket",
298+
options: {},
243299
port: 5432,
244300
tls: {
245301
enabled: true,
@@ -304,6 +360,27 @@ export function createParams(
304360
host = provided_host ?? DEFAULT_OPTIONS.host;
305361
}
306362

363+
const provided_options = params.options ?? pgEnv.options;
364+
365+
let options: Record<string, string>;
366+
if (provided_options) {
367+
if (typeof provided_options === "string") {
368+
options = parseOptionsArgument(provided_options);
369+
} else {
370+
options = provided_options;
371+
}
372+
} else {
373+
options = {};
374+
}
375+
376+
for (const key in options) {
377+
if (!/^\w+$/.test(key)) {
378+
throw new Error(`The "${key}" key in the options argument is invalid`);
379+
}
380+
381+
options[key] = options[key].replaceAll(" ", "\\ ");
382+
}
383+
307384
let port: number;
308385
if (params.port) {
309386
port = Number(params.port);
@@ -344,6 +421,7 @@ export function createParams(
344421
database: params.database ?? pgEnv.database,
345422
hostname: host,
346423
host_type,
424+
options,
347425
password: params.password ?? pgEnv.password,
348426
port,
349427
tls: {

docs/README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@ config = {
5252
hostname: "localhost",
5353
host_type: "tcp",
5454
password: "password",
55+
options: {
56+
"max_index_keys": "32",
57+
},
5558
port: 5432,
5659
user: "user",
5760
tls: {
@@ -108,6 +111,9 @@ of search parameters such as the following:
108111
- host: If host is not specified in the url, this will be taken instead
109112
- password: If password is not specified in the url, this will be taken instead
110113
- port: If port is not specified in the url, this will be taken instead
114+
- options: This parameter can be used by other database engines usable through
115+
the Postgres protocol (such as Cockroachdb for example) to send additional
116+
values for connection (ej: options=--cluster=your_cluster_name)
111117
- sslmode: Allows you to specify the tls configuration for your client, the
112118
allowed values are the following:
113119
- disable: Skip TLS connection altogether

tests/config.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ export const getClearConfiguration = (
4545
database: config.postgres_clear.database,
4646
host_type: "tcp",
4747
hostname: config.postgres_clear.hostname,
48+
options: {},
4849
password: config.postgres_clear.password,
4950
port: config.postgres_clear.port,
5051
tls: tls ? enabled_tls : disabled_tls,
@@ -58,6 +59,7 @@ export const getClearSocketConfiguration = (): SocketConfiguration => {
5859
database: config.postgres_clear.database,
5960
host_type: "socket",
6061
hostname: config.postgres_clear.socket,
62+
options: {},
6163
password: config.postgres_clear.password,
6264
port: config.postgres_clear.port,
6365
user: config.postgres_clear.users.socket,
@@ -71,6 +73,7 @@ export const getMainConfiguration = (): TcpConfiguration => {
7173
database: config.postgres_md5.database,
7274
hostname: config.postgres_md5.hostname,
7375
host_type: "tcp",
76+
options: {},
7477
password: config.postgres_md5.password,
7578
port: config.postgres_md5.port,
7679
tls: enabled_tls,
@@ -84,6 +87,7 @@ export const getMd5Configuration = (tls: boolean): TcpConfiguration => {
8487
database: config.postgres_md5.database,
8588
hostname: config.postgres_md5.hostname,
8689
host_type: "tcp",
90+
options: {},
8791
password: config.postgres_md5.password,
8892
port: config.postgres_md5.port,
8993
tls: tls ? enabled_tls : disabled_tls,
@@ -97,6 +101,7 @@ export const getMd5SocketConfiguration = (): SocketConfiguration => {
97101
database: config.postgres_md5.database,
98102
hostname: config.postgres_md5.socket,
99103
host_type: "socket",
104+
options: {},
100105
password: config.postgres_md5.password,
101106
port: config.postgres_md5.port,
102107
user: config.postgres_md5.users.socket,
@@ -109,6 +114,7 @@ export const getScramConfiguration = (tls: boolean): TcpConfiguration => {
109114
database: config.postgres_scram.database,
110115
hostname: config.postgres_scram.hostname,
111116
host_type: "tcp",
117+
options: {},
112118
password: config.postgres_scram.password,
113119
port: config.postgres_scram.port,
114120
tls: tls ? enabled_tls : disabled_tls,
@@ -122,6 +128,7 @@ export const getScramSocketConfiguration = (): SocketConfiguration => {
122128
database: config.postgres_scram.database,
123129
hostname: config.postgres_scram.socket,
124130
host_type: "socket",
131+
options: {},
125132
password: config.postgres_scram.password,
126133
port: config.postgres_scram.port,
127134
user: config.postgres_scram.users.socket,
@@ -134,6 +141,7 @@ export const getTlsOnlyConfiguration = (): TcpConfiguration => {
134141
database: config.postgres_md5.database,
135142
hostname: config.postgres_md5.hostname,
136143
host_type: "tcp",
144+
options: {},
137145
password: config.postgres_md5.password,
138146
port: config.postgres_md5.port,
139147
tls: enabled_tls,

0 commit comments

Comments
 (0)