@quark-fw/clisma 0.1.7 → 0.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/build-count-query.d.ts +4 -0
- package/dist/build-count-query.js +12 -0
- package/dist/build-delete-query.d.ts +4 -0
- package/dist/build-delete-query.js +11 -0
- package/dist/build-find-many-query.d.ts +3 -0
- package/dist/build-find-many-query.js +58 -0
- package/dist/build-lightweight-delete-query.d.ts +4 -0
- package/dist/build-lightweight-delete-query.js +11 -0
- package/dist/build-update-query.d.ts +4 -0
- package/dist/build-update-query.js +13 -0
- package/dist/client.d.ts +2 -2
- package/dist/client.js +4 -4
- package/dist/column.d.ts +3 -3
- package/dist/column.js +1 -8
- package/dist/ddl.d.ts +2 -2
- package/dist/ddl.js +1 -3
- package/dist/migrate.d.ts +2 -2
- package/dist/rows.d.ts +2 -2
- package/dist/rows.js +8 -3
- package/dist/schema.d.ts +3 -2
- package/dist/sql-shared.d.ts +30 -0
- package/dist/sql-shared.js +162 -0
- package/dist/sql.d.ts +6 -25
- package/dist/sql.js +8 -254
- package/dist/table-client.d.ts +3 -3
- package/dist/table-client.js +18 -13
- package/dist/table.d.ts +4 -2
- package/dist/where.d.ts +2 -2
- package/package.json +1 -1
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// src/build-count-query.ts
|
|
2
|
+
import { buildFindManyQuery } from "./build-find-many-query.js";
|
|
3
|
+
export function buildCountQuery(database, tableName, table, where) {
|
|
4
|
+
const inner = buildFindManyQuery(database, tableName, table, { where });
|
|
5
|
+
// with no select arg the query starts with "SELECT * FROM ..." —
|
|
6
|
+
// swap the projection for count()
|
|
7
|
+
const from = inner.sql.indexOf(" FROM ");
|
|
8
|
+
return {
|
|
9
|
+
sql: "SELECT count() AS count" + inner.sql.slice(from),
|
|
10
|
+
params: inner.params,
|
|
11
|
+
};
|
|
12
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// src/build-delete-query.ts
|
|
2
|
+
import { createBuildContext, escapeId } from "./sql-shared.js";
|
|
3
|
+
export function buildDeleteQuery(database, tableName, table, where) {
|
|
4
|
+
const ctx = createBuildContext(tableName, table.columns);
|
|
5
|
+
const whereSQL = where ? ctx.whereSQL(where) : "";
|
|
6
|
+
return {
|
|
7
|
+
sql: `ALTER TABLE ${escapeId(database)}.${escapeId(tableName)} ` +
|
|
8
|
+
`DELETE WHERE ${whereSQL || "1"}`,
|
|
9
|
+
params: ctx.params,
|
|
10
|
+
};
|
|
11
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// src/build-find-many-query.ts
|
|
2
|
+
import { checkPagination, createBuildContext, NO_LIMIT, tableRef, } from "./sql-shared.js";
|
|
3
|
+
export function buildFindManyQuery(database, tableName, table, args = {}) {
|
|
4
|
+
const ctx = createBuildContext(tableName, table.columns);
|
|
5
|
+
// SELECT
|
|
6
|
+
let selectSQL = "*";
|
|
7
|
+
if (args.select) {
|
|
8
|
+
const select = args.select;
|
|
9
|
+
const keys = Object.keys(select).filter((k) => select[k] === true);
|
|
10
|
+
if (keys.length) {
|
|
11
|
+
selectSQL = keys.map(ctx.colRef).join(", ");
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
const clauses = [
|
|
15
|
+
`SELECT ${selectSQL} FROM ${tableRef(database, tableName, table)}`,
|
|
16
|
+
];
|
|
17
|
+
// WHERE
|
|
18
|
+
if (args.where) {
|
|
19
|
+
const where = ctx.whereSQL(args.where);
|
|
20
|
+
if (where)
|
|
21
|
+
clauses.push(`WHERE ${where}`);
|
|
22
|
+
}
|
|
23
|
+
// ORDER BY
|
|
24
|
+
if (args.orderBy) {
|
|
25
|
+
const list = Array.isArray(args.orderBy)
|
|
26
|
+
? args.orderBy
|
|
27
|
+
: [args.orderBy];
|
|
28
|
+
const orders = [];
|
|
29
|
+
for (const entry of list) {
|
|
30
|
+
for (const key in entry) {
|
|
31
|
+
const dir = entry[key];
|
|
32
|
+
if (dir === undefined)
|
|
33
|
+
continue;
|
|
34
|
+
if (dir !== "asc" && dir !== "desc") {
|
|
35
|
+
throw new Error(`Invalid sort direction "${dir}" for column "${key}"`);
|
|
36
|
+
}
|
|
37
|
+
orders.push(`${ctx.colRef(key)} ${dir.toUpperCase()}`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
if (orders.length)
|
|
41
|
+
clauses.push(`ORDER BY ${orders.join(", ")}`);
|
|
42
|
+
}
|
|
43
|
+
// LIMIT / OFFSET
|
|
44
|
+
if (args.take !== undefined)
|
|
45
|
+
checkPagination("take", args.take);
|
|
46
|
+
if (args.skip !== undefined)
|
|
47
|
+
checkPagination("skip", args.skip);
|
|
48
|
+
if (args.take !== undefined && args.skip !== undefined) {
|
|
49
|
+
clauses.push(`LIMIT ${args.take} OFFSET ${args.skip}`);
|
|
50
|
+
}
|
|
51
|
+
else if (args.take !== undefined) {
|
|
52
|
+
clauses.push(`LIMIT ${args.take}`);
|
|
53
|
+
}
|
|
54
|
+
else if (args.skip !== undefined) {
|
|
55
|
+
clauses.push(`LIMIT ${NO_LIMIT} OFFSET ${args.skip}`);
|
|
56
|
+
}
|
|
57
|
+
return { sql: clauses.join(" "), params: ctx.params };
|
|
58
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { AnyTable } from "./table.js";
|
|
2
|
+
import { WhereInput } from "./where.js";
|
|
3
|
+
import { SQLBuildResult } from "./sql-shared.js";
|
|
4
|
+
export declare function buildLightweightDeleteQuery<T>(database: string, tableName: string, table: AnyTable, where?: WhereInput<T>): SQLBuildResult;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// src/build-lightweight-delete-query.ts
|
|
2
|
+
import { createBuildContext, escapeId } from "./sql-shared.js";
|
|
3
|
+
export function buildLightweightDeleteQuery(database, tableName, table, where) {
|
|
4
|
+
const ctx = createBuildContext(tableName, table.columns);
|
|
5
|
+
const whereSQL = where ? ctx.whereSQL(where) : "";
|
|
6
|
+
return {
|
|
7
|
+
sql: `DELETE FROM ${escapeId(database)}.${escapeId(tableName)} ` +
|
|
8
|
+
`WHERE ${whereSQL || "1"}`,
|
|
9
|
+
params: ctx.params,
|
|
10
|
+
};
|
|
11
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { AnyTable } from "./table.js";
|
|
2
|
+
import { WhereInput } from "./where.js";
|
|
3
|
+
import { SQLBuildResult } from "./sql-shared.js";
|
|
4
|
+
export declare function buildUpdateQuery<T>(database: string, tableName: string, table: AnyTable, where: WhereInput<T> | undefined, data: Record<string, unknown>): SQLBuildResult;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// src/build-update-query.ts
|
|
2
|
+
import { createBuildContext, escapeId, validateUpdateData, } from "./sql-shared.js";
|
|
3
|
+
export function buildUpdateQuery(database, tableName, table, where, data) {
|
|
4
|
+
const keys = validateUpdateData(table, tableName, data);
|
|
5
|
+
const ctx = createBuildContext(tableName, table.columns);
|
|
6
|
+
const sets = keys.map((k) => `${ctx.colRef(k)} = ${ctx.addParam(data[k], ctx.baseType(k))}`);
|
|
7
|
+
const whereSQL = where ? ctx.whereSQL(where) : "";
|
|
8
|
+
return {
|
|
9
|
+
sql: `ALTER TABLE ${escapeId(database)}.${escapeId(tableName)} ` +
|
|
10
|
+
`UPDATE ${sets.join(", ")} WHERE ${whereSQL || "1"}`,
|
|
11
|
+
params: ctx.params,
|
|
12
|
+
};
|
|
13
|
+
}
|
package/dist/client.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { TableClient } from "./table-client.js";
|
|
2
|
-
import { InferRow } from "./table.js";
|
|
2
|
+
import { AnyTable, InferRow } from "./table.js";
|
|
3
3
|
import { MigrationPlan } from "./migrate.js";
|
|
4
4
|
type ClientOptions = {
|
|
5
5
|
url: string;
|
|
@@ -10,7 +10,7 @@ type ClientOptions = {
|
|
|
10
10
|
};
|
|
11
11
|
export declare function createClient<TSchema extends {
|
|
12
12
|
database?: string;
|
|
13
|
-
tables: Record<string,
|
|
13
|
+
tables: Record<string, AnyTable>;
|
|
14
14
|
}>(schema: TSchema, options: ClientOptions): { [K in keyof TSchema["tables"]]: TableClient<InferRow<TSchema["tables"][K]>>; } & {
|
|
15
15
|
$init(): Promise<void>;
|
|
16
16
|
$migrate(): Promise<MigrationPlan>;
|
package/dist/client.js
CHANGED
|
@@ -9,7 +9,7 @@ export function createClient(schema, options) {
|
|
|
9
9
|
url: options.url,
|
|
10
10
|
username: options.username,
|
|
11
11
|
password: options.password,
|
|
12
|
-
database
|
|
12
|
+
database,
|
|
13
13
|
});
|
|
14
14
|
const db = {};
|
|
15
15
|
for (const tableName in schema.tables) {
|
|
@@ -20,11 +20,11 @@ export function createClient(schema, options) {
|
|
|
20
20
|
const rootClient = createCHClient({
|
|
21
21
|
url: options.url,
|
|
22
22
|
username: options.username,
|
|
23
|
-
password: options.password
|
|
23
|
+
password: options.password,
|
|
24
24
|
});
|
|
25
25
|
try {
|
|
26
26
|
await rootClient.command({
|
|
27
|
-
query: `CREATE DATABASE IF NOT EXISTS ${escapeId(database)}
|
|
27
|
+
query: `CREATE DATABASE IF NOT EXISTS ${escapeId(database)}`,
|
|
28
28
|
});
|
|
29
29
|
}
|
|
30
30
|
finally {
|
|
@@ -50,7 +50,7 @@ export function createClient(schema, options) {
|
|
|
50
50
|
ORDER BY table, position
|
|
51
51
|
`,
|
|
52
52
|
query_params: { db: database },
|
|
53
|
-
format: "JSONEachRow"
|
|
53
|
+
format: "JSONEachRow",
|
|
54
54
|
});
|
|
55
55
|
const existing = await result.json();
|
|
56
56
|
const plan = buildMigrationPlan(database, schema.tables, existing);
|
package/dist/column.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ export type Column<T> = {
|
|
|
3
3
|
nullable: boolean;
|
|
4
4
|
__type?: T;
|
|
5
5
|
};
|
|
6
|
-
type NullableColumn<C extends Column<
|
|
6
|
+
type NullableColumn<C extends Column<unknown>> = C extends Column<infer T> ? Column<T | null> : never;
|
|
7
7
|
export declare const t: {
|
|
8
8
|
string: () => Column<string>;
|
|
9
9
|
uint8: () => Column<number>;
|
|
@@ -11,7 +11,7 @@ export declare const t: {
|
|
|
11
11
|
int32: () => Column<number>;
|
|
12
12
|
boolean: () => Column<boolean>;
|
|
13
13
|
datetime: () => Column<Date>;
|
|
14
|
-
nullable<C extends Column<
|
|
15
|
-
array<C extends Column<
|
|
14
|
+
nullable<C extends Column<unknown>>(col: C): NullableColumn<C>;
|
|
15
|
+
array<C extends Column<unknown>>(col: C): Column<C extends Column<infer T> ? T[] : never>;
|
|
16
16
|
};
|
|
17
17
|
export {};
|
package/dist/column.js
CHANGED
|
@@ -5,13 +5,6 @@ function makeColumn(clickhouseType, nullable = false) {
|
|
|
5
5
|
nullable,
|
|
6
6
|
};
|
|
7
7
|
}
|
|
8
|
-
function uint64Id() {
|
|
9
|
-
return {
|
|
10
|
-
clickhouseType: "UInt64",
|
|
11
|
-
nullable: false,
|
|
12
|
-
autoId: true // phantom field для TS
|
|
13
|
-
};
|
|
14
|
-
}
|
|
15
8
|
export const t = {
|
|
16
9
|
string: () => makeColumn("String"),
|
|
17
10
|
uint8: () => makeColumn("UInt8"),
|
|
@@ -27,5 +20,5 @@ export const t = {
|
|
|
27
20
|
},
|
|
28
21
|
array(col) {
|
|
29
22
|
return makeColumn(`Array(${col.clickhouseType})`);
|
|
30
|
-
}
|
|
23
|
+
},
|
|
31
24
|
};
|
package/dist/ddl.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export declare function buildCreateTableSQL(database: string, tableName: string, table:
|
|
1
|
+
import { AnyTable } from "./table.js";
|
|
2
|
+
export declare function buildCreateTableSQL(database: string, tableName: string, table: AnyTable): string;
|
package/dist/ddl.js
CHANGED
|
@@ -2,9 +2,7 @@
|
|
|
2
2
|
import { escapeId } from "./sql.js";
|
|
3
3
|
export function buildCreateTableSQL(database, tableName, table) {
|
|
4
4
|
const columnsSQL = Object.entries(table.columns)
|
|
5
|
-
.map(([name, col]) => {
|
|
6
|
-
return `${escapeId(name)} ${col.clickhouseType}`;
|
|
7
|
-
})
|
|
5
|
+
.map(([name, col]) => `${escapeId(name)} ${col.clickhouseType}`)
|
|
8
6
|
.join(",\n");
|
|
9
7
|
return `
|
|
10
8
|
CREATE TABLE IF NOT EXISTS ${escapeId(database)}.${escapeId(tableName)}
|
package/dist/migrate.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { AnyTable } from "./table.js";
|
|
2
2
|
export type DbColumn = {
|
|
3
3
|
table: string;
|
|
4
4
|
name: string;
|
|
@@ -8,4 +8,4 @@ export type MigrationPlan = {
|
|
|
8
8
|
statements: string[];
|
|
9
9
|
warnings: string[];
|
|
10
10
|
};
|
|
11
|
-
export declare function buildMigrationPlan(database: string, tables: Record<string,
|
|
11
|
+
export declare function buildMigrationPlan(database: string, tables: Record<string, AnyTable>, existing: DbColumn[]): MigrationPlan;
|
package/dist/rows.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export declare function deserializeRows<T>(rows:
|
|
1
|
+
import { AnyColumns } from "./table.js";
|
|
2
|
+
export declare function deserializeRows<T>(rows: Record<string, unknown>[], columns: AnyColumns): T[];
|
package/dist/rows.js
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
// src/rows.ts
|
|
2
2
|
const BIGINT_TYPES = new Set([
|
|
3
|
-
"UInt64",
|
|
3
|
+
"UInt64",
|
|
4
|
+
"Int64",
|
|
5
|
+
"UInt128",
|
|
6
|
+
"Int128",
|
|
7
|
+
"UInt256",
|
|
8
|
+
"Int256",
|
|
4
9
|
]);
|
|
5
10
|
function convertValue(value, chType) {
|
|
6
11
|
if (value === null || value === undefined)
|
|
@@ -10,7 +15,7 @@ function convertValue(value, chType) {
|
|
|
10
15
|
return convertValue(value, nullable[1]);
|
|
11
16
|
const array = /^Array\((.+)\)$/.exec(chType);
|
|
12
17
|
if (array) {
|
|
13
|
-
return value.map(v => convertValue(v, array[1]));
|
|
18
|
+
return value.map((v) => convertValue(v, array[1]));
|
|
14
19
|
}
|
|
15
20
|
if (BIGINT_TYPES.has(chType))
|
|
16
21
|
return BigInt(value);
|
|
@@ -20,7 +25,7 @@ function convertValue(value, chType) {
|
|
|
20
25
|
return value;
|
|
21
26
|
}
|
|
22
27
|
export function deserializeRows(rows, columns) {
|
|
23
|
-
return rows.map(row => {
|
|
28
|
+
return rows.map((row) => {
|
|
24
29
|
const out = {};
|
|
25
30
|
for (const k in row) {
|
|
26
31
|
const col = columns[k];
|
package/dist/schema.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
import { AnyTable } from "./table.js";
|
|
2
|
+
export type ClismaSchema<TTables extends Record<string, AnyTable>> = {
|
|
2
3
|
database?: string;
|
|
3
4
|
tables: TTables;
|
|
4
5
|
};
|
|
5
|
-
export declare function createClisma<TTables extends Record<string,
|
|
6
|
+
export declare function createClisma<TTables extends Record<string, AnyTable>>(schema: ClismaSchema<TTables>): ClismaSchema<TTables>;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { AnyColumns, AnyTable } from "./table.js";
|
|
2
|
+
import { WhereInput } from "./where.js";
|
|
3
|
+
export type SQLBuildResult = {
|
|
4
|
+
sql: string;
|
|
5
|
+
params: Record<string, unknown>;
|
|
6
|
+
};
|
|
7
|
+
export type OrderByInput<T> = {
|
|
8
|
+
[K in keyof T]?: "asc" | "desc";
|
|
9
|
+
};
|
|
10
|
+
export type FindManyArgs<T> = {
|
|
11
|
+
where?: WhereInput<T>;
|
|
12
|
+
select?: {
|
|
13
|
+
[K in keyof T]?: boolean;
|
|
14
|
+
};
|
|
15
|
+
orderBy?: OrderByInput<T> | OrderByInput<T>[];
|
|
16
|
+
take?: number;
|
|
17
|
+
skip?: number;
|
|
18
|
+
};
|
|
19
|
+
export declare function escapeId(name: string): string;
|
|
20
|
+
export declare const NO_LIMIT = "18446744073709551615";
|
|
21
|
+
export declare function createBuildContext(tableName: string, columns: AnyColumns): {
|
|
22
|
+
params: Record<string, unknown>;
|
|
23
|
+
colRef: (key: string) => string;
|
|
24
|
+
baseType: (key: string) => string;
|
|
25
|
+
addParam: (value: unknown, type: string) => string;
|
|
26
|
+
whereSQL: (where: Record<string, unknown>) => string;
|
|
27
|
+
};
|
|
28
|
+
export declare function tableRef(database: string, tableName: string, table: AnyTable): string;
|
|
29
|
+
export declare function checkPagination(name: string, value: number): void;
|
|
30
|
+
export declare function validateUpdateData(table: AnyTable, tableName: string, data: Record<string, unknown>): string[];
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
// src/sql-shared.ts
|
|
2
|
+
// Shared helpers and types for the SQL builders (build-*.ts).
|
|
3
|
+
export function escapeId(name) {
|
|
4
|
+
return "`" + name.replace(/\\/g, "\\\\").replace(/`/g, "\\`") + "`";
|
|
5
|
+
}
|
|
6
|
+
// UInt64 max — used to emulate a bare OFFSET (skip without take)
|
|
7
|
+
export const NO_LIMIT = "18446744073709551615";
|
|
8
|
+
// DateTime is second-precision; the client would send Date with
|
|
9
|
+
// a fractional part, which ClickHouse rejects — send unix seconds.
|
|
10
|
+
function serialize(value) {
|
|
11
|
+
if (value instanceof Date) {
|
|
12
|
+
return Math.floor(value.getTime() / 1000);
|
|
13
|
+
}
|
|
14
|
+
if (Array.isArray(value)) {
|
|
15
|
+
return value.map(serialize);
|
|
16
|
+
}
|
|
17
|
+
return value;
|
|
18
|
+
}
|
|
19
|
+
export function createBuildContext(tableName, columns) {
|
|
20
|
+
const params = {};
|
|
21
|
+
let paramIndex = 0;
|
|
22
|
+
function colRef(key) {
|
|
23
|
+
if (!columns[key]) {
|
|
24
|
+
throw new Error(`Unknown column "${key}" in table "${tableName}"`);
|
|
25
|
+
}
|
|
26
|
+
return escapeId(key);
|
|
27
|
+
}
|
|
28
|
+
function baseType(key) {
|
|
29
|
+
const chType = columns[key].clickhouseType;
|
|
30
|
+
const m = /^Nullable\((.+)\)$/.exec(chType);
|
|
31
|
+
return m ? m[1] : chType;
|
|
32
|
+
}
|
|
33
|
+
function addParam(value, type) {
|
|
34
|
+
const name = `p${paramIndex++}`;
|
|
35
|
+
params[name] = serialize(value);
|
|
36
|
+
return `{${name}:${type}}`;
|
|
37
|
+
}
|
|
38
|
+
function buildFieldFilter(key, filter) {
|
|
39
|
+
const col = colRef(key);
|
|
40
|
+
const conditions = [];
|
|
41
|
+
for (const op in filter) {
|
|
42
|
+
const value = filter[op];
|
|
43
|
+
if (value === undefined)
|
|
44
|
+
continue;
|
|
45
|
+
switch (op) {
|
|
46
|
+
case "equals":
|
|
47
|
+
conditions.push(value === null
|
|
48
|
+
? `${col} IS NULL`
|
|
49
|
+
: `${col} = ${addParam(value, baseType(key))}`);
|
|
50
|
+
break;
|
|
51
|
+
case "not":
|
|
52
|
+
conditions.push(value === null
|
|
53
|
+
? `${col} IS NOT NULL`
|
|
54
|
+
: `${col} != ${addParam(value, baseType(key))}`);
|
|
55
|
+
break;
|
|
56
|
+
case "in":
|
|
57
|
+
conditions.push(value.length === 0
|
|
58
|
+
? "0"
|
|
59
|
+
: `${col} IN ${addParam(value, `Array(${baseType(key)})`)}`);
|
|
60
|
+
break;
|
|
61
|
+
case "notIn":
|
|
62
|
+
if (value.length > 0) {
|
|
63
|
+
conditions.push(`${col} NOT IN ${addParam(value, `Array(${baseType(key)})`)}`);
|
|
64
|
+
}
|
|
65
|
+
break;
|
|
66
|
+
case "lt":
|
|
67
|
+
case "lte":
|
|
68
|
+
case "gt":
|
|
69
|
+
case "gte": {
|
|
70
|
+
const cmp = { lt: "<", lte: "<=", gt: ">", gte: ">=" }[op];
|
|
71
|
+
conditions.push(`${col} ${cmp} ${addParam(value, baseType(key))}`);
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
case "contains":
|
|
75
|
+
conditions.push(`position(${col}, ${addParam(value, "String")}) > 0`);
|
|
76
|
+
break;
|
|
77
|
+
case "startsWith":
|
|
78
|
+
conditions.push(`startsWith(${col}, ${addParam(value, "String")})`);
|
|
79
|
+
break;
|
|
80
|
+
case "endsWith":
|
|
81
|
+
conditions.push(`endsWith(${col}, ${addParam(value, "String")})`);
|
|
82
|
+
break;
|
|
83
|
+
default:
|
|
84
|
+
throw new Error(`Unknown operator "${op}" for column "${key}"`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return conditions;
|
|
88
|
+
}
|
|
89
|
+
function isFilterObject(value) {
|
|
90
|
+
return (value !== null &&
|
|
91
|
+
typeof value === "object" &&
|
|
92
|
+
!(value instanceof Date) &&
|
|
93
|
+
!Array.isArray(value));
|
|
94
|
+
}
|
|
95
|
+
function whereSQL(where) {
|
|
96
|
+
const parts = [];
|
|
97
|
+
for (const key in where) {
|
|
98
|
+
const value = where[key];
|
|
99
|
+
if (value === undefined)
|
|
100
|
+
continue;
|
|
101
|
+
if (key === "AND") {
|
|
102
|
+
const list = (Array.isArray(value) ? value : [value]);
|
|
103
|
+
for (const w of list) {
|
|
104
|
+
const c = whereSQL(w);
|
|
105
|
+
if (c)
|
|
106
|
+
parts.push(`(${c})`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
else if (key === "OR") {
|
|
110
|
+
const conds = value
|
|
111
|
+
.map((w) => whereSQL(w))
|
|
112
|
+
.filter(Boolean)
|
|
113
|
+
.map((c) => `(${c})`);
|
|
114
|
+
if (conds.length)
|
|
115
|
+
parts.push(`(${conds.join(" OR ")})`);
|
|
116
|
+
}
|
|
117
|
+
else if (key === "NOT") {
|
|
118
|
+
const list = (Array.isArray(value) ? value : [value]);
|
|
119
|
+
for (const w of list) {
|
|
120
|
+
const c = whereSQL(w);
|
|
121
|
+
if (c)
|
|
122
|
+
parts.push(`NOT (${c})`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
else if (value === null) {
|
|
126
|
+
parts.push(`${colRef(key)} IS NULL`);
|
|
127
|
+
}
|
|
128
|
+
else if (isFilterObject(value)) {
|
|
129
|
+
parts.push(...buildFieldFilter(key, value));
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
parts.push(`${colRef(key)} = ${addParam(value, baseType(key))}`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return parts.join(" AND ");
|
|
136
|
+
}
|
|
137
|
+
return { params, colRef, baseType, addParam, whereSQL };
|
|
138
|
+
}
|
|
139
|
+
export function tableRef(database, tableName, table) {
|
|
140
|
+
const ref = `${escapeId(database)}.${escapeId(tableName)}`;
|
|
141
|
+
// replacing tables must read the latest row versions
|
|
142
|
+
return table.mutations === "replacing" ? `${ref} FINAL` : ref;
|
|
143
|
+
}
|
|
144
|
+
export function checkPagination(name, value) {
|
|
145
|
+
if (!Number.isInteger(value) || value < 0) {
|
|
146
|
+
throw new Error(`"${name}" must be a non-negative integer, got: ${value}`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
// shared by buildUpdateQuery and the replacing-strategy update path
|
|
150
|
+
export function validateUpdateData(table, tableName, data) {
|
|
151
|
+
const keys = Object.keys(data).filter((k) => data[k] !== undefined);
|
|
152
|
+
if (keys.length === 0) {
|
|
153
|
+
throw new Error(`updateMany requires non-empty data`);
|
|
154
|
+
}
|
|
155
|
+
for (const key of keys) {
|
|
156
|
+
if (table.orderBy.includes(key)) {
|
|
157
|
+
throw new Error(`Cannot update ORDER BY key column "${key}" ` +
|
|
158
|
+
`of table "${tableName}"`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return keys;
|
|
162
|
+
}
|
package/dist/sql.d.ts
CHANGED
|
@@ -1,25 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
export
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
};
|
|
7
|
-
export type OrderByInput<T> = {
|
|
8
|
-
[K in keyof T]?: "asc" | "desc";
|
|
9
|
-
};
|
|
10
|
-
export type FindManyArgs<T> = {
|
|
11
|
-
where?: WhereInput<T>;
|
|
12
|
-
select?: {
|
|
13
|
-
[K in keyof T]?: boolean;
|
|
14
|
-
};
|
|
15
|
-
orderBy?: OrderByInput<T> | OrderByInput<T>[];
|
|
16
|
-
take?: number;
|
|
17
|
-
skip?: number;
|
|
18
|
-
};
|
|
19
|
-
export declare function escapeId(name: string): string;
|
|
20
|
-
export declare function buildFindManyQuery(database: string, tableName: string, table: TableDefinition<any>, args?: FindManyArgs<any>): SQLBuildResult;
|
|
21
|
-
export declare function buildCountQuery(database: string, tableName: string, table: TableDefinition<any>, where?: WhereInput<any>): SQLBuildResult;
|
|
22
|
-
export declare function validateUpdateData(table: TableDefinition<any>, tableName: string, data: Record<string, any>): string[];
|
|
23
|
-
export declare function buildUpdateQuery(database: string, tableName: string, table: TableDefinition<any>, where: WhereInput<any> | undefined, data: Record<string, any>): SQLBuildResult;
|
|
24
|
-
export declare function buildDeleteQuery(database: string, tableName: string, table: TableDefinition<any>, where?: WhereInput<any>): SQLBuildResult;
|
|
25
|
-
export declare function buildLightweightDeleteQuery(database: string, tableName: string, table: TableDefinition<any>, where?: WhereInput<any>): SQLBuildResult;
|
|
1
|
+
export { escapeId, validateUpdateData, type FindManyArgs, type OrderByInput, type SQLBuildResult, } from "./sql-shared.js";
|
|
2
|
+
export { buildFindManyQuery } from "./build-find-many-query.js";
|
|
3
|
+
export { buildCountQuery } from "./build-count-query.js";
|
|
4
|
+
export { buildUpdateQuery } from "./build-update-query.js";
|
|
5
|
+
export { buildDeleteQuery } from "./build-delete-query.js";
|
|
6
|
+
export { buildLightweightDeleteQuery } from "./build-lightweight-delete-query.js";
|
package/dist/sql.js
CHANGED
|
@@ -1,255 +1,9 @@
|
|
|
1
1
|
// src/sql.ts
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
if (value instanceof Date) {
|
|
11
|
-
return Math.floor(value.getTime() / 1000);
|
|
12
|
-
}
|
|
13
|
-
if (Array.isArray(value)) {
|
|
14
|
-
return value.map(serialize);
|
|
15
|
-
}
|
|
16
|
-
return value;
|
|
17
|
-
}
|
|
18
|
-
function createBuildContext(tableName, columns) {
|
|
19
|
-
const params = {};
|
|
20
|
-
let paramIndex = 0;
|
|
21
|
-
function colRef(key) {
|
|
22
|
-
if (!columns[key]) {
|
|
23
|
-
throw new Error(`Unknown column "${key}" in table "${tableName}"`);
|
|
24
|
-
}
|
|
25
|
-
return escapeId(key);
|
|
26
|
-
}
|
|
27
|
-
function baseType(key) {
|
|
28
|
-
const chType = columns[key].clickhouseType;
|
|
29
|
-
const m = /^Nullable\((.+)\)$/.exec(chType);
|
|
30
|
-
return m ? m[1] : chType;
|
|
31
|
-
}
|
|
32
|
-
function addParam(value, type) {
|
|
33
|
-
const name = `p${paramIndex++}`;
|
|
34
|
-
params[name] = serialize(value);
|
|
35
|
-
return `{${name}:${type}}`;
|
|
36
|
-
}
|
|
37
|
-
function buildFieldFilter(key, filter) {
|
|
38
|
-
const col = colRef(key);
|
|
39
|
-
const conditions = [];
|
|
40
|
-
for (const op in filter) {
|
|
41
|
-
const value = filter[op];
|
|
42
|
-
if (value === undefined)
|
|
43
|
-
continue;
|
|
44
|
-
switch (op) {
|
|
45
|
-
case "equals":
|
|
46
|
-
conditions.push(value === null
|
|
47
|
-
? `${col} IS NULL`
|
|
48
|
-
: `${col} = ${addParam(value, baseType(key))}`);
|
|
49
|
-
break;
|
|
50
|
-
case "not":
|
|
51
|
-
conditions.push(value === null
|
|
52
|
-
? `${col} IS NOT NULL`
|
|
53
|
-
: `${col} != ${addParam(value, baseType(key))}`);
|
|
54
|
-
break;
|
|
55
|
-
case "in":
|
|
56
|
-
conditions.push(value.length === 0
|
|
57
|
-
? "0"
|
|
58
|
-
: `${col} IN ${addParam(value, `Array(${baseType(key)})`)}`);
|
|
59
|
-
break;
|
|
60
|
-
case "notIn":
|
|
61
|
-
if (value.length > 0) {
|
|
62
|
-
conditions.push(`${col} NOT IN ${addParam(value, `Array(${baseType(key)})`)}`);
|
|
63
|
-
}
|
|
64
|
-
break;
|
|
65
|
-
case "lt":
|
|
66
|
-
case "lte":
|
|
67
|
-
case "gt":
|
|
68
|
-
case "gte": {
|
|
69
|
-
const cmp = { lt: "<", lte: "<=", gt: ">", gte: ">=" }[op];
|
|
70
|
-
conditions.push(`${col} ${cmp} ${addParam(value, baseType(key))}`);
|
|
71
|
-
break;
|
|
72
|
-
}
|
|
73
|
-
case "contains":
|
|
74
|
-
conditions.push(`position(${col}, ${addParam(value, "String")}) > 0`);
|
|
75
|
-
break;
|
|
76
|
-
case "startsWith":
|
|
77
|
-
conditions.push(`startsWith(${col}, ${addParam(value, "String")})`);
|
|
78
|
-
break;
|
|
79
|
-
case "endsWith":
|
|
80
|
-
conditions.push(`endsWith(${col}, ${addParam(value, "String")})`);
|
|
81
|
-
break;
|
|
82
|
-
default:
|
|
83
|
-
throw new Error(`Unknown operator "${op}" for column "${key}"`);
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
return conditions;
|
|
87
|
-
}
|
|
88
|
-
function isFilterObject(value) {
|
|
89
|
-
return (value !== null &&
|
|
90
|
-
typeof value === "object" &&
|
|
91
|
-
!(value instanceof Date) &&
|
|
92
|
-
!Array.isArray(value));
|
|
93
|
-
}
|
|
94
|
-
function whereSQL(where) {
|
|
95
|
-
const parts = [];
|
|
96
|
-
for (const key in where) {
|
|
97
|
-
const value = where[key];
|
|
98
|
-
if (value === undefined)
|
|
99
|
-
continue;
|
|
100
|
-
if (key === "AND") {
|
|
101
|
-
const list = Array.isArray(value) ? value : [value];
|
|
102
|
-
for (const w of list) {
|
|
103
|
-
const c = whereSQL(w);
|
|
104
|
-
if (c)
|
|
105
|
-
parts.push(`(${c})`);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
else if (key === "OR") {
|
|
109
|
-
const conds = value
|
|
110
|
-
.map((w) => whereSQL(w))
|
|
111
|
-
.filter(Boolean)
|
|
112
|
-
.map((c) => `(${c})`);
|
|
113
|
-
if (conds.length)
|
|
114
|
-
parts.push(`(${conds.join(" OR ")})`);
|
|
115
|
-
}
|
|
116
|
-
else if (key === "NOT") {
|
|
117
|
-
const list = Array.isArray(value) ? value : [value];
|
|
118
|
-
for (const w of list) {
|
|
119
|
-
const c = whereSQL(w);
|
|
120
|
-
if (c)
|
|
121
|
-
parts.push(`NOT (${c})`);
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
else if (value === null) {
|
|
125
|
-
parts.push(`${colRef(key)} IS NULL`);
|
|
126
|
-
}
|
|
127
|
-
else if (isFilterObject(value)) {
|
|
128
|
-
parts.push(...buildFieldFilter(key, value));
|
|
129
|
-
}
|
|
130
|
-
else {
|
|
131
|
-
parts.push(`${colRef(key)} = ${addParam(value, baseType(key))}`);
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
return parts.join(" AND ");
|
|
135
|
-
}
|
|
136
|
-
return { params, colRef, baseType, addParam, whereSQL };
|
|
137
|
-
}
|
|
138
|
-
function tableRef(database, tableName, table) {
|
|
139
|
-
const ref = `${escapeId(database)}.${escapeId(tableName)}`;
|
|
140
|
-
// replacing tables must read the latest row versions
|
|
141
|
-
return table.mutations === "replacing" ? `${ref} FINAL` : ref;
|
|
142
|
-
}
|
|
143
|
-
function checkPagination(name, value) {
|
|
144
|
-
if (!Number.isInteger(value) || value < 0) {
|
|
145
|
-
throw new Error(`"${name}" must be a non-negative integer, got: ${value}`);
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
export function buildFindManyQuery(database, tableName, table, args = {}) {
|
|
149
|
-
const ctx = createBuildContext(tableName, table.columns);
|
|
150
|
-
// SELECT
|
|
151
|
-
let selectSQL = "*";
|
|
152
|
-
if (args.select) {
|
|
153
|
-
const keys = Object.keys(args.select).filter(k => args.select[k] === true);
|
|
154
|
-
if (keys.length) {
|
|
155
|
-
selectSQL = keys.map(ctx.colRef).join(", ");
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
const clauses = [
|
|
159
|
-
`SELECT ${selectSQL} FROM ${tableRef(database, tableName, table)}`
|
|
160
|
-
];
|
|
161
|
-
// WHERE
|
|
162
|
-
if (args.where) {
|
|
163
|
-
const where = ctx.whereSQL(args.where);
|
|
164
|
-
if (where)
|
|
165
|
-
clauses.push(`WHERE ${where}`);
|
|
166
|
-
}
|
|
167
|
-
// ORDER BY
|
|
168
|
-
if (args.orderBy) {
|
|
169
|
-
const list = Array.isArray(args.orderBy)
|
|
170
|
-
? args.orderBy
|
|
171
|
-
: [args.orderBy];
|
|
172
|
-
const orders = [];
|
|
173
|
-
for (const entry of list) {
|
|
174
|
-
for (const key in entry) {
|
|
175
|
-
const dir = entry[key];
|
|
176
|
-
if (dir === undefined)
|
|
177
|
-
continue;
|
|
178
|
-
if (dir !== "asc" && dir !== "desc") {
|
|
179
|
-
throw new Error(`Invalid sort direction "${dir}" for column "${key}"`);
|
|
180
|
-
}
|
|
181
|
-
orders.push(`${ctx.colRef(key)} ${dir.toUpperCase()}`);
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
if (orders.length)
|
|
185
|
-
clauses.push(`ORDER BY ${orders.join(", ")}`);
|
|
186
|
-
}
|
|
187
|
-
// LIMIT / OFFSET
|
|
188
|
-
if (args.take !== undefined)
|
|
189
|
-
checkPagination("take", args.take);
|
|
190
|
-
if (args.skip !== undefined)
|
|
191
|
-
checkPagination("skip", args.skip);
|
|
192
|
-
if (args.take !== undefined && args.skip !== undefined) {
|
|
193
|
-
clauses.push(`LIMIT ${args.take} OFFSET ${args.skip}`);
|
|
194
|
-
}
|
|
195
|
-
else if (args.take !== undefined) {
|
|
196
|
-
clauses.push(`LIMIT ${args.take}`);
|
|
197
|
-
}
|
|
198
|
-
else if (args.skip !== undefined) {
|
|
199
|
-
clauses.push(`LIMIT ${NO_LIMIT} OFFSET ${args.skip}`);
|
|
200
|
-
}
|
|
201
|
-
return { sql: clauses.join(" "), params: ctx.params };
|
|
202
|
-
}
|
|
203
|
-
export function buildCountQuery(database, tableName, table, where) {
|
|
204
|
-
const inner = buildFindManyQuery(database, tableName, table, { where });
|
|
205
|
-
// with no select arg the query starts with "SELECT * FROM ..." —
|
|
206
|
-
// swap the projection for count()
|
|
207
|
-
const from = inner.sql.indexOf(" FROM ");
|
|
208
|
-
return {
|
|
209
|
-
sql: "SELECT count() AS count" + inner.sql.slice(from),
|
|
210
|
-
params: inner.params
|
|
211
|
-
};
|
|
212
|
-
}
|
|
213
|
-
// shared by buildUpdateQuery and the replacing-strategy update path
|
|
214
|
-
export function validateUpdateData(table, tableName, data) {
|
|
215
|
-
const keys = Object.keys(data).filter(k => data[k] !== undefined);
|
|
216
|
-
if (keys.length === 0) {
|
|
217
|
-
throw new Error(`updateMany requires non-empty data`);
|
|
218
|
-
}
|
|
219
|
-
for (const key of keys) {
|
|
220
|
-
if (table.orderBy.includes(key)) {
|
|
221
|
-
throw new Error(`Cannot update ORDER BY key column "${key}" ` +
|
|
222
|
-
`of table "${tableName}"`);
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
return keys;
|
|
226
|
-
}
|
|
227
|
-
export function buildUpdateQuery(database, tableName, table, where, data) {
|
|
228
|
-
const keys = validateUpdateData(table, tableName, data);
|
|
229
|
-
const ctx = createBuildContext(tableName, table.columns);
|
|
230
|
-
const sets = keys.map(k => `${ctx.colRef(k)} = ${ctx.addParam(data[k], ctx.baseType(k))}`);
|
|
231
|
-
const whereSQL = where ? ctx.whereSQL(where) : "";
|
|
232
|
-
return {
|
|
233
|
-
sql: `ALTER TABLE ${escapeId(database)}.${escapeId(tableName)} ` +
|
|
234
|
-
`UPDATE ${sets.join(", ")} WHERE ${whereSQL || "1"}`,
|
|
235
|
-
params: ctx.params
|
|
236
|
-
};
|
|
237
|
-
}
|
|
238
|
-
export function buildDeleteQuery(database, tableName, table, where) {
|
|
239
|
-
const ctx = createBuildContext(tableName, table.columns);
|
|
240
|
-
const whereSQL = where ? ctx.whereSQL(where) : "";
|
|
241
|
-
return {
|
|
242
|
-
sql: `ALTER TABLE ${escapeId(database)}.${escapeId(tableName)} ` +
|
|
243
|
-
`DELETE WHERE ${whereSQL || "1"}`,
|
|
244
|
-
params: ctx.params
|
|
245
|
-
};
|
|
246
|
-
}
|
|
247
|
-
export function buildLightweightDeleteQuery(database, tableName, table, where) {
|
|
248
|
-
const ctx = createBuildContext(tableName, table.columns);
|
|
249
|
-
const whereSQL = where ? ctx.whereSQL(where) : "";
|
|
250
|
-
return {
|
|
251
|
-
sql: `DELETE FROM ${escapeId(database)}.${escapeId(tableName)} ` +
|
|
252
|
-
`WHERE ${whereSQL || "1"}`,
|
|
253
|
-
params: ctx.params
|
|
254
|
-
};
|
|
255
|
-
}
|
|
2
|
+
// Public surface of the SQL builders; implementation is split by function
|
|
3
|
+
// (one function per file), shared helpers live in sql-shared.ts.
|
|
4
|
+
export { escapeId, validateUpdateData, } from "./sql-shared.js";
|
|
5
|
+
export { buildFindManyQuery } from "./build-find-many-query.js";
|
|
6
|
+
export { buildCountQuery } from "./build-count-query.js";
|
|
7
|
+
export { buildUpdateQuery } from "./build-update-query.js";
|
|
8
|
+
export { buildDeleteQuery } from "./build-delete-query.js";
|
|
9
|
+
export { buildLightweightDeleteQuery } from "./build-lightweight-delete-query.js";
|
package/dist/table-client.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ClickHouseClient } from "@clickhouse/client";
|
|
2
|
-
import {
|
|
2
|
+
import { AnyTable } from "./table.js";
|
|
3
3
|
import { Simplify } from "./types.js";
|
|
4
4
|
import { WhereInput } from "./where.js";
|
|
5
5
|
import { FindManyArgs } from "./sql.js";
|
|
@@ -15,12 +15,12 @@ type OptionalCreateKeys<T> = NullableKeys<T> | ("id" & keyof T);
|
|
|
15
15
|
export type CreateData<T> = Simplify<Omit<T, OptionalCreateKeys<T>> & {
|
|
16
16
|
[K in OptionalCreateKeys<T>]?: T[K];
|
|
17
17
|
}>;
|
|
18
|
-
export declare class TableClient<T
|
|
18
|
+
export declare class TableClient<T extends Record<string, unknown>> {
|
|
19
19
|
private database;
|
|
20
20
|
private tableName;
|
|
21
21
|
private table;
|
|
22
22
|
private client;
|
|
23
|
-
constructor(database: string, tableName: string, table:
|
|
23
|
+
constructor(database: string, tableName: string, table: AnyTable, client: ClickHouseClient);
|
|
24
24
|
private get tableRef();
|
|
25
25
|
private runQuery;
|
|
26
26
|
findMany<A extends FindManyArgs<T>>(args?: A): Promise<FindManyResult<T, A>[]>;
|
package/dist/table-client.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/table-client.ts
|
|
2
|
-
import { buildFindManyQuery, buildCountQuery, buildUpdateQuery, buildDeleteQuery, buildLightweightDeleteQuery, validateUpdateData, escapeId } from "./sql.js";
|
|
2
|
+
import { buildFindManyQuery, buildCountQuery, buildUpdateQuery, buildDeleteQuery, buildLightweightDeleteQuery, validateUpdateData, escapeId, } from "./sql.js";
|
|
3
3
|
import { deserializeRows } from "./rows.js";
|
|
4
4
|
// process-unique id: ms timestamp in the high bits + 20-bit counter
|
|
5
5
|
let idCounter = 0n;
|
|
@@ -31,8 +31,8 @@ export class TableClient {
|
|
|
31
31
|
date_time_output_format: "iso",
|
|
32
32
|
// force 64-bit ints to arrive as strings so BigInt() is
|
|
33
33
|
// lossless even if the server default was changed
|
|
34
|
-
output_format_json_quote_64bit_integers: 1
|
|
35
|
-
}
|
|
34
|
+
output_format_json_quote_64bit_integers: 1,
|
|
35
|
+
},
|
|
36
36
|
});
|
|
37
37
|
return result.json();
|
|
38
38
|
}
|
|
@@ -42,7 +42,10 @@ export class TableClient {
|
|
|
42
42
|
return deserializeRows(rows, this.table.columns);
|
|
43
43
|
}
|
|
44
44
|
async findFirst(args) {
|
|
45
|
-
const rows = await this.findMany({
|
|
45
|
+
const rows = await this.findMany({
|
|
46
|
+
...args,
|
|
47
|
+
take: 1,
|
|
48
|
+
});
|
|
46
49
|
return rows[0] ?? null;
|
|
47
50
|
}
|
|
48
51
|
async count(args) {
|
|
@@ -65,11 +68,13 @@ export class TableClient {
|
|
|
65
68
|
// subsequent read returns: omitted nullable columns are null,
|
|
66
69
|
// DateTime values are stored with second precision
|
|
67
70
|
for (const colName in this.table.columns) {
|
|
68
|
-
if (full[colName] === undefined &&
|
|
71
|
+
if (full[colName] === undefined &&
|
|
72
|
+
this.table.columns[colName].nullable) {
|
|
69
73
|
full[colName] = null;
|
|
70
74
|
}
|
|
71
75
|
else if (full[colName] instanceof Date) {
|
|
72
|
-
full[colName] = new Date(Math.floor(full[colName].getTime() / 1000) *
|
|
76
|
+
full[colName] = new Date(Math.floor(full[colName].getTime() / 1000) *
|
|
77
|
+
1000);
|
|
73
78
|
}
|
|
74
79
|
}
|
|
75
80
|
const obj = {};
|
|
@@ -103,8 +108,8 @@ export class TableClient {
|
|
|
103
108
|
// with wait_for_async_insert=0 on the server, inserts are
|
|
104
109
|
// acked before flush: errors are swallowed and rows are not
|
|
105
110
|
// immediately readable — force read-your-writes semantics
|
|
106
|
-
wait_for_async_insert: 1
|
|
107
|
-
}
|
|
111
|
+
wait_for_async_insert: 1,
|
|
112
|
+
},
|
|
108
113
|
});
|
|
109
114
|
}
|
|
110
115
|
async insert(rows) {
|
|
@@ -133,7 +138,7 @@ export class TableClient {
|
|
|
133
138
|
const patch = {};
|
|
134
139
|
for (const k of keys)
|
|
135
140
|
patch[k] = args.data[k];
|
|
136
|
-
const { wire } = this.prepareRows(rows.map(r => ({ ...r, ...patch })));
|
|
141
|
+
const { wire } = this.prepareRows(rows.map((r) => ({ ...r, ...patch })));
|
|
137
142
|
await this.insertWire(wire);
|
|
138
143
|
return { count: rows.length };
|
|
139
144
|
}
|
|
@@ -148,8 +153,8 @@ export class TableClient {
|
|
|
148
153
|
query_params: params,
|
|
149
154
|
clickhouse_settings: {
|
|
150
155
|
// wait for the mutation so subsequent reads see the change
|
|
151
|
-
mutations_sync: "1"
|
|
152
|
-
}
|
|
156
|
+
mutations_sync: "1",
|
|
157
|
+
},
|
|
153
158
|
});
|
|
154
159
|
return { count };
|
|
155
160
|
}
|
|
@@ -162,7 +167,7 @@ export class TableClient {
|
|
|
162
167
|
await this.client.command({
|
|
163
168
|
query: sql,
|
|
164
169
|
query_params: params,
|
|
165
|
-
clickhouse_settings: { mutations_sync: "1" }
|
|
170
|
+
clickhouse_settings: { mutations_sync: "1" },
|
|
166
171
|
});
|
|
167
172
|
}
|
|
168
173
|
else {
|
|
@@ -171,7 +176,7 @@ export class TableClient {
|
|
|
171
176
|
await this.client.command({
|
|
172
177
|
query: sql,
|
|
173
178
|
query_params: params,
|
|
174
|
-
clickhouse_settings: { lightweight_deletes_sync: "2" }
|
|
179
|
+
clickhouse_settings: { lightweight_deletes_sync: "2" },
|
|
175
180
|
});
|
|
176
181
|
}
|
|
177
182
|
return { count };
|
package/dist/table.d.ts
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { Column } from "./column.js";
|
|
2
2
|
import { Simplify } from "./types.js";
|
|
3
3
|
export type MutationStrategy = "alter" | "lightweight" | "replacing";
|
|
4
|
-
export type TableDefinition<TColumns extends Record<string, Column<
|
|
4
|
+
export type TableDefinition<TColumns extends Record<string, Column<unknown>>> = {
|
|
5
5
|
engine: string;
|
|
6
6
|
orderBy: string[];
|
|
7
7
|
mutations?: MutationStrategy;
|
|
8
8
|
columns: TColumns;
|
|
9
9
|
};
|
|
10
|
-
export
|
|
10
|
+
export type AnyColumns = Record<string, Column<unknown>>;
|
|
11
|
+
export type AnyTable = TableDefinition<AnyColumns>;
|
|
12
|
+
export declare function table<TColumns extends Record<string, Column<unknown>>>(def: TableDefinition<TColumns>): TableDefinition<TColumns>;
|
|
11
13
|
export type InferRow<TTable> = TTable extends TableDefinition<infer TColumns> ? Simplify<{
|
|
12
14
|
[K in keyof TColumns]: TColumns[K] extends Column<infer T> ? T : never;
|
|
13
15
|
}> : never;
|
package/dist/where.d.ts
CHANGED
|
@@ -14,11 +14,11 @@ export type ScalarFilter<T> = {
|
|
|
14
14
|
not?: T | null;
|
|
15
15
|
in?: T[];
|
|
16
16
|
notIn?: T[];
|
|
17
|
-
} & (T extends number | bigint | Date | string ? ComparableFilter<T> :
|
|
17
|
+
} & (T extends number | bigint | Date | string ? ComparableFilter<T> : unknown) & (T extends string ? StringFilter : unknown);
|
|
18
18
|
export type WhereInput<T> = {
|
|
19
19
|
AND?: WhereInput<T> | WhereInput<T>[];
|
|
20
20
|
OR?: WhereInput<T>[];
|
|
21
21
|
NOT?: WhereInput<T> | WhereInput<T>[];
|
|
22
22
|
} & {
|
|
23
|
-
[K in keyof T]?: T[K] extends readonly
|
|
23
|
+
[K in keyof T]?: T[K] extends readonly unknown[] ? never : T[K] | ScalarFilter<NonNullable<T[K]>> | null;
|
|
24
24
|
};
|