Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for branch configuration #910

Merged
merged 19 commits into from Apr 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 5 additions & 0 deletions packages/driver/jest.connect-config.js
@@ -0,0 +1,5 @@
module.exports = {
preset: "ts-jest",
testEnvironment: "node",
testPathIgnorePatterns: ["./dist", "./test/deno/"],
};
118 changes: 103 additions & 15 deletions packages/driver/src/conUtils.ts
Expand Up @@ -18,7 +18,7 @@

import * as errors from "./errors";
import {
Credentials,
type Credentials,
getCredentialsPath,
readCredentialsFile,
validateCredentials,
Expand Down Expand Up @@ -76,6 +76,7 @@ export interface ConnectConfig {
host?: string;
port?: number;
database?: string;
branch?: string;
user?: string;
password?: string;
secretKey?: string;
Expand Down Expand Up @@ -116,6 +117,7 @@ type ConnectConfigParams =
| "host"
| "port"
| "database"
| "branch"
| "user"
| "password"
| "secretKey"
Expand All @@ -141,6 +143,9 @@ export class ResolvedConnectConfig {
_database: string | null = null;
_databaseSource: string | null = null;

_branch: string | null = null;
_branchSource: string | null = null;

_user: string | null = null;
_userSource: string | null = null;

Expand Down Expand Up @@ -168,6 +173,7 @@ export class ResolvedConnectConfig {
this.setHost = this.setHost.bind(this);
this.setPort = this.setPort.bind(this);
this.setDatabase = this.setDatabase.bind(this);
this.setBranch = this.setBranch.bind(this);
this.setUser = this.setUser.bind(this);
this.setPassword = this.setPassword.bind(this);
this.setSecretKey = this.setSecretKey.bind(this);
Expand Down Expand Up @@ -229,6 +235,15 @@ export class ResolvedConnectConfig {
});
}

setBranch(branch: string | null, source: string): boolean {
return this._setParam("branch", branch, source, (branchName: string) => {
if (branchName === "") {
throw new InterfaceError(`invalid branch name: '${branchName}'`);
}
return branchName;
});
}

setUser(user: string | null, source: string): boolean {
return this._setParam("user", user, source, (_user: string) => {
if (_user === "") {
Expand Down Expand Up @@ -336,7 +351,11 @@ export class ResolvedConnectConfig {
}

get database(): string {
return this._database ?? "edgedb";
return this._database ?? this._branch ?? "edgedb";
}

get branch(): string {
return this._branch ?? this._database ?? "__default__";
}

get user(): string {
Expand Down Expand Up @@ -514,6 +533,7 @@ async function parseConnectDsnAndArgs(
host: config.host,
port: config.port,
database: config.database,
branch: config.branch,
user: config.user,
password: config.password,
secretKey: config.secretKey,
Expand All @@ -535,6 +555,7 @@ async function parseConnectDsnAndArgs(
host: `'host' option`,
port: `'port' option`,
database: `'database' option`,
branch: `'branch' option`,
user: `'user' option`,
password: `'password' option`,
secretKey: `'secretKey' option`,
Expand Down Expand Up @@ -574,6 +595,7 @@ async function parseConnectDsnAndArgs(
host: getEnv("EDGEDB_HOST"),
port,
database: getEnv("EDGEDB_DATABASE"),
branch: getEnv("EDGEDB_BRANCH"),
user: getEnv("EDGEDB_USER"),
password: getEnv("EDGEDB_PASSWORD"),
secretKey: getEnv("EDGEDB_SECRET_KEY"),
Expand All @@ -590,6 +612,7 @@ async function parseConnectDsnAndArgs(
host: `'EDGEDB_HOST' environment variable`,
port: `'EDGEDB_PORT' environment variable`,
database: `'EDGEDB_DATABASE' environment variable`,
branch: `'EDGEDB_BRANCH' environment variable`,
user: `'EDGEDB_USER' environment variable`,
password: `'EDGEDB_PASSWORD' environment variable`,
secretKey: `'EDGEDB_SECRET_KEY' environment variable`,
Expand Down Expand Up @@ -679,6 +702,7 @@ interface ResolveConfigOptionsConfig {
host: string;
port: number | string;
database: string;
branch: string;
user: string;
password: string;
secretKey: string;
Expand Down Expand Up @@ -715,9 +739,28 @@ async function resolveConfigOptions<
);
}

anyOptionsUsed =
resolvedConfig.setDatabase(config.database ?? null, sources.database!) ||
anyOptionsUsed;
if (config.database != null) {
if (config.branch != null) {
throw new InterfaceError(
`${sources.database} and ${sources.branch} are mutually exclusive`
);
}
if (resolvedConfig._branch == null) {
anyOptionsUsed =
resolvedConfig.setDatabase(
config.database ?? null,
sources.database!
) || anyOptionsUsed;
}
}
if (config.branch != null) {
if (resolvedConfig._database == null) {
anyOptionsUsed =
resolvedConfig.setBranch(config.branch ?? null, sources.branch!) ||
anyOptionsUsed;
}
}

anyOptionsUsed =
resolvedConfig.setUser(config.user ?? null, sources.user!) ||
anyOptionsUsed;
Expand Down Expand Up @@ -839,7 +882,15 @@ async function resolveConfigOptions<

resolvedConfig.setHost(creds.host ?? null, source);
resolvedConfig.setPort(creds.port ?? null, source);
resolvedConfig.setDatabase(creds.database ?? null, source);
if (creds.database != null) {
if (resolvedConfig._branch == null) {
resolvedConfig.setDatabase(creds.database ?? null, source);
}
} else if (creds.branch != null) {
if (resolvedConfig._database == null) {
resolvedConfig.setBranch(creds.branch ?? null, source);
}
}
resolvedConfig.setUser(creds.user ?? null, source);
resolvedConfig.setPassword(creds.password ?? null, source);
resolvedConfig.setTlsCAData(creds.tlsCAData ?? null, source);
Expand All @@ -861,7 +912,7 @@ async function parseDSNIntoConfig(
// https://url.spec.whatwg.org/#host-representation
let dsnString = _dsnString;
let regexHostname: string | null = null;
let zoneId: string = "";
let zoneId = "";
const regexResult = /\[(.*?)(%25.+?)\]/.exec(_dsnString);
if (regexResult) {
regexHostname = regexResult[1];
Expand Down Expand Up @@ -891,7 +942,7 @@ async function parseDSNIntoConfig(
}

const searchParams = new Map<string, string>();
for (const [key, value] of parsed.searchParams as any) {
for (const [key, value] of parsed.searchParams) {
if (searchParams.has(key)) {
throw new InterfaceError(
`invalid DSN: duplicate query parameter '${key}'`
Expand Down Expand Up @@ -965,13 +1016,50 @@ async function parseDSNIntoConfig(
await handleDSNPart("port", parsed.port, config._port, config.setPort);

const stripLeadingSlash = (str: string) => str.replace(/^\//, "");
await handleDSNPart(
"database",
stripLeadingSlash(parsed.pathname),
config._database,
config.setDatabase,
stripLeadingSlash
);

const searchParamsContainsDatabase =
searchParams.has("database") ||
searchParams.has("database_env") ||
searchParams.has("database_file");
const searchParamsContainsBranch =
searchParams.has("branch") ||
searchParams.has("branch_env") ||
searchParams.has("branch_file");

if (searchParamsContainsBranch) {
if (searchParamsContainsDatabase) {
throw new InterfaceError(
`invalid DSN: cannot specify both 'database' and 'branch'`
);
}
if (config._database === null) {
await handleDSNPart(
"branch",
stripLeadingSlash(parsed.pathname),
config._branch,
config.setBranch,
stripLeadingSlash
);
} else {
searchParams.delete("branch");
searchParams.delete("branch_env");
searchParams.delete("branch_file");
}
} else {
if (config._branch === null) {
await handleDSNPart(
"database",
stripLeadingSlash(parsed.pathname),
config._database,
config.setDatabase,
stripLeadingSlash
);
} else {
searchParams.delete("database");
searchParams.delete("database_env");
searchParams.delete("database_file");
}
}

await handleDSNPart("user", parsed.username, config._user, config.setUser);

Expand Down
18 changes: 17 additions & 1 deletion packages/driver/src/credentials.ts
@@ -1,4 +1,8 @@
import { ServerUtils, TlsSecurity, validTlsSecurityValues } from "./conUtils";
import {
type ServerUtils,
type TlsSecurity,
validTlsSecurityValues,
} from "./conUtils";

import { InterfaceError } from "./errors";

Expand All @@ -8,6 +12,7 @@ export interface Credentials {
user: string;
password?: string;
database?: string;
branch?: string;
tlsCAData?: string;
tlsSecurity?: TlsSecurity;
}
Expand Down Expand Up @@ -63,6 +68,17 @@ export function validateCredentials(data: any): Credentials {
result.database = database;
}

const branch = data.branch;
if (branch != null) {
if (typeof branch !== "string") {
throw new InterfaceError("`branch` must be string");
}
if (database != null && branch !== database) {
throw new InterfaceError("`database` and `branch` cannot both be set");
}
result.branch = branch;
}

const password = data.password;
if (password != null) {
if (typeof password !== "string") {
Expand Down