@node-c/data-clickhouse 1.0.0-alpha64 → 1.0.0-beta0

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/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Node-C / Data: RDB
1
+ # Node-C / Data: ClickHouse
2
2
  This is Node-C's package providing the functionality for working with ClickHouse.
3
3
 
4
4
  The documentation can be found on the [Node-C Github repo homepage](https://github.com/RazorDude/node-c).
@@ -50,7 +50,7 @@ class ClickHouseConnectionModule {
50
50
  client = (0, client_1.createClient)(connectionOptions);
51
51
  const pingResult = yield client.ping({ select: true });
52
52
  if (!pingResult.success) {
53
- throw new Error(JSON.stringify(pingResult));
53
+ throw new core_1.ApplicationError(JSON.stringify(pingResult));
54
54
  }
55
55
  }
56
56
  catch (err) {
@@ -1 +1 @@
1
- {"version":3,"file":"clickhouse.connection.module.js","sourceRoot":"","sources":["../../src/connectionModule/clickhouse.connection.module.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,+CAAoE;AAIpE,uCAAuE;AAIvE,uDAAkD;AAElD,MAAa,0BAA0B;IACrC,MAAM,CAAC,QAAQ,CAAC,OAA0C;QACxD,MAAM,EAAE,cAAc,EAAE,GAAG,OAAO,CAAC;QACnC,MAAM,UAAU,GAAG,GAAG,uBAAS,CAAC,wBAAwB,GAAG,cAAc,EAAE,CAAC;QAC5E,OAAO;YACL,MAAM,EAAE,IAAI;YACZ,MAAM,EAAE,0BAA0B;YAClC,OAAO,EAAE,EAAE;YACX,SAAS,EAAE;gBACT;oBACE,OAAO,EAAE,UAAU;oBACnB,UAAU,EAAE,CAAO,cAAqC,EAAE,EAAE;wBAC1D,MAAM,UAAU,GAAG,cAAc,CAAC,MAAM,CAAC,IAAI,CAAC;wBAC9C,MAAM,EACJ,WAAW,EACX,QAAQ,EACR,qBAAqB,GAAG,IAAI,EAC5B,IAAI,EACJ,QAAQ,EACR,IAAI,EACJ,QAAQ,EACR,cAAc,EACd,YAAY,EACZ,IAAI,EACL,GAAG,UAAU,CAAC,cAAyC,CAAqB,CAAC;wBAC9E,MAAM,iBAAiB,GAAsC;4BAC3D,QAAQ;4BACR,QAAQ;4BACR,QAAQ,EAAE,IAAI;yBACf,CAAC;wBACF,MAAM,GAAG,GAAG,GAAG,QAAQ,IAAI,MAAM,MAAM,IAAI,IAAI,IAAI,EAAE,CAAC;wBACtD,IAAI,MAAwB,CAAC;wBAC7B,IAAI,WAAW,EAAE,CAAC;4BAChB,iBAAiB,CAAC,WAAW,GAAG,WAAW,CAAC;wBAC9C,CAAC;wBACD,IAAI,cAAc,EAAE,CAAC;4BACnB,iBAAiB,CAAC,eAAe,GAAG,cAAc,CAAC;wBACrD,CAAC;wBACD,IAAI,YAAY,EAAE,CAAC;4BACjB,iBAAiB,CAAC,IAAI,GAAG,GAAG,CAAC;wBAC/B,CAAC;6BAAM,CAAC;4BACN,iBAAiB,CAAC,GAAG,GAAG,GAAG,CAAC;wBAC9B,CAAC;wBACD,IAAI,CAAC;4BACH,MAAM,GAAG,IAAA,qBAAY,EAAC,iBAAiB,CAAC,CAAC;4BACzC,MAAM,UAAU,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;4BACvD,IAAI,CAAC,UAAU,CAAC,OAAO,EAAE,CAAC;gCACxB,MAAM,IAAI,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC,CAAC;4BAC9C,CAAC;wBACH,CAAC;wBAAC,OAAO,GAAG,EAAE,CAAC;4BACb,OAAO,CAAC,KAAK,CAAC,gCAAgC,cAAc,oCAAoC,EAAE,GAAG,CAAC,CAAC;4BACvG,IAAI,qBAAqB,EAAE,CAAC;gCAC1B,MAAM,GAAG,CAAC;4BACZ,CAAC;wBACH,CAAC;wBACD,OAAO,MAAO,CAAC;oBACjB,CAAC,CAAA;oBACD,MAAM,EAAE,CAAC,4BAAqB,CAAC;iBAChC;aACF;YACD,OAAO,EAAE,CAAC,UAAU,CAAC;SACtB,CAAC;IACJ,CAAC;CACF;AA/DD,gEA+DC"}
1
+ {"version":3,"file":"clickhouse.connection.module.js","sourceRoot":"","sources":["../../src/connectionModule/clickhouse.connection.module.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,+CAAoE;AAIpE,uCAAyF;AAIzF,uDAAkD;AAElD,MAAa,0BAA0B;IACrC,MAAM,CAAC,QAAQ,CAAC,OAA0C;QACxD,MAAM,EAAE,cAAc,EAAE,GAAG,OAAO,CAAC;QACnC,MAAM,UAAU,GAAG,GAAG,uBAAS,CAAC,wBAAwB,GAAG,cAAc,EAAE,CAAC;QAC5E,OAAO;YACL,MAAM,EAAE,IAAI;YACZ,MAAM,EAAE,0BAA0B;YAClC,OAAO,EAAE,EAAE;YACX,SAAS,EAAE;gBACT;oBACE,OAAO,EAAE,UAAU;oBACnB,UAAU,EAAE,CAAO,cAAqC,EAAE,EAAE;wBAC1D,MAAM,UAAU,GAAG,cAAc,CAAC,MAAM,CAAC,IAAI,CAAC;wBAC9C,MAAM,EACJ,WAAW,EACX,QAAQ,EACR,qBAAqB,GAAG,IAAI,EAC5B,IAAI,EACJ,QAAQ,EACR,IAAI,EACJ,QAAQ,EACR,cAAc,EACd,YAAY,EACZ,IAAI,EACL,GAAG,UAAU,CAAC,cAAyC,CAAqB,CAAC;wBAC9E,MAAM,iBAAiB,GAAsC;4BAC3D,QAAQ;4BACR,QAAQ;4BACR,QAAQ,EAAE,IAAI;yBACf,CAAC;wBACF,MAAM,GAAG,GAAG,GAAG,QAAQ,IAAI,MAAM,MAAM,IAAI,IAAI,IAAI,EAAE,CAAC;wBACtD,IAAI,MAAwB,CAAC;wBAC7B,IAAI,WAAW,EAAE,CAAC;4BAChB,iBAAiB,CAAC,WAAW,GAAG,WAAW,CAAC;wBAC9C,CAAC;wBACD,IAAI,cAAc,EAAE,CAAC;4BACnB,iBAAiB,CAAC,eAAe,GAAG,cAAc,CAAC;wBACrD,CAAC;wBACD,IAAI,YAAY,EAAE,CAAC;4BACjB,iBAAiB,CAAC,IAAI,GAAG,GAAG,CAAC;wBAC/B,CAAC;6BAAM,CAAC;4BACN,iBAAiB,CAAC,GAAG,GAAG,GAAG,CAAC;wBAC9B,CAAC;wBACD,IAAI,CAAC;4BACH,MAAM,GAAG,IAAA,qBAAY,EAAC,iBAAiB,CAAC,CAAC;4BACzC,MAAM,UAAU,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;4BACvD,IAAI,CAAC,UAAU,CAAC,OAAO,EAAE,CAAC;gCACxB,MAAM,IAAI,uBAAgB,CAAC,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC,CAAC;4BACzD,CAAC;wBACH,CAAC;wBAAC,OAAO,GAAG,EAAE,CAAC;4BACb,OAAO,CAAC,KAAK,CAAC,gCAAgC,cAAc,oCAAoC,EAAE,GAAG,CAAC,CAAC;4BACvG,IAAI,qBAAqB,EAAE,CAAC;gCAC1B,MAAM,GAAG,CAAC;4BACZ,CAAC;wBACH,CAAC;wBACD,OAAO,MAAO,CAAC;oBACjB,CAAC,CAAA;oBACD,MAAM,EAAE,CAAC,4BAAqB,CAAC;iBAChC;aACF;YACD,OAAO,EAAE,CAAC,UAAU,CAAC;SACtB,CAAC;IACJ,CAAC;CACF;AA/DD,gEA+DC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@node-c/data-clickhouse",
3
- "version": "1.0.0-alpha64",
3
+ "version": "1.0.0-beta0",
4
4
  "license": "MIT",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {
@@ -15,10 +15,10 @@
15
15
  },
16
16
  "dependencies": {
17
17
  "@clickhouse/client": "^1.12.1",
18
- "@nestjs/common": "^10.4.12",
19
- "@nestjs/typeorm": "^10.0.2",
20
- "@node-c/core": "^1.0.0-alpha64",
21
- "@node-c/data-rdb": "^1.0.0-alpha64",
18
+ "@nestjs/common": "^11.1.16",
19
+ "@nestjs/typeorm": "^11.0.0",
20
+ "@node-c/core": "^1.0.0-beta0",
21
+ "@node-c/data-rdb": "^1.0.0-beta0",
22
22
  "class-validator": "^0.14.1",
23
23
  "typeorm": "^0.3.20",
24
24
  "uuid": "^11.0.5"
@@ -0,0 +1,6 @@
1
+ export enum Constants {
2
+ // eslint-disable-next-line no-unused-vars
3
+ CLICKHOUSE_CLIENT = 'CLICKHOUSE_CLIENT',
4
+ // eslint-disable-next-line no-unused-vars
5
+ CLICKHOUSE_CLIENT_PREFIX = 'CLICKHOUSE_CLIENT_'
6
+ }
@@ -0,0 +1 @@
1
+ export * from './common.constants';
@@ -0,0 +1,3 @@
1
+ export interface ClickHouseConnectionModuleOptions {
2
+ dataModuleName: string;
3
+ }
@@ -0,0 +1,74 @@
1
+ import { ClickHouseClient, createClient } from '@clickhouse/client';
2
+ import { NodeClickHouseClientConfigOptions } from '@clickhouse/client/dist/config';
3
+ import { DynamicModule } from '@nestjs/common';
4
+
5
+ import { AppConfigDataRDB, ApplicationError, ConfigProviderService } from '@node-c/core';
6
+
7
+ import { ClickHouseConnectionModuleOptions } from './clickhouse.connection.module.definitions';
8
+
9
+ import { Constants } from '../common/definitions';
10
+
11
+ export class ClickHouseConnectionModule {
12
+ static register(options: ClickHouseConnectionModuleOptions): DynamicModule {
13
+ const { dataModuleName } = options;
14
+ const clientName = `${Constants.CLICKHOUSE_CLIENT_PREFIX}${dataModuleName}`;
15
+ return {
16
+ global: true,
17
+ module: ClickHouseConnectionModule,
18
+ imports: [],
19
+ providers: [
20
+ {
21
+ provide: clientName,
22
+ useFactory: async (configProvider: ConfigProviderService) => {
23
+ const dataConfig = configProvider.config.data;
24
+ const {
25
+ application,
26
+ database,
27
+ failOnConnectionError = true,
28
+ host,
29
+ password,
30
+ port,
31
+ protocol,
32
+ requestTimeout,
33
+ useHostParam,
34
+ user
35
+ } = dataConfig[dataModuleName as keyof typeof dataConfig] as AppConfigDataRDB;
36
+ const connectionOptions: NodeClickHouseClientConfigOptions = {
37
+ database,
38
+ password,
39
+ username: user
40
+ };
41
+ const url = `${protocol || 'http'}://${host}:${port}`;
42
+ let client: ClickHouseClient;
43
+ if (application) {
44
+ connectionOptions.application = application;
45
+ }
46
+ if (requestTimeout) {
47
+ connectionOptions.request_timeout = requestTimeout;
48
+ }
49
+ if (useHostParam) {
50
+ connectionOptions.host = url;
51
+ } else {
52
+ connectionOptions.url = url;
53
+ }
54
+ try {
55
+ client = createClient(connectionOptions);
56
+ const pingResult = await client.ping({ select: true });
57
+ if (!pingResult.success) {
58
+ throw new ApplicationError(JSON.stringify(pingResult));
59
+ }
60
+ } catch (err) {
61
+ console.error(`[ClickHouseConnectionModule][${dataModuleName}]: Error connecting to ClickHouse:`, err);
62
+ if (failOnConnectionError) {
63
+ throw err;
64
+ }
65
+ }
66
+ return client!;
67
+ },
68
+ inject: [ConfigProviderService]
69
+ }
70
+ ],
71
+ exports: [clientName]
72
+ };
73
+ }
74
+ }
@@ -0,0 +1,2 @@
1
+ export * from './clickhouse.connection.module.definitions';
2
+ export * from './clickhouse.connection.module';
@@ -0,0 +1,61 @@
1
+ import { ClickHouseClient } from '@clickhouse/client';
2
+ import { Inject, Injectable } from '@nestjs/common';
3
+
4
+ import { GenericObject } from '@node-c/core';
5
+ import { Constants as RDBConstants, RDBEntityManager, RDBRepository } from '@node-c/data-rdb';
6
+
7
+ import { Constants } from '../common/definitions';
8
+
9
+ @Injectable()
10
+ export class ClickHouseEntityManager implements RDBEntityManager {
11
+ constructor(
12
+ @Inject(Constants.CLICKHOUSE_CLIENT)
13
+ // eslint-disable-next-line no-unused-vars
14
+ protected client: ClickHouseClient,
15
+ @Inject(RDBConstants.RDB_ENTITY_REPOSITORY)
16
+ // eslint-disable-next-line no-unused-vars
17
+ protected repository: RDBRepository<GenericObject<unknown>>
18
+ ) {}
19
+
20
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
21
+ getRepository<Entity extends GenericObject<unknown>>(_target: string): RDBRepository<Entity> {
22
+ return this.repository as RDBRepository<Entity>;
23
+ }
24
+
25
+ // TODO: column aliases
26
+ insert(data: Record<string, unknown>[]): Promise<unknown> {
27
+ return this.client.insert({
28
+ format: 'JSONEachRow',
29
+ table: this.repository.metadata.tableName,
30
+ values: data
31
+ });
32
+ }
33
+
34
+ async query<ReturnData = unknown>(
35
+ query: string,
36
+ params?: { field: string; value: string | number }[]
37
+ ): Promise<ReturnData> {
38
+ let queryParams: Record<string, string | number> | undefined = undefined;
39
+ if (params?.length) {
40
+ queryParams = {};
41
+ params.forEach(item => (queryParams![item.field] = item.value));
42
+ }
43
+ const results = await this.client.query({ format: 'JSON', query, query_params: queryParams });
44
+ const jsonData = await results.json();
45
+ return jsonData as ReturnData;
46
+ }
47
+
48
+ // TODO: figure out how to de-circularize this
49
+ save<Entity extends GenericObject<unknown> = GenericObject<unknown>>(
50
+ _target: unknown,
51
+ data: Partial<Entity> | Partial<Entity[]>,
52
+ options?: unknown
53
+ ): Promise<unknown> {
54
+ return this.repository.save(data, options);
55
+ }
56
+
57
+ // TODO: actual transactions
58
+ transaction(callback: (_em: ClickHouseEntityManager) => Promise<unknown>): Promise<unknown> {
59
+ return callback(this);
60
+ }
61
+ }
@@ -0,0 +1 @@
1
+ export * from './clickhouse.entity.manager';
@@ -0,0 +1,21 @@
1
+ import { ConfigProviderService, DataDefaultData, GenericObject } from '@node-c/core';
2
+ import { RDBEntityService, SQLQueryBuilderService } from '@node-c/data-rdb';
3
+
4
+ import { ClickHouseDBEntitySchema, ClickHouseDBRepository } from '../repository';
5
+
6
+ export class ClickHouseDBEntityService<
7
+ Entity extends GenericObject,
8
+ Data extends DataDefaultData<Entity> = DataDefaultData<Entity>
9
+ > extends RDBEntityService<Entity, Data> {
10
+ protected primaryKeys: string[];
11
+
12
+ constructor(
13
+ protected configProvider: ConfigProviderService,
14
+ protected qb: SQLQueryBuilderService,
15
+ protected repository: ClickHouseDBRepository<Entity>,
16
+ protected schema: ClickHouseDBEntitySchema<Entity>
17
+ ) {
18
+ super(configProvider, qb, repository, schema);
19
+ this.primaryKeys = repository.primaryKeys;
20
+ }
21
+ }
@@ -0,0 +1 @@
1
+ export * from './clickhouse.entity.service';
package/src/index.ts ADDED
@@ -0,0 +1,7 @@
1
+ export * from './common/definitions';
2
+ export * from './connectionModule';
3
+ export * from './entityManager';
4
+ export * from './entityService';
5
+ export * from './module';
6
+ export * from './ormQueryBuilder';
7
+ export * from './repository';
@@ -0,0 +1,18 @@
1
+ import { ModuleMetadata } from '@nestjs/common';
2
+
3
+ import { GenericObject } from '@node-c/core';
4
+
5
+ export interface ClickHouseDBModuleOptions {
6
+ entityModuleRegisterOptions?: unknown;
7
+ exports?: ModuleMetadata['exports'];
8
+ folderData: GenericObject<unknown>;
9
+ imports?: {
10
+ atEnd?: ModuleMetadata['imports'];
11
+ postORM?: ModuleMetadata['imports'];
12
+ preORM?: ModuleMetadata['imports'];
13
+ };
14
+ moduleClass: unknown;
15
+ moduleName: string;
16
+ providers?: ModuleMetadata['providers'];
17
+ registerOptionsPerEntityModule?: GenericObject;
18
+ }
@@ -0,0 +1,33 @@
1
+ import { DynamicModule } from '@nestjs/common';
2
+
3
+ import { loadDynamicModules } from '@node-c/core';
4
+ import { SQLQueryBuilderModule } from '@node-c/data-rdb';
5
+
6
+ import { ClickHouseDBModuleOptions } from './clickhouse.module.definitions';
7
+
8
+ import { ClickHouseConnectionModule } from '../connectionModule';
9
+
10
+ export class ClickHouseDBModule {
11
+ static register(options: ClickHouseDBModuleOptions): DynamicModule {
12
+ const { folderData, imports: additionalImports, moduleClass, moduleName } = options;
13
+ const { atEnd: importsAtEnd, postORM: importsPostORM, preORM: importsPreORM } = additionalImports || {};
14
+ const { modules } = loadDynamicModules(folderData, {
15
+ moduleRegisterOptions: options.entityModuleRegisterOptions,
16
+ registerOptionsPerModule: options.registerOptionsPerEntityModule
17
+ });
18
+ return {
19
+ global: true,
20
+ module: moduleClass as DynamicModule['module'],
21
+ imports: [
22
+ ...(importsPreORM || []),
23
+ ClickHouseConnectionModule.register({ dataModuleName: moduleName }),
24
+ SQLQueryBuilderModule.register({ dataModuleName: moduleName }),
25
+ ...(importsPostORM || []),
26
+ ...(modules || []),
27
+ ...(importsAtEnd || [])
28
+ ],
29
+ providers: [...(options.providers || [])],
30
+ exports: [...(modules || []), ...(options.exports || [])]
31
+ };
32
+ }
33
+ }
@@ -0,0 +1,2 @@
1
+ export * from './clickhouse.module.definitions';
2
+ export * from './clickhouse.module';
@@ -0,0 +1,181 @@
1
+ import { ApplicationError, DataOrderByDirection, GenericObject } from '@node-c/core';
2
+ import { OrmDeleteQueryBuilder, OrmSelectQueryBuilder, OrmUpdateQueryBuilder } from '@node-c/data-rdb';
3
+
4
+ import { ClickHouseEntityManager } from '../entityManager';
5
+ import { ClickHouseDBEntitySchema } from '../repository/clickhouse.repository.definitions';
6
+
7
+ // TODO: field selection, join, update, delete
8
+ export class ClickHouseSelectQueryBuilder<Entity extends GenericObject<unknown>>
9
+ implements OrmSelectQueryBuilder<Entity>
10
+ {
11
+ protected deletedColumn?: string;
12
+ protected limitClause: string = '';
13
+ protected offsetClause: string = '';
14
+ protected orderByClause: string = '';
15
+ protected whereClause: string = '';
16
+ protected withDeletedEnabled: boolean = false;
17
+
18
+ constructor(
19
+ // eslint-disable-next-line no-unused-vars
20
+ protected manager: ClickHouseEntityManager,
21
+ // eslint-disable-next-line no-unused-vars
22
+ protected schema: ClickHouseDBEntitySchema<Entity>
23
+ ) {
24
+ const {
25
+ options: { columns }
26
+ } = schema;
27
+ for (const columnName in columns) {
28
+ if (columns[columnName].isDeletionDate) {
29
+ this.deletedColumn = columnName;
30
+ break;
31
+ }
32
+ }
33
+ }
34
+
35
+ protected addDeletedToWhereClause(): string {
36
+ const { deletedColumn, whereClause, withDeletedEnabled } = this;
37
+ return !withDeletedEnabled && deletedColumn
38
+ ? `${whereClause.replace('where ', 'where (')}) and \`${deletedColumn}\` is null`
39
+ : whereClause;
40
+ }
41
+
42
+ addOrderBy(field: string, direction: DataOrderByDirection): ClickHouseSelectQueryBuilder<Entity> {
43
+ this.orderByClause += `, ${this.parseOrderByClause(field, direction)}`;
44
+ return this;
45
+ }
46
+
47
+ andWhere(query: string, params?: GenericObject<unknown>): ClickHouseSelectQueryBuilder<Entity> {
48
+ this.whereClause += ` and (${this.parseWhereClause(query, params)})`;
49
+ return this;
50
+ }
51
+
52
+ delete(): OrmDeleteQueryBuilder<Entity> {
53
+ throw new ApplicationError('Method ClickHouseSelectQueryBuilder.delete not implemented.');
54
+ }
55
+
56
+ async getCount(): Promise<number> {
57
+ const {
58
+ limitClause,
59
+ offsetClause,
60
+ orderByClause,
61
+ schema: {
62
+ options: { name, tableName }
63
+ }
64
+ } = this;
65
+ const result = await this.manager.query<{ data: [{ 'count()': string }] }>(
66
+ `select count() from \`${tableName}\` as \`${name}\` ` +
67
+ `${this.addDeletedToWhereClause()} ${orderByClause} ${limitClause} ${offsetClause}`
68
+ );
69
+ return parseInt(result.data[0]?.['count()'], 10);
70
+ }
71
+
72
+ async getMany(): Promise<Entity[]> {
73
+ const {
74
+ limitClause,
75
+ offsetClause,
76
+ orderByClause,
77
+ schema: {
78
+ options: { name, tableName }
79
+ }
80
+ } = this;
81
+ const result = await this.manager.query<{ data: Entity[] }>(
82
+ `select * from \`${tableName}\` as \`${name}\` ` +
83
+ `${this.addDeletedToWhereClause()} ${orderByClause} ${limitClause} ${offsetClause}`
84
+ );
85
+ return result.data;
86
+ }
87
+
88
+ async getOne(): Promise<Entity | null> {
89
+ const {
90
+ orderByClause,
91
+ schema: {
92
+ options: { name, tableName }
93
+ }
94
+ } = this;
95
+ const result = await this.manager.query<{ data: Entity }>(
96
+ `select * from \`${tableName}\` as \`${name}\` ${this.addDeletedToWhereClause()} ${orderByClause} limit 1`
97
+ );
98
+ return result.data || null;
99
+ }
100
+
101
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
102
+ leftJoinAndSelect(..._args: unknown[]): ClickHouseSelectQueryBuilder<Entity> {
103
+ throw new ApplicationError('Method ClickHouseSelectQueryBuilder.leftJoinAndSelect not implemented.');
104
+ }
105
+
106
+ orWhere(query: string, params?: GenericObject<unknown>): ClickHouseSelectQueryBuilder<Entity> {
107
+ this.whereClause += ` or (${this.parseWhereClause(query, params)})`;
108
+ return this;
109
+ }
110
+
111
+ orderBy(field: string, direction: DataOrderByDirection): ClickHouseSelectQueryBuilder<Entity> {
112
+ this.orderByClause += `order by ${this.parseOrderByClause(field, direction)}`;
113
+ return this;
114
+ }
115
+
116
+ protected parseOrderByClause(field: string, direction: DataOrderByDirection): string {
117
+ return `${field.replace(/[';]/g, '')} ${direction.replace(/[';]/g, '')}`;
118
+ }
119
+
120
+ protected parseWhereClause(query: string, params?: GenericObject<unknown>): string {
121
+ let queryWithReplacements = `${query.replace(/[';]/g, '')}`;
122
+ if (params) {
123
+ for (const paramName in params) {
124
+ const rawValue = params[paramName];
125
+ if (typeof rawValue === 'undefined') {
126
+ continue;
127
+ }
128
+ // TODO: process dates, arrays and stringifyable objects
129
+ let value = '';
130
+ if (typeof rawValue === 'string') {
131
+ value = `'${rawValue.replace(/'/g, "\'")}'`;
132
+ } else {
133
+ value = `${rawValue}`;
134
+ }
135
+ queryWithReplacements = queryWithReplacements.replace(`:${paramName}`, value);
136
+ }
137
+ }
138
+ return queryWithReplacements;
139
+ }
140
+
141
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
142
+ select(_selection: string[]): ClickHouseSelectQueryBuilder<Entity> {
143
+ throw new ApplicationError('Method ClickHouseSelectQueryBuilder.select not implemented.');
144
+ }
145
+
146
+ skip(skipCount: number): ClickHouseSelectQueryBuilder<Entity> {
147
+ // we need this to prevent SQL injection, since TS types don't work at runtime
148
+ if (typeof skipCount !== 'number') {
149
+ throw new ApplicationError('Method ClickHouseSelectQueryBuilder.skip expects a number input for skipCount.');
150
+ }
151
+ this.offsetClause = `offset ${skipCount}`;
152
+ return this;
153
+ }
154
+
155
+ softDelete(): OrmDeleteQueryBuilder<Entity> {
156
+ throw new ApplicationError('Method ClickHouseSelectQueryBuilder.softDelete not implemented.');
157
+ }
158
+
159
+ take(takeCount: number): ClickHouseSelectQueryBuilder<Entity> {
160
+ // we need this to prevent SQL injection, since TS types don't work at runtime
161
+ if (typeof takeCount !== 'number') {
162
+ throw new ApplicationError('Method ClickHouseSelectQueryBuilder.take expects a number input for takeCount.');
163
+ }
164
+ this.limitClause += `limit ${takeCount}`;
165
+ return this;
166
+ }
167
+
168
+ update(): OrmUpdateQueryBuilder<Entity> {
169
+ throw new ApplicationError('Method ClickHouseSelectQueryBuilder.update not implemented.');
170
+ }
171
+
172
+ where(query: string, params?: GenericObject<unknown>): ClickHouseSelectQueryBuilder<Entity> {
173
+ this.whereClause += `where (${this.parseWhereClause(query, params)})`;
174
+ return this;
175
+ }
176
+
177
+ withDeleted(): OrmSelectQueryBuilder<Entity> {
178
+ this.withDeletedEnabled = true;
179
+ return this;
180
+ }
181
+ }
@@ -0,0 +1 @@
1
+ export * from './clickhouse.selectQueryBuilder';
@@ -0,0 +1,49 @@
1
+ import { GenericObject } from '@node-c/core';
2
+ import { RDBEntitySchema } from '@node-c/data-rdb';
3
+
4
+ export interface ClickHouseDBEntitySchema<EntityClass extends GenericObject<unknown>> extends RDBEntitySchema {
5
+ options: {
6
+ columns: {
7
+ // eslint-disable-next-line no-unused-vars
8
+ [columnName in keyof EntityClass]: ClickHouseDBEntitySchemaColumnOptions;
9
+ };
10
+ name: string;
11
+ paranoid?: boolean;
12
+ tableName: string;
13
+ };
14
+ }
15
+
16
+ export interface ClickHouseDBEntitySchemaColumnOptions {
17
+ generated?: boolean;
18
+ isCreationDate?: boolean;
19
+ isDeletionDate?: boolean;
20
+ isUpdateDate?: boolean;
21
+ primary?: boolean;
22
+ type?: ClickHouseDBEntitySchemaColumnType;
23
+ }
24
+
25
+ export enum ClickHouseDBEntitySchemaColumnType {
26
+ // eslint-disable-next-line no-unused-vars
27
+ BigInteger = 'BIGINT',
28
+ // eslint-disable-next-line no-unused-vars
29
+ Boolean = 'BOOL',
30
+ // eslint-disable-next-line no-unused-vars
31
+ DateTime = 'DATETIME',
32
+ // eslint-disable-next-line no-unused-vars
33
+ Enum = 'ENUM',
34
+ // eslint-disable-next-line no-unused-vars
35
+ Integer = 'INT',
36
+ // eslint-disable-next-line no-unused-vars
37
+ JSON = 'JSON',
38
+ // eslint-disable-next-line no-unused-vars
39
+ Text = 'TEXT',
40
+ // eslint-disable-next-line no-unused-vars
41
+ UUID = 'UUID',
42
+ // eslint-disable-next-line no-unused-vars
43
+ Varchar = 'VARCHAR'
44
+ }
45
+
46
+ export interface ClickHouseDBRepositoryModuleOptions<EntityClass extends GenericObject<unknown>> {
47
+ entitySchema: ClickHouseDBEntitySchema<EntityClass>;
48
+ dataModuleName: string;
49
+ }
@@ -0,0 +1,58 @@
1
+ import { ClickHouseClient } from '@clickhouse/client';
2
+ import { DynamicModule, Module } from '@nestjs/common';
3
+
4
+ import { GenericObject } from '@node-c/core';
5
+ import { Constants as RDBConstants, SQLQueryBuilderService } from '@node-c/data-rdb';
6
+
7
+ import { ClickHouseDBRepository } from './clickhouse.repository';
8
+ import { ClickHouseDBRepositoryModuleOptions } from './clickhouse.repository.definitions';
9
+
10
+ import { Constants } from '../common/definitions';
11
+ import { ClickHouseEntityManager } from '../entityManager';
12
+
13
+ @Module({})
14
+ export class ClickHouseDBRepositoryModule {
15
+ static register<Entity extends GenericObject<unknown>>(
16
+ options: ClickHouseDBRepositoryModuleOptions<Entity>
17
+ ): DynamicModule {
18
+ const { entitySchema, dataModuleName } = options;
19
+ const clientName = `${Constants.CLICKHOUSE_CLIENT_PREFIX}${dataModuleName}`;
20
+ return {
21
+ module: ClickHouseDBRepositoryModule,
22
+ providers: [
23
+ {
24
+ provide: SQLQueryBuilderService,
25
+ useFactory: (sqlQueryBuilderService: SQLQueryBuilderService) => sqlQueryBuilderService,
26
+ inject: [`${dataModuleName}${RDBConstants.SQL_BUILDER_SERVICE_TOKEN_SUFFIX}`]
27
+ },
28
+ {
29
+ provide: Constants.CLICKHOUSE_CLIENT,
30
+ useFactory: (clickhouseClient: ClickHouseClient) => clickhouseClient,
31
+ inject: [clientName]
32
+ },
33
+ {
34
+ provide: RDBConstants.RDB_REPOSITORY_ENTITY_CLASS,
35
+ useValue: entitySchema
36
+ },
37
+ ClickHouseEntityManager,
38
+ ClickHouseDBRepository<Entity>,
39
+ {
40
+ provide: RDBConstants.RDB_ENTITY_REPOSITORY,
41
+ useExisting: ClickHouseDBRepository<Entity>
42
+ }
43
+ ],
44
+ exports: [
45
+ SQLQueryBuilderService,
46
+ {
47
+ provide: RDBConstants.RDB_ENTITY_REPOSITORY,
48
+ useExisting: ClickHouseDBRepository<Entity>
49
+ },
50
+ {
51
+ provide: Constants.CLICKHOUSE_CLIENT,
52
+ useFactory: (clickhouseClient: ClickHouseClient) => clickhouseClient,
53
+ inject: [clientName]
54
+ }
55
+ ]
56
+ };
57
+ }
58
+ }
@@ -0,0 +1,68 @@
1
+ import { Inject, Injectable, forwardRef } from '@nestjs/common';
2
+
3
+ import { GenericObject } from '@node-c/core';
4
+ import { Constants as RDBConstants, RDBRepository } from '@node-c/data-rdb';
5
+
6
+ import { ClickHouseDBEntitySchema } from './clickhouse.repository.definitions';
7
+
8
+ import { ClickHouseEntityManager } from '../entityManager';
9
+ import { ClickHouseSelectQueryBuilder } from '../ormQueryBuilder';
10
+
11
+ // TODO: save method
12
+ @Injectable()
13
+ export class ClickHouseDBRepository<Entity extends GenericObject<unknown>> implements RDBRepository<Entity> {
14
+ readonly metadata: { name: string; tableName: string };
15
+ readonly primaryKeys: string[];
16
+ readonly target: string;
17
+
18
+ constructor(
19
+ @Inject(RDBConstants.RDB_REPOSITORY_ENTITY_CLASS)
20
+ protected entitySchema: ClickHouseDBEntitySchema<Entity>,
21
+ @Inject(forwardRef(() => ClickHouseEntityManager))
22
+ // eslint-disable-next-line no-unused-vars
23
+ public readonly manager: ClickHouseEntityManager
24
+ ) {
25
+ const {
26
+ options: { columns, name, tableName }
27
+ } = entitySchema;
28
+ const primaryKeys: string[] = [];
29
+ this.metadata = { name, tableName };
30
+ for (const columnName in columns) {
31
+ if (columns[columnName]?.primary) {
32
+ primaryKeys.push(columnName);
33
+ }
34
+ }
35
+ this.primaryKeys = primaryKeys;
36
+ }
37
+
38
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
39
+ createQueryBuilder(_entityName: string, _queryRunner?: unknown): ClickHouseSelectQueryBuilder<Entity> {
40
+ return new ClickHouseSelectQueryBuilder(this.manager, this.entitySchema);
41
+ }
42
+
43
+ // TODO: update
44
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
45
+ save(data: Partial<Entity> | Partial<Entity[]>, _options?: unknown): Promise<unknown> {
46
+ // throw new ApplicationError('Method ClickHouseDBRepository.save not implemented.');
47
+ const dataInput = (data instanceof Array ? data : [data]) as Entity[];
48
+ // const {
49
+ // options: { columns }
50
+ // } = this.entitySchema;
51
+ // const columnsMap: GenericObject<boolean> = {};
52
+ // const params: GenericObject<unknown> = {};
53
+ // // first pass - go through all data items and make a list of columns for the header of the insert query
54
+ // dataInput.forEach(dataItem => {
55
+ // for (const columnName in columns) {
56
+ // const value = dataItem[columnName];
57
+ // if (typeof value === 'undefined') {
58
+ // continue;
59
+ // }
60
+ // if (!columnsMap[columnName]) {
61
+ // columnsMap[columnName] = true;
62
+ // }
63
+ // }
64
+ // });
65
+ // // second pass - prepare the data itself
66
+ return this.manager.insert(dataInput);
67
+ }
68
+ }
@@ -0,0 +1,3 @@
1
+ export * from './clickhouse.repository';
2
+ export * from './clickhouse.repository.definitions';
3
+ export * from './clickhouse.repository.module';