@node-c/data-clickhouse 1.0.0-alpha63 → 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 +1 -1
- package/dist/connectionModule/clickhouse.connection.module.js +1 -1
- package/dist/connectionModule/clickhouse.connection.module.js.map +1 -1
- package/package.json +5 -5
- package/src/common/definitions/common.constants.ts +6 -0
- package/src/common/definitions/index.ts +1 -0
- package/src/connectionModule/clickhouse.connection.module.definitions.ts +3 -0
- package/src/connectionModule/clickhouse.connection.module.ts +74 -0
- package/src/connectionModule/index.ts +2 -0
- package/src/entityManager/clickhouse.entity.manager.ts +61 -0
- package/src/entityManager/index.ts +1 -0
- package/src/entityService/clickhouse.entity.service.ts +21 -0
- package/src/entityService/index.ts +1 -0
- package/src/index.ts +7 -0
- package/src/module/clickhouse.module.definitions.ts +18 -0
- package/src/module/clickhouse.module.ts +33 -0
- package/src/module/index.ts +2 -0
- package/src/ormQueryBuilder/clickhouse.selectQueryBuilder.ts +181 -0
- package/src/ormQueryBuilder/index.ts +1 -0
- package/src/repository/clickhouse.repository.definitions.ts +49 -0
- package/src/repository/clickhouse.repository.module.ts +58 -0
- package/src/repository/clickhouse.repository.ts +68 -0
- package/src/repository/index.ts +3 -0
package/README.md
CHANGED
|
@@ -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
|
|
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,
|
|
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-
|
|
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": "^
|
|
19
|
-
"@nestjs/typeorm": "^
|
|
20
|
-
"@node-c/core": "^1.0.0-
|
|
21
|
-
"@node-c/data-rdb": "^1.0.0-
|
|
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 @@
|
|
|
1
|
+
export * from './common.constants';
|
|
@@ -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,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,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,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
|
+
}
|