@toa.io/extensions.chronicles 1.0.0-alpha.332 → 1.0.0-alpha.333
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/LICENSE +22 -0
- package/components/chronicles.encounters/manifest.toa.yaml +108 -0
- package/components/{chronicles.ingestion → chronicles.encounters}/operations/anonymous.js +1 -1
- package/components/chronicles.encounters/operations/describe.js +4 -0
- package/components/{chronicles.ingestion → chronicles.encounters}/operations/identified.js +1 -1
- package/components/{chronicles.ingestion → chronicles.encounters}/types/toa.d.ts +13 -0
- package/components/chronicles.queries/manifest.toa.yaml +39 -15
- package/components/chronicles.queries/operations/define.js +1 -1
- package/components/chronicles.queries/types/toa.d.ts +8 -3
- package/components/{chronicles.facts → chronicles.relay}/manifest.toa.yaml +14 -3
- package/components/chronicles.relay/operations/encounter.js +4 -0
- package/components/{chronicles.facts → chronicles.relay}/operations/record.js +1 -1
- package/components/{chronicles.facts → chronicles.relay}/types/toa.d.ts +5 -0
- package/guides/clickhouse.md +19 -0
- package/guides/log.md +52 -0
- package/package.json +8 -4
- package/readme.md +459 -142
- package/store/clickhouse.scope.sql +29 -0
- package/store/clickhouse.sql +99 -0
- package/transpiled/Client.d.ts +19 -6
- package/transpiled/Client.js +89 -37
- package/transpiled/Client.js.map +1 -1
- package/transpiled/Factory.d.ts +13 -5
- package/transpiled/Factory.js +32 -23
- package/transpiled/Factory.js.map +1 -1
- package/transpiled/Provider.d.ts +76 -25
- package/transpiled/Provider.js.map +1 -1
- package/transpiled/Publisher.d.ts +50 -0
- package/transpiled/Publisher.js +169 -0
- package/transpiled/Publisher.js.map +1 -0
- package/transpiled/Reader.d.ts +15 -3
- package/transpiled/Reader.js +98 -14
- package/transpiled/Reader.js.map +1 -1
- package/transpiled/Recorder.d.ts +21 -11
- package/transpiled/Recorder.js +49 -24
- package/transpiled/Recorder.js.map +1 -1
- package/transpiled/Relay.d.ts +14 -0
- package/transpiled/Relay.js +27 -0
- package/transpiled/Relay.js.map +1 -0
- package/transpiled/Sender.d.ts +8 -4
- package/transpiled/Sender.js +27 -10
- package/transpiled/Sender.js.map +1 -1
- package/transpiled/Service.d.ts +26 -0
- package/transpiled/Service.js +70 -0
- package/transpiled/Service.js.map +1 -0
- package/transpiled/agent.d.ts +16 -0
- package/transpiled/agent.js +48 -0
- package/transpiled/agent.js.map +1 -0
- package/transpiled/clickhouse/Client.d.ts +5 -8
- package/transpiled/clickhouse/Client.js +12 -14
- package/transpiled/clickhouse/Client.js.map +1 -1
- package/transpiled/clickhouse/Provider.d.ts +20 -18
- package/transpiled/clickhouse/Provider.js +97 -72
- package/transpiled/clickhouse/Provider.js.map +1 -1
- package/transpiled/clickhouse/ddl.d.ts +3 -20
- package/transpiled/clickhouse/ddl.js +6 -79
- package/transpiled/clickhouse/ddl.js.map +1 -1
- package/transpiled/clickhouse/views.d.ts +11 -0
- package/transpiled/clickhouse/views.js +97 -0
- package/transpiled/clickhouse/views.js.map +1 -0
- package/transpiled/contract.d.ts +12 -0
- package/transpiled/contract.js +58 -0
- package/transpiled/contract.js.map +1 -0
- package/transpiled/index.d.ts +9 -3
- package/transpiled/settings.d.ts +6 -4
- package/transpiled/settings.js +27 -14
- package/transpiled/settings.js.map +1 -1
- package/transpiled/store.d.ts +3 -3
- package/transpiled/store.js +3 -3
- package/transpiled/store.js.map +1 -1
- package/components/chronicles.ingestion/manifest.toa.yaml +0 -60
- package/transpiled/Facts.d.ts +0 -9
- package/transpiled/Facts.js +0 -16
- package/transpiled/Facts.js.map +0 -1
- package/transpiled/Scribe.d.ts +0 -25
- package/transpiled/Scribe.js +0 -61
- package/transpiled/Scribe.js.map +0 -1
- package/transpiled/Writer.d.ts +0 -14
- package/transpiled/Writer.js +0 -50
- package/transpiled/Writer.js.map +0 -1
- /package/components/{chronicles.facts → chronicles.encounters}/types/index.d.ts +0 -0
- /package/components/{chronicles.ingestion → chronicles.relay}/types/index.d.ts +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Client.js","sourceRoot":"","sources":["../../source/clickhouse/Client.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,OAAO,UAAU;
|
|
1
|
+
{"version":3,"file":"Client.js","sourceRoot":"","sources":["../../source/clickhouse/Client.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,22 +1,26 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type {
|
|
1
|
+
import type { Definition, Encountered, Field, Label, Provider, Result, Summary, View } from '../Provider.ts';
|
|
2
|
+
import type { User } from '../settings.ts';
|
|
3
|
+
import type { Property } from '@toa.io/definitions/extensions.chronicles';
|
|
3
4
|
/**
|
|
4
|
-
* The log in ClickHouse
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* The log in ClickHouse, read as the context's user of it: `events`, `states` and `encounters` in
|
|
6
|
+
* the user's default database, which the store lets it read the rows of its scope of, under limits
|
|
7
|
+
* of its own. A query is asked to be read-only besides; only a view is created, in the database
|
|
8
|
+
* named after the scope, where the store lets the user create one.
|
|
9
|
+
*
|
|
10
|
+
* A view says what it is in its comment, as JSON: the `title` and the `description` it was defined
|
|
11
|
+
* with.
|
|
7
12
|
*/
|
|
8
13
|
export declare class ClickHouseProvider implements Provider {
|
|
9
|
-
readonly
|
|
14
|
+
readonly guide: string;
|
|
10
15
|
readonly tables: string[];
|
|
11
|
-
private readonly
|
|
12
|
-
private readonly
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
provision(): Promise<void>;
|
|
17
|
-
write(rows: Row[], states: State[]): Promise<void>;
|
|
16
|
+
private readonly user;
|
|
17
|
+
private readonly scope;
|
|
18
|
+
/** `scope` is the context's, which its views are kept under */
|
|
19
|
+
constructor(url: string, user: User, scope: string);
|
|
20
|
+
fields(properties: Record<string, Property>): Field[];
|
|
18
21
|
states(): Promise<Summary[]>;
|
|
19
22
|
labels(): Promise<Label[]>;
|
|
23
|
+
encounters(): Promise<Encountered[]>;
|
|
20
24
|
/**
|
|
21
25
|
* One row more than is answered, cut by the `limit` setting — the result's own, outside whatever
|
|
22
26
|
* the statement says — so a result of the limit exactly reads as whole, and one past it as cut.
|
|
@@ -24,10 +28,8 @@ export declare class ClickHouseProvider implements Provider {
|
|
|
24
28
|
query(sql: string): Promise<Result>;
|
|
25
29
|
validate(sql: string): Promise<void>;
|
|
26
30
|
views(): Promise<View[]>;
|
|
27
|
-
define(
|
|
31
|
+
define(definition: Definition): Promise<void>;
|
|
28
32
|
drop(name: string): Promise<void>;
|
|
29
|
-
private
|
|
30
|
-
|
|
31
|
-
private run;
|
|
32
|
-
private engine;
|
|
33
|
+
private view;
|
|
34
|
+
private read;
|
|
33
35
|
}
|
|
@@ -1,118 +1,136 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
2
3
|
import { ClickHouse, ClickHouseError } from './Client.js';
|
|
3
4
|
import * as ddl from './ddl.js';
|
|
5
|
+
import * as views from './views.js';
|
|
4
6
|
import { Refusal } from '../Provider.js';
|
|
5
7
|
/**
|
|
6
|
-
* The log in ClickHouse
|
|
7
|
-
*
|
|
8
|
-
*
|
|
8
|
+
* The log in ClickHouse, read as the context's user of it: `events`, `states` and `encounters` in
|
|
9
|
+
* the user's default database, which the store lets it read the rows of its scope of, under limits
|
|
10
|
+
* of its own. A query is asked to be read-only besides; only a view is created, in the database
|
|
11
|
+
* named after the scope, where the store lets the user create one.
|
|
12
|
+
*
|
|
13
|
+
* A view says what it is in its comment, as JSON: the `title` and the `description` it was defined
|
|
14
|
+
* with.
|
|
9
15
|
*/
|
|
10
16
|
export class ClickHouseProvider {
|
|
11
|
-
|
|
17
|
+
guide = GUIDE;
|
|
12
18
|
tables = ddl.TABLES;
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
this.
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
}
|
|
23
|
-
async provision() {
|
|
24
|
-
const { database } = this.writer;
|
|
25
|
-
for (const statement of ddl.database(database))
|
|
26
|
-
await this.writer.run(statement, {}, '');
|
|
27
|
-
await this.run(ddl.tables(this.retention));
|
|
28
|
-
await this.run(ddl.retention(this.retention, await this.engine()));
|
|
29
|
-
await this.run(ddl.reader(database, this.password));
|
|
30
|
-
console.info('Chronicles provisioned', { database });
|
|
31
|
-
}
|
|
32
|
-
async write(rows, states) {
|
|
33
|
-
if (rows.length > 0)
|
|
34
|
-
await this.insert('events', EVENTS, rows);
|
|
35
|
-
if (states.length > 0)
|
|
36
|
-
await this.insert('states', STATES, states);
|
|
19
|
+
user;
|
|
20
|
+
scope;
|
|
21
|
+
/** `scope` is the context's, which its views are kept under */
|
|
22
|
+
constructor(url, user, scope) {
|
|
23
|
+
this.user = new ClickHouse(url, user.username, user.password);
|
|
24
|
+
this.scope = scope;
|
|
25
|
+
}
|
|
26
|
+
fields(properties) {
|
|
27
|
+
return views.fields(properties);
|
|
37
28
|
}
|
|
38
29
|
async states() {
|
|
39
|
-
return await this.
|
|
40
|
-
arraySort(groupUniqArrayArray(JSONAllPaths(payload))) AS paths
|
|
30
|
+
return await this.read(`SELECT component, uniqExact(entity) AS entities, toString(max(time)) AS last
|
|
41
31
|
FROM states GROUP BY component ORDER BY component`);
|
|
42
32
|
}
|
|
43
33
|
async labels() {
|
|
44
|
-
|
|
34
|
+
return await this.read(`SELECT label, count() AS rows, toString(min(time)) AS first, toString(max(time)) AS last
|
|
45
35
|
FROM events GROUP BY label ORDER BY label`);
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
36
|
+
}
|
|
37
|
+
async encounters() {
|
|
38
|
+
const names = await this.read(`SELECT name, count() AS rows, toString(min(time)) AS first, toString(max(time)) AS last,
|
|
39
|
+
toString(max(received)) AS received
|
|
40
|
+
FROM encounters GROUP BY name ORDER BY name`);
|
|
41
|
+
// the fields of the latest payloads of each name, as they were sent: two types of one path
|
|
42
|
+
// are two clients that disagree
|
|
43
|
+
const fields = await this.read(`SELECT name, path, arraySort(groupUniqArray(type)) AS types
|
|
44
|
+
FROM (
|
|
45
|
+
SELECT name, typed.1 AS path, typed.2 AS type
|
|
46
|
+
FROM (SELECT name, payload FROM encounters ORDER BY received DESC LIMIT ${SAMPLE} BY name)
|
|
47
|
+
ARRAY JOIN arrayZip(
|
|
48
|
+
mapKeys(JSONAllPathsWithTypes(payload)),
|
|
49
|
+
mapValues(JSONAllPathsWithTypes(payload))
|
|
50
|
+
) AS typed
|
|
51
|
+
)
|
|
52
|
+
GROUP BY name, path ORDER BY name, path`);
|
|
53
|
+
return names.map((encountered) => ({
|
|
54
|
+
...encountered,
|
|
55
|
+
fields: fields
|
|
56
|
+
.filter(({ name }) => name === encountered.name)
|
|
57
|
+
.map(({ path, types }) => ({ path, types }))
|
|
58
|
+
}));
|
|
52
59
|
}
|
|
53
60
|
/**
|
|
54
61
|
* One row more than is answered, cut by the `limit` setting — the result's own, outside whatever
|
|
55
62
|
* the statement says — so a result of the limit exactly reads as whole, and one past it as cut.
|
|
56
63
|
*/
|
|
57
64
|
async query(sql) {
|
|
58
|
-
const settings = { ...NUMBERS, limit: String(ddl.LIMITS.rows + 1) };
|
|
59
|
-
const answer = await refusing(async () => await this.
|
|
65
|
+
const settings = { ...READONLY, ...NUMBERS, limit: String(ddl.LIMITS.rows + 1) };
|
|
66
|
+
const answer = await refusing(async () => await this.user.select(sql, settings));
|
|
60
67
|
return {
|
|
61
68
|
columns: answer.meta,
|
|
62
69
|
rows: answer.data.slice(0, ddl.LIMITS.rows),
|
|
63
70
|
truncated: answer.data.length > ddl.LIMITS.rows
|
|
64
71
|
};
|
|
65
72
|
}
|
|
66
|
-
// parsed and planned as the
|
|
67
|
-
// could not
|
|
73
|
+
// parsed and planned as the user, which is what refuses a view that reads what a query could not
|
|
68
74
|
async validate(sql) {
|
|
69
75
|
await refusing(async () => {
|
|
70
|
-
const [ast] = await this.
|
|
76
|
+
const [ast] = await this.read(`EXPLAIN AST ${sql}`);
|
|
71
77
|
if (!ast.explain.startsWith('SelectWithUnionQuery'))
|
|
72
78
|
throw new Refusal('A view is one SELECT');
|
|
73
|
-
await this.
|
|
79
|
+
await this.user.run(`EXPLAIN PLAN ${sql}`, READONLY);
|
|
74
80
|
});
|
|
75
81
|
}
|
|
76
82
|
async views() {
|
|
77
|
-
const tables = await this.
|
|
78
|
-
WHERE database =
|
|
79
|
-
AND name NOT IN (${this.tables.map(ddl.literal).join(', ')})
|
|
80
|
-
AND NOT startsWith(name, '.inner')
|
|
83
|
+
const tables = await this.read(`SELECT name, as_select, create_table_query, comment FROM system.tables
|
|
84
|
+
WHERE database = ${ddl.literal(this.scope)} AND NOT startsWith(name, '.inner')
|
|
81
85
|
ORDER BY name`);
|
|
86
|
+
const refreshes = await this.read(`SELECT view, toString(last_success_time) AS refreshed FROM system.view_refreshes
|
|
87
|
+
WHERE database = ${ddl.literal(this.scope)} AND last_success_time IS NOT NULL`);
|
|
88
|
+
const refreshed = new Map(refreshes.map(({ view, refreshed }) => [view, refreshed]));
|
|
82
89
|
return tables.map((table) => {
|
|
83
|
-
const
|
|
90
|
+
const { title, description } = said(table.comment);
|
|
91
|
+
const view = {
|
|
92
|
+
name: table.name,
|
|
93
|
+
read: `${ddl.id(this.scope)}.${ddl.id(table.name)}`,
|
|
94
|
+
sql: table.as_select
|
|
95
|
+
};
|
|
96
|
+
if (title !== undefined)
|
|
97
|
+
view.title = title;
|
|
98
|
+
if (description !== undefined)
|
|
99
|
+
view.description = description;
|
|
84
100
|
// stated in seconds, and kept in the largest unit that divides them
|
|
85
101
|
const every = /REFRESH EVERY (\d+) (SECOND|MINUTE|HOUR|DAY|WEEK)/.exec(table.create_table_query);
|
|
86
|
-
if (every !== null)
|
|
102
|
+
if (every !== null) {
|
|
87
103
|
view.refresh = Number(every[1]) * UNITS[every[2]];
|
|
104
|
+
const at = refreshed.get(table.name);
|
|
105
|
+
if (at !== undefined)
|
|
106
|
+
view.refreshed = at;
|
|
107
|
+
}
|
|
88
108
|
return view;
|
|
89
109
|
});
|
|
90
110
|
}
|
|
91
|
-
|
|
92
|
-
|
|
111
|
+
// the comment is on a line of its own: a statement that ends in a comment of its own would
|
|
112
|
+
// take it in otherwise
|
|
113
|
+
async define(definition) {
|
|
114
|
+
const { name, sql, refresh, title, description } = definition;
|
|
115
|
+
const comment = ddl.literal(JSON.stringify({ title, description }));
|
|
116
|
+
const view = this.view(name);
|
|
93
117
|
await refusing(async () => {
|
|
118
|
+
await this.user.run(`DROP TABLE IF EXISTS ${view} SYNC`);
|
|
94
119
|
if (refresh === undefined)
|
|
95
|
-
await this.
|
|
120
|
+
await this.user.run(`CREATE VIEW ${view} SQL SECURITY INVOKER AS ${sql}\nCOMMENT ${comment}`);
|
|
96
121
|
else
|
|
97
|
-
await this.
|
|
98
|
-
ENGINE = MergeTree ORDER BY tuple() AS ${sql}`);
|
|
122
|
+
await this.user.run(`CREATE MATERIALIZED VIEW ${view} REFRESH EVERY ${refresh} SECOND
|
|
123
|
+
ENGINE = MergeTree ORDER BY tuple() AS ${sql}\nCOMMENT ${comment}`);
|
|
99
124
|
});
|
|
100
125
|
}
|
|
101
126
|
async drop(name) {
|
|
102
|
-
await this.
|
|
103
|
-
}
|
|
104
|
-
async insert(table, columns, rows) {
|
|
105
|
-
const lines = rows.map((row) => JSON.stringify({ ...row, time: timestamp(row.time) }));
|
|
106
|
-
await this.writer.run(`INSERT INTO ${table} (${columns}) FORMAT JSONEachRow\n${lines.join('\n')}`);
|
|
127
|
+
await refusing(async () => await this.user.run(`DROP TABLE IF EXISTS ${this.view(name)} SYNC`));
|
|
107
128
|
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
for (const statement of statements)
|
|
111
|
-
await this.writer.run(statement);
|
|
129
|
+
view(name) {
|
|
130
|
+
return `${ddl.id(this.scope)}.${ddl.id(name)}`;
|
|
112
131
|
}
|
|
113
|
-
async
|
|
114
|
-
|
|
115
|
-
return table?.engine_full ?? '';
|
|
132
|
+
async read(sql) {
|
|
133
|
+
return await this.user.rows(sql, READONLY);
|
|
116
134
|
}
|
|
117
135
|
}
|
|
118
136
|
/** What ClickHouse refused is the provider's refusal, in ClickHouse's words. */
|
|
@@ -126,12 +144,16 @@ async function refusing(fn) {
|
|
|
126
144
|
throw exception;
|
|
127
145
|
}
|
|
128
146
|
}
|
|
129
|
-
/**
|
|
130
|
-
function
|
|
131
|
-
|
|
147
|
+
/** What a view's comment says of it; nothing, of one made otherwise. */
|
|
148
|
+
function said(comment) {
|
|
149
|
+
try {
|
|
150
|
+
const parsed = JSON.parse(comment);
|
|
151
|
+
return typeof parsed === 'object' && parsed !== null ? parsed : {};
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
return {};
|
|
155
|
+
}
|
|
132
156
|
}
|
|
133
|
-
const EVENTS = 'id, label, component, time, entity, version, identity, ip, country, trace, payload';
|
|
134
|
-
const STATES = 'component, entity, version, identity, time, payload';
|
|
135
157
|
const UNITS = {
|
|
136
158
|
SECOND: 1,
|
|
137
159
|
MINUTE: 60,
|
|
@@ -140,5 +162,8 @@ const UNITS = {
|
|
|
140
162
|
WEEK: 604800
|
|
141
163
|
};
|
|
142
164
|
const SAMPLE = 1000;
|
|
165
|
+
const GUIDE = readFileSync(resolve(import.meta.dirname, '../../guides/clickhouse.md'), 'utf8');
|
|
143
166
|
const NUMBERS = { output_format_json_quote_64bit_integers: '0' };
|
|
167
|
+
/** what a request asks besides its user's rights: to change nothing */
|
|
168
|
+
const READONLY = { readonly: '1' };
|
|
144
169
|
//# sourceMappingURL=Provider.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Provider.js","sourceRoot":"","sources":["../../source/clickhouse/Provider.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"Provider.js","sourceRoot":"","sources":["../../source/clickhouse/Provider.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,26 +1,9 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The log in ClickHouse. Every statement is one a second start changes nothing with, so two
|
|
3
|
-
* replicas starting together collide on nothing; the retention, which would change, is compared
|
|
4
|
-
* with what is there and changed only where it differs.
|
|
5
|
-
*/
|
|
1
|
+
/** The log's tables, which a view is not named after. */
|
|
6
2
|
export declare const TABLES: string[];
|
|
7
|
-
|
|
8
|
-
/** What is kept: a row for everything that happened, and each entity as it stands. */
|
|
9
|
-
export declare function tables(retention: number | undefined): string[];
|
|
10
|
-
/** `ALTER TABLE` for the retention declared, given what the table states now; none if it agrees. */
|
|
11
|
-
export declare function retention(declared: number | undefined, engine: string): string[];
|
|
12
|
-
/**
|
|
13
|
-
* The user queries run as: it reads the database and nothing else, under limits a query cannot
|
|
14
|
-
* lift, because they are constraints of its profile rather than settings a request passes. Past
|
|
15
|
-
* the rows a query is refused, which is the overflow a client's query cache allows; a query of
|
|
16
|
-
* the provider's own asks for one row more than it answers, to tell a full answer from a cut one.
|
|
17
|
-
*/
|
|
18
|
-
export declare function reader(database: string, password: string): string[];
|
|
3
|
+
/** What a query answers at most, which the provider cuts a result at to tell it is cut. */
|
|
19
4
|
export declare const LIMITS: {
|
|
20
|
-
seconds: number;
|
|
21
5
|
rows: number;
|
|
22
|
-
memory: number;
|
|
23
6
|
};
|
|
24
|
-
/** An identifier, which
|
|
7
|
+
/** An identifier, quoted: a scope, which may have hyphens, or a name held to letters and digits. */
|
|
25
8
|
export declare function id(name: string): string;
|
|
26
9
|
export declare function literal(value: string): string;
|
|
@@ -1,78 +1,8 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
*/
|
|
6
|
-
export const TABLES = ['events', 'states'];
|
|
7
|
-
export function database(name) {
|
|
8
|
-
return [`CREATE DATABASE IF NOT EXISTS ${id(name)}`];
|
|
9
|
-
}
|
|
10
|
-
/** What is kept: a row for everything that happened, and each entity as it stands. */
|
|
11
|
-
export function tables(retention) {
|
|
12
|
-
return [
|
|
13
|
-
`CREATE TABLE IF NOT EXISTS events (
|
|
14
|
-
id String,
|
|
15
|
-
label LowCardinality(String),
|
|
16
|
-
component LowCardinality(String),
|
|
17
|
-
time DateTime64(3, 'UTC'),
|
|
18
|
-
received DateTime64(3, 'UTC') DEFAULT now64(3),
|
|
19
|
-
entity Nullable(String),
|
|
20
|
-
version Nullable(UInt64),
|
|
21
|
-
identity Nullable(String),
|
|
22
|
-
ip Nullable(String),
|
|
23
|
-
country LowCardinality(Nullable(String)),
|
|
24
|
-
trace Nullable(String),
|
|
25
|
-
payload JSON
|
|
26
|
-
) ENGINE = ReplacingMergeTree
|
|
27
|
-
PARTITION BY toYYYYMM(time)
|
|
28
|
-
ORDER BY (label, toDate(time), id)
|
|
29
|
-
${retention === undefined ? '' : ttl(retention)}`,
|
|
30
|
-
`CREATE TABLE IF NOT EXISTS states (
|
|
31
|
-
component LowCardinality(String),
|
|
32
|
-
entity String,
|
|
33
|
-
version UInt64,
|
|
34
|
-
identity Nullable(String),
|
|
35
|
-
time DateTime64(3, 'UTC'),
|
|
36
|
-
received DateTime64(3, 'UTC') DEFAULT now64(3),
|
|
37
|
-
payload JSON
|
|
38
|
-
) ENGINE = ReplacingMergeTree(version)
|
|
39
|
-
ORDER BY (component, entity)`
|
|
40
|
-
];
|
|
41
|
-
}
|
|
42
|
-
/** `ALTER TABLE` for the retention declared, given what the table states now; none if it agrees. */
|
|
43
|
-
export function retention(declared, engine) {
|
|
44
|
-
const stated = /TTL toDateTime\(time\) \+ toIntervalDay\((\d+)\)/.exec(engine)?.[1];
|
|
45
|
-
if (declared === undefined)
|
|
46
|
-
return stated === undefined && !engine.includes(' TTL ')
|
|
47
|
-
? []
|
|
48
|
-
: ['ALTER TABLE events REMOVE TTL'];
|
|
49
|
-
if (stated === String(declared))
|
|
50
|
-
return [];
|
|
51
|
-
return [`ALTER TABLE events MODIFY ${ttl(declared)}`];
|
|
52
|
-
}
|
|
53
|
-
/**
|
|
54
|
-
* The user queries run as: it reads the database and nothing else, under limits a query cannot
|
|
55
|
-
* lift, because they are constraints of its profile rather than settings a request passes. Past
|
|
56
|
-
* the rows a query is refused, which is the overflow a client's query cache allows; a query of
|
|
57
|
-
* the provider's own asks for one row more than it answers, to tell a full answer from a cut one.
|
|
58
|
-
*/
|
|
59
|
-
export function reader(database, password) {
|
|
60
|
-
const name = id(`${database}_reader`);
|
|
61
|
-
return [
|
|
62
|
-
`CREATE SETTINGS PROFILE IF NOT EXISTS ${name}`,
|
|
63
|
-
`ALTER SETTINGS PROFILE ${name} SETTINGS
|
|
64
|
-
readonly = 2 CONST,
|
|
65
|
-
max_execution_time = ${LIMITS.seconds} CONST,
|
|
66
|
-
max_result_rows = ${LIMITS.rows + 1} CONST,
|
|
67
|
-
max_memory_usage = ${LIMITS.memory} CONST`,
|
|
68
|
-
`CREATE USER IF NOT EXISTS ${name} IDENTIFIED WITH sha256_password BY ${literal(password)}`,
|
|
69
|
-
`ALTER USER ${name} IDENTIFIED WITH sha256_password BY ${literal(password)}
|
|
70
|
-
SETTINGS PROFILE ${literal(`${database}_reader`)}`,
|
|
71
|
-
`GRANT SELECT ON ${id(database)}.* TO ${name}`
|
|
72
|
-
];
|
|
73
|
-
}
|
|
74
|
-
export const LIMITS = { seconds: 30, rows: 10_000, memory: 4_000_000_000 };
|
|
75
|
-
/** An identifier, which the annotation and the schema have already held to this. */
|
|
1
|
+
/** The log's tables, which a view is not named after. */
|
|
2
|
+
export const TABLES = ['events', 'states', 'encounters'];
|
|
3
|
+
/** What a query answers at most, which the provider cuts a result at to tell it is cut. */
|
|
4
|
+
export const LIMITS = { rows: 10_000 };
|
|
5
|
+
/** An identifier, quoted: a scope, which may have hyphens, or a name held to letters and digits. */
|
|
76
6
|
export function id(name) {
|
|
77
7
|
if (!IDENTIFIER.test(name))
|
|
78
8
|
throw new Error(`'${name}' is not a ClickHouse identifier chronicles accept`);
|
|
@@ -81,8 +11,5 @@ export function id(name) {
|
|
|
81
11
|
export function literal(value) {
|
|
82
12
|
return "'" + value.replace(/\\/g, '\\\\').replace(/'/g, "\\'") + "'";
|
|
83
13
|
}
|
|
84
|
-
|
|
85
|
-
return `TTL toDateTime(time) + INTERVAL ${days} DAY`;
|
|
86
|
-
}
|
|
87
|
-
const IDENTIFIER = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
|
|
14
|
+
const IDENTIFIER = /^[a-zA-Z0-9_][a-zA-Z0-9_-]*$/;
|
|
88
15
|
//# sourceMappingURL=ddl.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ddl.js","sourceRoot":"","sources":["../../source/clickhouse/ddl.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"ddl.js","sourceRoot":"","sources":["../../source/clickhouse/ddl.ts"],"names":[],"mappings":"AAAA,yDAAyD;AACzD,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,QAAQ,EAAE,QAAQ,EAAE,YAAY,CAAC,CAAA;AAExD,2FAA2F;AAC3F,MAAM,CAAC,MAAM,MAAM,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE,CAAA;AAEtC,oGAAoG;AACpG,MAAM,UAAU,EAAE,CAAC,IAAY;IAC7B,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC;QACxB,MAAM,IAAI,KAAK,CAAC,IAAI,IAAI,oDAAoD,CAAC,CAAA;IAE/E,OAAO,GAAG,GAAG,IAAI,GAAG,GAAG,CAAA;AACzB,CAAC;AAED,MAAM,UAAU,OAAO,CAAC,KAAa;IACnC,OAAO,GAAG,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,GAAG,GAAG,CAAA;AACtE,CAAC;AAED,MAAM,UAAU,GAAG,8BAA8B,CAAA"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Property } from '@toa.io/definitions/extensions.chronicles';
|
|
2
|
+
import type { Field } from '../Provider.ts';
|
|
3
|
+
/**
|
|
4
|
+
* How a declared property is read from the payload in its type, by `JSONExtract`, which reads what
|
|
5
|
+
* is not of the type as `NULL` rather than failing the query — a row written under an earlier
|
|
6
|
+
* contract is read by the one there is now.
|
|
7
|
+
*/
|
|
8
|
+
/** The fields of a contract, at every depth, each with the type its column reads it as. */
|
|
9
|
+
export declare function fields(properties: Record<string, Property>, prefix?: string): Field[];
|
|
10
|
+
/** What a property reads as, `NULL` aside. */
|
|
11
|
+
export declare function type(property: Property): string;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { id, literal } from './ddl.js';
|
|
2
|
+
/**
|
|
3
|
+
* How a declared property is read from the payload in its type, by `JSONExtract`, which reads what
|
|
4
|
+
* is not of the type as `NULL` rather than failing the query — a row written under an earlier
|
|
5
|
+
* contract is read by the one there is now.
|
|
6
|
+
*/
|
|
7
|
+
/** The fields of a contract, at every depth, each with the type its column reads it as. */
|
|
8
|
+
export function fields(properties, prefix = '') {
|
|
9
|
+
const found = [];
|
|
10
|
+
for (const [name, property] of Object.entries(properties)) {
|
|
11
|
+
if (empty(property))
|
|
12
|
+
continue;
|
|
13
|
+
const path = prefix === '' ? name : `${prefix}.${name}`;
|
|
14
|
+
const field = { path, type: type(property), description: property.description };
|
|
15
|
+
// a property of a property is read inside the one that holds it
|
|
16
|
+
if (prefix === '')
|
|
17
|
+
field.read = extract(name, property);
|
|
18
|
+
found.push(field);
|
|
19
|
+
const nested = property.type === 'array' ? property.items : property;
|
|
20
|
+
if (nested.type === 'object')
|
|
21
|
+
found.push(...fields(nested.properties, path));
|
|
22
|
+
}
|
|
23
|
+
return found;
|
|
24
|
+
}
|
|
25
|
+
/** What a property reads as, `NULL` aside. */
|
|
26
|
+
export function type(property) {
|
|
27
|
+
switch (property.type) {
|
|
28
|
+
case 'object':
|
|
29
|
+
return `Tuple(${members(property, type)})`;
|
|
30
|
+
case 'array':
|
|
31
|
+
return `Array(${type(property.items)})`;
|
|
32
|
+
default:
|
|
33
|
+
return scalar(property);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/** The column of a property: a value of the type, or `NULL` where it was not one. */
|
|
37
|
+
function column(property) {
|
|
38
|
+
switch (property.type) {
|
|
39
|
+
case 'object':
|
|
40
|
+
return `Tuple(${members(property, column)})`;
|
|
41
|
+
case 'array':
|
|
42
|
+
return `Array(${column(property.items)})`;
|
|
43
|
+
default:
|
|
44
|
+
return property.enum === undefined
|
|
45
|
+
? `Nullable(${scalar(property)})`
|
|
46
|
+
: 'LowCardinality(Nullable(String))';
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function scalar(property) {
|
|
50
|
+
if (property.enum !== undefined)
|
|
51
|
+
return 'LowCardinality(String)';
|
|
52
|
+
const formatted = property.format === undefined ? undefined : FORMATS[property.format];
|
|
53
|
+
return formatted?.type === property.type ? formatted.as : SCALARS[property.type];
|
|
54
|
+
}
|
|
55
|
+
function members(property, of) {
|
|
56
|
+
return Object.entries(property.properties)
|
|
57
|
+
.filter(([, nested]) => !empty(nested))
|
|
58
|
+
.map(([name, nested]) => `${name} ${of(nested)}`)
|
|
59
|
+
.join(', ');
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* An object is read from the JSON of its paths, which is an object however few of them a row has;
|
|
63
|
+
* anything else from the JSON of its value, which is `null` where a row has none.
|
|
64
|
+
*/
|
|
65
|
+
function extract(name, property) {
|
|
66
|
+
const json = property.type === 'object'
|
|
67
|
+
? `toJSONString(payload.^${id(name)})`
|
|
68
|
+
: `ifNull(toJSONString(payload.${id(name)}), 'null')`;
|
|
69
|
+
return `JSONExtract(${json}, ${literal(column(property))})`;
|
|
70
|
+
}
|
|
71
|
+
/** An object of no properties is no column: a tuple of nothing is no type. */
|
|
72
|
+
function empty(property) {
|
|
73
|
+
const nested = property.type === 'array' ? property.items : property;
|
|
74
|
+
return (nested.type === 'object' &&
|
|
75
|
+
Object.values(nested.properties ?? {}).every((member) => empty(member)));
|
|
76
|
+
}
|
|
77
|
+
const SCALARS = {
|
|
78
|
+
string: 'String',
|
|
79
|
+
integer: 'Int64',
|
|
80
|
+
number: 'Float64',
|
|
81
|
+
boolean: 'Bool'
|
|
82
|
+
};
|
|
83
|
+
const FORMATS = {
|
|
84
|
+
'date-time': { type: 'string', as: "DateTime64(3, 'UTC')" },
|
|
85
|
+
'iso-date-time': { type: 'string', as: "DateTime64(3, 'UTC')" },
|
|
86
|
+
date: { type: 'string', as: 'Date32' },
|
|
87
|
+
time: { type: 'string', as: 'Time64(3)' },
|
|
88
|
+
'iso-time': { type: 'string', as: 'Time64(3)' },
|
|
89
|
+
uuid: { type: 'string', as: 'UUID' },
|
|
90
|
+
ipv4: { type: 'string', as: 'IPv4' },
|
|
91
|
+
ipv6: { type: 'string', as: 'IPv6' },
|
|
92
|
+
int32: { type: 'integer', as: 'Int32' },
|
|
93
|
+
int64: { type: 'integer', as: 'Int64' },
|
|
94
|
+
float: { type: 'number', as: 'Float32' },
|
|
95
|
+
double: { type: 'number', as: 'Float64' }
|
|
96
|
+
};
|
|
97
|
+
//# sourceMappingURL=views.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"views.js","sourceRoot":"","sources":["../../source/clickhouse/views.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Property } from '@toa.io/definitions/extensions.chronicles';
|
|
2
|
+
/**
|
|
3
|
+
* What of a payload is written: the declared properties, at every depth, and nothing else. A
|
|
4
|
+
* value that is not of its declared type is a payload the contract does not admit, which throws a
|
|
5
|
+
* `Mismatch` naming where; `null` is admitted for any property, and an absent one stays absent.
|
|
6
|
+
*/
|
|
7
|
+
export declare function project(payload: object, properties: Record<string, Property>, path?: string): Record<string, unknown>;
|
|
8
|
+
/** A payload's value at `path` is not of the type its contract declares. */
|
|
9
|
+
export declare class Mismatch extends Error {
|
|
10
|
+
readonly path: string;
|
|
11
|
+
constructor(path: string, property: Property);
|
|
12
|
+
}
|