@clickup/pg-microsharding
Version:
Microshards support for PostgreSQL
93 lines (83 loc) • 2.22 kB
text/typescript
export type Value = string | number | null | Sql | Ident;
/**
* Quotes the value to be used as a shell argument.
*/
export function shellQuote(s: string): string {
function quote(s: string): string {
return "'" + s.replace(/'/g, "'\\''") + "'";
}
return s === ";"
? "\\;"
: s.match(/^[-a-z_0-9:/@.=,]+$/is)
? s
: s.match(/^((?:--)?[-a-z_0-9]+=)(.*)$/is)
? RegExp.$1 + quote(RegExp.$2)
: quote(s);
}
/**
* Builds a compound SQL statement from a typed template string literal. All SQL
* clauses built by this helper will have newlines removed.
*/
export function sql(
strings: readonly string[],
...values: readonly Value[]
): Sql {
const SEP = "\x00";
return new Sql(
strings
.join(SEP)
.trim()
.replace(/[ \t\r\n]+/g, " ") // glue all lines in one
.trimEnd()
.split(SEP),
values,
);
}
/**
* Builds a PG identifier from a string.
*/
export function ident(name: string): Ident {
return new Ident(name);
}
/**
* Joins multiple compound SQL statements.
*/
export function join(
sqls: ReadonlyArray<Sql | Ident | string>,
sep: string,
): Sql {
return new Sql([sqls.map((sql) => sql.toString()).join(sep)], []);
}
class Ident {
constructor(private name: string) {}
toString(): string {
return this.name.match(/^[a-z_][a-z_0-9]*$/is)
? this.name
: '"' + this.name.replace(/"/g, '""') + '"';
}
}
export class Sql {
constructor(
private strings: readonly string[],
private values: readonly Value[],
) {}
toString(): string {
return (
this.values
.map(
(value, i) =>
this.strings[i] +
(value instanceof Sql || value instanceof Ident
? value.toString()
: value === null || value === undefined
? "NULL"
: // Postgres doesn't like ASCII NUL character (error message is "unterminated
// quoted string" or "invalid message format"), so we remove it too.
"'" +
("" + value).replace(/\0/g, "").replace(/'/g, "''") +
"'"),
)
.join("") + this.strings[this.values.length]
);
}
}