@clickup/ent-framework
Version:
A PostgreSQL graph-database-alike library with microsharding and row-level security
107 lines (94 loc) • 3.33 kB
text/typescript
import type { QueryAnnotation } from "../abstract/QueryAnnotation";
import { QueryBase } from "../abstract/QueryBase";
import type { Schema } from "../abstract/Schema";
import type { InsertInput, Table } from "../types";
import { ID } from "../types";
import type { PgClient } from "./PgClient";
import { PgRunner } from "./PgRunner";
export class PgQueryInsert<TTable extends Table> extends QueryBase<
TTable,
InsertInput<TTable>,
string | null,
PgClient
> {
/** @ignore */
readonly RUNNER_CLASS = PgRunnerInsert;
}
class PgRunnerInsert<TTable extends Table> extends PgRunner<
TTable,
InsertInput<TTable>,
string | null
> {
static override readonly IS_WRITE = true;
private singleBuilder;
private batchBuilder;
readonly op = "INSERT";
readonly maxBatchSize = 100;
readonly default = null; // In case of duplicate key error, returns null.
constructor(schema: Schema<TTable>, client: PgClient) {
super(schema, client);
const fields = this.addPK(Object.keys(this.schema.table), "append");
this.singleBuilder = this.createValuesBuilder({
prefix: this.fmt("INSERT INTO %T (%FIELDS) VALUES", { fields }),
indent: " ",
fields,
suffix: this.fmt(` ON CONFLICT DO NOTHING RETURNING %PK AS ${ID}`),
});
// We use WITH clause in INSERT, because "ON CONFLICT DO NOTHING" clause
// doesn't emit anything in "RETURNING" clause, so we could've not
// distinguished rows which were inserted from the rows which were not.
// Having WITH solves this (see RETURNING below).
this.batchBuilder = this.createWithBuilder({
fields,
suffix: this.fmt(
"INSERT INTO %T (%FIELDS)\n" +
"SELECT %FIELDS FROM rows OFFSET 1\n" +
`ON CONFLICT DO NOTHING RETURNING (SELECT _key FROM rows WHERE %PK(rows)=%PK(%T)), %PK AS ${ID}`,
{ fields },
),
});
}
override key(input: InsertInput<TTable>): string {
// We must NEVER dedup inserts, because:
// 1. If the table DOESN'T have an unique key, then we must insert all
// input rows (no dedup allowed).
// 2. If the table DOES have an unique key, then we must logically ensure
// that only one concurrent promise is resolved into an inserted row ID,
// and all other are resolved with null (aka "not inserted due to
// duplicate").
return super.key(input);
}
async runSingle(
input: InsertInput<TTable>,
annotations: QueryAnnotation[],
): Promise<string | undefined> {
const sql =
this.singleBuilder.prefix +
this.singleBuilder.func([["", input]]) +
this.singleBuilder.suffix;
const rows = await this.clientQuery<{ [ID]: string }>(sql, annotations, 1);
if (!rows.length) {
return undefined;
}
return rows[0][ID];
}
async runBatch(
inputs: Map<string, InsertInput<TTable>>,
annotations: QueryAnnotation[],
): Promise<Map<string, string>> {
const sql =
this.batchBuilder.prefix +
this.batchBuilder.func(inputs) +
this.batchBuilder.suffix;
const rows = await this.clientQuery<{ _key: string; [ID]: string }>(
sql,
annotations,
inputs.size,
);
const outputs = new Map<string, string>();
for (const row of rows) {
outputs.set(row._key, row[ID]);
}
return outputs;
}
}