@quark-fw/clisma 0.1.6 → 0.1.8

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.
@@ -0,0 +1,4 @@
1
+ import { TableDefinition } from "./table.js";
2
+ import { WhereInput } from "./where.js";
3
+ import { SQLBuildResult } from "./sql-shared.js";
4
+ export declare function buildCountQuery(database: string, tableName: string, table: TableDefinition<any>, where?: WhereInput<any>): SQLBuildResult;
@@ -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,4 @@
1
+ import { TableDefinition } from "./table.js";
2
+ import { WhereInput } from "./where.js";
3
+ import { SQLBuildResult } from "./sql-shared.js";
4
+ export declare function buildDeleteQuery(database: string, tableName: string, table: TableDefinition<any>, where?: WhereInput<any>): SQLBuildResult;
@@ -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,3 @@
1
+ import { TableDefinition } from "./table.js";
2
+ import { FindManyArgs, SQLBuildResult } from "./sql-shared.js";
3
+ export declare function buildFindManyQuery(database: string, tableName: string, table: TableDefinition<any>, args?: FindManyArgs<any>): SQLBuildResult;
@@ -0,0 +1,57 @@
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 keys = Object.keys(args.select).filter((k) => args.select[k] === true);
9
+ if (keys.length) {
10
+ selectSQL = keys.map(ctx.colRef).join(", ");
11
+ }
12
+ }
13
+ const clauses = [
14
+ `SELECT ${selectSQL} FROM ${tableRef(database, tableName, table)}`,
15
+ ];
16
+ // WHERE
17
+ if (args.where) {
18
+ const where = ctx.whereSQL(args.where);
19
+ if (where)
20
+ clauses.push(`WHERE ${where}`);
21
+ }
22
+ // ORDER BY
23
+ if (args.orderBy) {
24
+ const list = Array.isArray(args.orderBy)
25
+ ? args.orderBy
26
+ : [args.orderBy];
27
+ const orders = [];
28
+ for (const entry of list) {
29
+ for (const key in entry) {
30
+ const dir = entry[key];
31
+ if (dir === undefined)
32
+ continue;
33
+ if (dir !== "asc" && dir !== "desc") {
34
+ throw new Error(`Invalid sort direction "${dir}" for column "${key}"`);
35
+ }
36
+ orders.push(`${ctx.colRef(key)} ${dir.toUpperCase()}`);
37
+ }
38
+ }
39
+ if (orders.length)
40
+ clauses.push(`ORDER BY ${orders.join(", ")}`);
41
+ }
42
+ // LIMIT / OFFSET
43
+ if (args.take !== undefined)
44
+ checkPagination("take", args.take);
45
+ if (args.skip !== undefined)
46
+ checkPagination("skip", args.skip);
47
+ if (args.take !== undefined && args.skip !== undefined) {
48
+ clauses.push(`LIMIT ${args.take} OFFSET ${args.skip}`);
49
+ }
50
+ else if (args.take !== undefined) {
51
+ clauses.push(`LIMIT ${args.take}`);
52
+ }
53
+ else if (args.skip !== undefined) {
54
+ clauses.push(`LIMIT ${NO_LIMIT} OFFSET ${args.skip}`);
55
+ }
56
+ return { sql: clauses.join(" "), params: ctx.params };
57
+ }
@@ -0,0 +1,4 @@
1
+ import { TableDefinition } from "./table.js";
2
+ import { WhereInput } from "./where.js";
3
+ import { SQLBuildResult } from "./sql-shared.js";
4
+ export declare function buildLightweightDeleteQuery(database: string, tableName: string, table: TableDefinition<any>, where?: WhereInput<any>): 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 { TableDefinition } from "./table.js";
2
+ import { WhereInput } from "./where.js";
3
+ import { SQLBuildResult } from "./sql-shared.js";
4
+ export declare function buildUpdateQuery(database: string, tableName: string, table: TableDefinition<any>, where: WhereInput<any> | undefined, data: Record<string, any>): 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.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.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/rows.js CHANGED
@@ -1,6 +1,11 @@
1
1
  // src/rows.ts
2
2
  const BIGINT_TYPES = new Set([
3
- "UInt64", "Int64", "UInt128", "Int128", "UInt256", "Int256"
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];
@@ -0,0 +1,31 @@
1
+ import { Column } from "./column.js";
2
+ import { TableDefinition } from "./table.js";
3
+ import { WhereInput } from "./where.js";
4
+ export type SQLBuildResult = {
5
+ sql: string;
6
+ params: Record<string, any>;
7
+ };
8
+ export type OrderByInput<T> = {
9
+ [K in keyof T]?: "asc" | "desc";
10
+ };
11
+ export type FindManyArgs<T> = {
12
+ where?: WhereInput<T>;
13
+ select?: {
14
+ [K in keyof T]?: boolean;
15
+ };
16
+ orderBy?: OrderByInput<T> | OrderByInput<T>[];
17
+ take?: number;
18
+ skip?: number;
19
+ };
20
+ export declare function escapeId(name: string): string;
21
+ export declare const NO_LIMIT = "18446744073709551615";
22
+ export declare function createBuildContext(tableName: string, columns: Record<string, Column<any>>): {
23
+ params: Record<string, any>;
24
+ colRef: (key: string) => string;
25
+ baseType: (key: string) => string;
26
+ addParam: (value: any, type: string) => string;
27
+ whereSQL: (where: any) => string;
28
+ };
29
+ export declare function tableRef(database: string, tableName: string, table: TableDefinition<any>): string;
30
+ export declare function checkPagination(name: string, value: any): void;
31
+ export declare function validateUpdateData(table: TableDefinition<any>, tableName: string, data: Record<string, any>): 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
- import { TableDefinition } from "./table.js";
2
- import { WhereInput } from "./where.js";
3
- export type SQLBuildResult = {
4
- sql: string;
5
- params: Record<string, any>;
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
- export function escapeId(name) {
3
- return "`" + name.replace(/\\/g, "\\\\").replace(/`/g, "\\`") + "`";
4
- }
5
- // UInt64 max — used to emulate a bare OFFSET (skip without take)
6
- const NO_LIMIT = "18446744073709551615";
7
- // DateTime is second-precision; the client would send Date with
8
- // a fractional part, which ClickHouse rejects — send unix seconds.
9
- function serialize(value) {
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";
@@ -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
  }
@@ -65,7 +65,8 @@ export class TableClient {
65
65
  // subsequent read returns: omitted nullable columns are null,
66
66
  // DateTime values are stored with second precision
67
67
  for (const colName in this.table.columns) {
68
- if (full[colName] === undefined && this.table.columns[colName].nullable) {
68
+ if (full[colName] === undefined &&
69
+ this.table.columns[colName].nullable) {
69
70
  full[colName] = null;
70
71
  }
71
72
  else if (full[colName] instanceof Date) {
@@ -103,8 +104,8 @@ export class TableClient {
103
104
  // with wait_for_async_insert=0 on the server, inserts are
104
105
  // acked before flush: errors are swallowed and rows are not
105
106
  // immediately readable — force read-your-writes semantics
106
- wait_for_async_insert: 1
107
- }
107
+ wait_for_async_insert: 1,
108
+ },
108
109
  });
109
110
  }
110
111
  async insert(rows) {
@@ -133,7 +134,7 @@ export class TableClient {
133
134
  const patch = {};
134
135
  for (const k of keys)
135
136
  patch[k] = args.data[k];
136
- const { wire } = this.prepareRows(rows.map(r => ({ ...r, ...patch })));
137
+ const { wire } = this.prepareRows(rows.map((r) => ({ ...r, ...patch })));
137
138
  await this.insertWire(wire);
138
139
  return { count: rows.length };
139
140
  }
@@ -148,8 +149,8 @@ export class TableClient {
148
149
  query_params: params,
149
150
  clickhouse_settings: {
150
151
  // wait for the mutation so subsequent reads see the change
151
- mutations_sync: "1"
152
- }
152
+ mutations_sync: "1",
153
+ },
153
154
  });
154
155
  return { count };
155
156
  }
@@ -162,7 +163,7 @@ export class TableClient {
162
163
  await this.client.command({
163
164
  query: sql,
164
165
  query_params: params,
165
- clickhouse_settings: { mutations_sync: "1" }
166
+ clickhouse_settings: { mutations_sync: "1" },
166
167
  });
167
168
  }
168
169
  else {
@@ -171,7 +172,7 @@ export class TableClient {
171
172
  await this.client.command({
172
173
  query: sql,
173
174
  query_params: params,
174
- clickhouse_settings: { lightweight_deletes_sync: "2" }
175
+ clickhouse_settings: { lightweight_deletes_sync: "2" },
175
176
  });
176
177
  }
177
178
  return { count };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quark-fw/clisma",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "description": "Prisma-like typed client and schema migrator for ClickHouse",
5
5
  "keywords": [
6
6
  "clickhouse",