namirasoft-node-clickhouse 1.4.4 → 1.4.5
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/package.json +1 -1
- package/src/BaseClickhouseDatabase.ts +117 -117
- package/src/BaseClickhouseModelSchema.ts +4 -4
- package/src/BaseClickhouseModelSchemaColumnOptions.ts +6 -6
- package/src/BaseClickhouseTable.ts +176 -176
- package/src/BaseFilterItemBuilderClickhouse.ts +107 -107
- package/src/FindOptions.ts +7 -7
- package/src/index.ts +4 -4
package/package.json
CHANGED
|
@@ -1,118 +1,118 @@
|
|
|
1
|
-
import { Logger } from "namirasoft-log";
|
|
2
|
-
import { BaseDatabase, BaseFilterItemBuilder_JoinTable } from "namirasoft-node";
|
|
3
|
-
import { createClient } from '@clickhouse/client';
|
|
4
|
-
import { NodeClickHouseClient } from '@clickhouse/client/dist/client';
|
|
5
|
-
import { BaseClickhouseTable } from "./BaseClickhouseTable";
|
|
6
|
-
import { FilterItem, SortItem } from "namirasoft-core";
|
|
7
|
-
import { FindOptions } from "./FindOptions";
|
|
8
|
-
import { BaseFilterItemBuilderClickhouse } from "./BaseFilterItemBuilderClickhouse";
|
|
9
|
-
|
|
10
|
-
export abstract class BaseClickhouseDatabase extends BaseDatabase
|
|
11
|
-
{
|
|
12
|
-
public client!: NodeClickHouseClient;
|
|
13
|
-
private name: string;
|
|
14
|
-
constructor(isHTTPS: boolean, host: string, port: number, name: string, user: string, pass: string, params?: { [name: string]: string })
|
|
15
|
-
{
|
|
16
|
-
super();
|
|
17
|
-
let parameters = Object.keys(params ?? {}).map(key => `${key}=>${params?.[key]}`).join("&");
|
|
18
|
-
if (!process.env.NAMIRASOFT_MUTE)
|
|
19
|
-
{
|
|
20
|
-
this.client = createClient({
|
|
21
|
-
url: `http${isHTTPS ? "s" : ""}://${user}:${pass}@${host}:${port}/${name}${parameters.length > 0 ? ("?" + parameters) : ""}`,
|
|
22
|
-
})
|
|
23
|
-
}
|
|
24
|
-
this.name = name;
|
|
25
|
-
}
|
|
26
|
-
override async connect()
|
|
27
|
-
{
|
|
28
|
-
if (!process.env.NAMIRASOFT_MUTE)
|
|
29
|
-
{
|
|
30
|
-
await this.client.ping();
|
|
31
|
-
Logger.main?.success(`Database Clickhouse was connected to ${this.name}`);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
override async sync(force: boolean)
|
|
35
|
-
{
|
|
36
|
-
if (!process.env.NAMIRASOFT_MUTE)
|
|
37
|
-
if (force)
|
|
38
|
-
{
|
|
39
|
-
// There is no such a feature
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
getQuery(table: string, option: FindOptions): string
|
|
43
|
-
{
|
|
44
|
-
let columns = (option.columns ?? []).filter(c => c);
|
|
45
|
-
let where = (option.where ?? []).filter(w => w);
|
|
46
|
-
let order = (option.order ?? []).filter(o => o);
|
|
47
|
-
|
|
48
|
-
let parts = ["SELECT"];
|
|
49
|
-
if (columns.length == 0)
|
|
50
|
-
parts.push("*");
|
|
51
|
-
parts.push(columns.join(", "));
|
|
52
|
-
parts.push("FROM");
|
|
53
|
-
parts.push(table);
|
|
54
|
-
if (where.length > 0)
|
|
55
|
-
{
|
|
56
|
-
parts.push("WHERE");
|
|
57
|
-
parts.push(where.join(" AND "));
|
|
58
|
-
}
|
|
59
|
-
if (order.length > 0)
|
|
60
|
-
{
|
|
61
|
-
parts.push("
|
|
62
|
-
parts.push(order.join(", "));
|
|
63
|
-
}
|
|
64
|
-
if (option.limit)
|
|
65
|
-
{
|
|
66
|
-
parts.push(`LIMIT ${option.limit}`);
|
|
67
|
-
if (option.offset)
|
|
68
|
-
parts.push(`OFFSET ${option.offset}`);
|
|
69
|
-
}
|
|
70
|
-
return parts.join(" ");
|
|
71
|
-
}
|
|
72
|
-
async select<T>(query: string): Promise<T[]>
|
|
73
|
-
{
|
|
74
|
-
let res = await this.client.query({
|
|
75
|
-
query,
|
|
76
|
-
format: "JSON"
|
|
77
|
-
});
|
|
78
|
-
let ans = await res.json<T>();
|
|
79
|
-
return ans.data;
|
|
80
|
-
}
|
|
81
|
-
async getFiltersConditions(table_main: BaseClickhouseTable<BaseClickhouseDatabase>, table_joins: { [table: string]: BaseFilterItemBuilder_JoinTable<string[]> }, filters?: FilterItem[] | undefined): Promise<string[][]>
|
|
82
|
-
{
|
|
83
|
-
let builder = new BaseFilterItemBuilderClickhouse(this);
|
|
84
|
-
return await builder.build(table_main, table_joins, filters);
|
|
85
|
-
}
|
|
86
|
-
getAdvancedSearchConditions(columns: string[], search: string): string[]
|
|
87
|
-
{
|
|
88
|
-
const conditions: string[] = [];
|
|
89
|
-
|
|
90
|
-
if (search && search.split)
|
|
91
|
-
{
|
|
92
|
-
const toks = search.split(' ').filter(Boolean);
|
|
93
|
-
|
|
94
|
-
for (const tok of toks)
|
|
95
|
-
{
|
|
96
|
-
const safeTok = tok.trim().replace(/'/g, "\\'"); // escape single quotes
|
|
97
|
-
const cs = columns.map(column => `ifNull(${column}, '')`);
|
|
98
|
-
const concatExpr = `concat(${cs.join(', ')})`;
|
|
99
|
-
const condition = `positionCaseInsensitiveUTF8(${concatExpr}, '${safeTok}') > 0`;
|
|
100
|
-
conditions.push(condition);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
return conditions;
|
|
105
|
-
}
|
|
106
|
-
override getSortOptions(sorts?: SortItem[] | undefined): string[]
|
|
107
|
-
{
|
|
108
|
-
let ans: string[] = sorts?.filter(s => s).map(s =>
|
|
109
|
-
{
|
|
110
|
-
return `s.column.name ${s.ascending ? "ASC" : "DESC"}`;
|
|
111
|
-
}) ?? [];
|
|
112
|
-
|
|
113
|
-
if (!ans || ans.length == 0)
|
|
114
|
-
ans = ["created_at DESC"];
|
|
115
|
-
|
|
116
|
-
return ans;
|
|
117
|
-
}
|
|
1
|
+
import { Logger } from "namirasoft-log";
|
|
2
|
+
import { BaseDatabase, BaseFilterItemBuilder_JoinTable } from "namirasoft-node";
|
|
3
|
+
import { createClient } from '@clickhouse/client';
|
|
4
|
+
import { NodeClickHouseClient } from '@clickhouse/client/dist/client';
|
|
5
|
+
import { BaseClickhouseTable } from "./BaseClickhouseTable";
|
|
6
|
+
import { FilterItem, SortItem } from "namirasoft-core";
|
|
7
|
+
import { FindOptions } from "./FindOptions";
|
|
8
|
+
import { BaseFilterItemBuilderClickhouse } from "./BaseFilterItemBuilderClickhouse";
|
|
9
|
+
|
|
10
|
+
export abstract class BaseClickhouseDatabase extends BaseDatabase
|
|
11
|
+
{
|
|
12
|
+
public client!: NodeClickHouseClient;
|
|
13
|
+
private name: string;
|
|
14
|
+
constructor(isHTTPS: boolean, host: string, port: number, name: string, user: string, pass: string, params?: { [name: string]: string })
|
|
15
|
+
{
|
|
16
|
+
super();
|
|
17
|
+
let parameters = Object.keys(params ?? {}).map(key => `${key}=>${params?.[key]}`).join("&");
|
|
18
|
+
if (!process.env.NAMIRASOFT_MUTE)
|
|
19
|
+
{
|
|
20
|
+
this.client = createClient({
|
|
21
|
+
url: `http${isHTTPS ? "s" : ""}://${user}:${pass}@${host}:${port}/${name}${parameters.length > 0 ? ("?" + parameters) : ""}`,
|
|
22
|
+
})
|
|
23
|
+
}
|
|
24
|
+
this.name = name;
|
|
25
|
+
}
|
|
26
|
+
override async connect()
|
|
27
|
+
{
|
|
28
|
+
if (!process.env.NAMIRASOFT_MUTE)
|
|
29
|
+
{
|
|
30
|
+
await this.client.ping();
|
|
31
|
+
Logger.main?.success(`Database Clickhouse was connected to ${this.name}`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
override async sync(force: boolean)
|
|
35
|
+
{
|
|
36
|
+
if (!process.env.NAMIRASOFT_MUTE)
|
|
37
|
+
if (force)
|
|
38
|
+
{
|
|
39
|
+
// There is no such a feature
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
getQuery(table: string, option: FindOptions): string
|
|
43
|
+
{
|
|
44
|
+
let columns = (option.columns ?? []).filter(c => c);
|
|
45
|
+
let where = (option.where ?? []).filter(w => w);
|
|
46
|
+
let order = (option.order ?? []).filter(o => o);
|
|
47
|
+
|
|
48
|
+
let parts = ["SELECT"];
|
|
49
|
+
if (columns.length == 0)
|
|
50
|
+
parts.push("*");
|
|
51
|
+
parts.push(columns.join(", "));
|
|
52
|
+
parts.push("FROM");
|
|
53
|
+
parts.push(`${this.name}.${table}`);
|
|
54
|
+
if (where.length > 0)
|
|
55
|
+
{
|
|
56
|
+
parts.push("WHERE");
|
|
57
|
+
parts.push(where.join(" AND "));
|
|
58
|
+
}
|
|
59
|
+
if (order.length > 0)
|
|
60
|
+
{
|
|
61
|
+
parts.push("ORDER BY");
|
|
62
|
+
parts.push(order.join(", "));
|
|
63
|
+
}
|
|
64
|
+
if (option.limit)
|
|
65
|
+
{
|
|
66
|
+
parts.push(`LIMIT ${option.limit}`);
|
|
67
|
+
if (option.offset)
|
|
68
|
+
parts.push(`OFFSET ${option.offset}`);
|
|
69
|
+
}
|
|
70
|
+
return parts.join(" ");
|
|
71
|
+
}
|
|
72
|
+
async select<T>(query: string): Promise<T[]>
|
|
73
|
+
{
|
|
74
|
+
let res = await this.client.query({
|
|
75
|
+
query,
|
|
76
|
+
format: "JSON"
|
|
77
|
+
});
|
|
78
|
+
let ans = await res.json<T>();
|
|
79
|
+
return ans.data;
|
|
80
|
+
}
|
|
81
|
+
async getFiltersConditions(table_main: BaseClickhouseTable<BaseClickhouseDatabase>, table_joins: { [table: string]: BaseFilterItemBuilder_JoinTable<string[]> }, filters?: FilterItem[] | undefined): Promise<string[][]>
|
|
82
|
+
{
|
|
83
|
+
let builder = new BaseFilterItemBuilderClickhouse(this);
|
|
84
|
+
return await builder.build(table_main, table_joins, filters);
|
|
85
|
+
}
|
|
86
|
+
getAdvancedSearchConditions(columns: string[], search: string): string[]
|
|
87
|
+
{
|
|
88
|
+
const conditions: string[] = [];
|
|
89
|
+
|
|
90
|
+
if (search && search.split)
|
|
91
|
+
{
|
|
92
|
+
const toks = search.split(' ').filter(Boolean);
|
|
93
|
+
|
|
94
|
+
for (const tok of toks)
|
|
95
|
+
{
|
|
96
|
+
const safeTok = tok.trim().replace(/'/g, "\\'"); // escape single quotes
|
|
97
|
+
const cs = columns.map(column => `ifNull(${column}, '')`);
|
|
98
|
+
const concatExpr = `concat(${cs.join(', ')})`;
|
|
99
|
+
const condition = `positionCaseInsensitiveUTF8(${concatExpr}, '${safeTok}') > 0`;
|
|
100
|
+
conditions.push(condition);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return conditions;
|
|
105
|
+
}
|
|
106
|
+
override getSortOptions(sorts?: SortItem[] | undefined): string[]
|
|
107
|
+
{
|
|
108
|
+
let ans: string[] = sorts?.filter(s => s).map(s =>
|
|
109
|
+
{
|
|
110
|
+
return `s.column.name ${s.ascending ? "ASC" : "DESC"}`;
|
|
111
|
+
}) ?? [];
|
|
112
|
+
|
|
113
|
+
if (!ans || ans.length == 0)
|
|
114
|
+
ans = ["created_at DESC"];
|
|
115
|
+
|
|
116
|
+
return ans;
|
|
117
|
+
}
|
|
118
118
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { BaseClickhouseModelSchemaColumnOptions } from "./BaseClickhouseModelSchemaColumnOptions";
|
|
2
|
-
|
|
3
|
-
export type BaseClickhouseModelSchema<TAttributes = any> = {
|
|
4
|
-
[name in keyof TAttributes]: BaseClickhouseModelSchemaColumnOptions;
|
|
1
|
+
import { BaseClickhouseModelSchemaColumnOptions } from "./BaseClickhouseModelSchemaColumnOptions";
|
|
2
|
+
|
|
3
|
+
export type BaseClickhouseModelSchema<TAttributes = any> = {
|
|
4
|
+
[name in keyof TAttributes]: BaseClickhouseModelSchemaColumnOptions;
|
|
5
5
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { BaseTableColumnOptions } from "namirasoft-node";
|
|
2
|
-
|
|
3
|
-
export interface BaseClickhouseModelSchemaColumnOptions extends BaseTableColumnOptions
|
|
4
|
-
{
|
|
5
|
-
type: any;
|
|
6
|
-
required: boolean;
|
|
1
|
+
import { BaseTableColumnOptions } from "namirasoft-node";
|
|
2
|
+
|
|
3
|
+
export interface BaseClickhouseModelSchemaColumnOptions extends BaseTableColumnOptions
|
|
4
|
+
{
|
|
5
|
+
type: any;
|
|
6
|
+
required: boolean;
|
|
7
7
|
};
|
|
@@ -1,177 +1,177 @@
|
|
|
1
|
-
import { BaseClickhouseDatabase } from "./BaseClickhouseDatabase";
|
|
2
|
-
import { BaseTable } from "namirasoft-node";
|
|
3
|
-
import { BaseMetaColumn, BaseMetaTable, SortItem } from "namirasoft-core";
|
|
4
|
-
import { AnySchema, ArraySchema, BaseTypeSchema, BoolSchema, DateTimeSchema, DoubleSchema, StringSchema, TypeSchema, VariableSchema } from "namirasoft-schema";
|
|
5
|
-
import { BaseClickhouseModelSchemaColumnOptions } from "./BaseClickhouseModelSchemaColumnOptions";
|
|
6
|
-
import { BaseClickhouseModelSchema } from "./BaseClickhouseModelSchema";
|
|
7
|
-
import { FindOptions } from "./FindOptions";
|
|
8
|
-
|
|
9
|
-
export abstract class BaseClickhouseTable<D extends BaseClickhouseDatabase> extends BaseTable<D, BaseClickhouseModelSchemaColumnOptions>
|
|
10
|
-
{
|
|
11
|
-
name!: string;
|
|
12
|
-
schema!: BaseClickhouseModelSchema;
|
|
13
|
-
constructor(database: D, name: string, schema: BaseClickhouseModelSchema)
|
|
14
|
-
{
|
|
15
|
-
super(database);
|
|
16
|
-
this.name = name;
|
|
17
|
-
this.schema = schema;
|
|
18
|
-
}
|
|
19
|
-
override getName(): string
|
|
20
|
-
{
|
|
21
|
-
return this.name;
|
|
22
|
-
}
|
|
23
|
-
override getSecureColumns(): string[]
|
|
24
|
-
{
|
|
25
|
-
let columns = super.getSecureColumns();
|
|
26
|
-
return ["_id", "__v", "deleted_at", "deletedAt", ...columns]
|
|
27
|
-
}
|
|
28
|
-
override getReadOnlyColumns(): string[]
|
|
29
|
-
{
|
|
30
|
-
let columns = super.getReadOnlyColumns();
|
|
31
|
-
return ["id", "created_at", "updated_at", "createdAt", "updatedAt", ...columns];
|
|
32
|
-
}
|
|
33
|
-
private getModelAllSchemas()
|
|
34
|
-
{
|
|
35
|
-
let otherAttributes: BaseClickhouseModelSchema = {
|
|
36
|
-
_id: { type: String, required: true },
|
|
37
|
-
__v: { type: String, required: true },
|
|
38
|
-
};
|
|
39
|
-
return { ... this.schema, ...otherAttributes };
|
|
40
|
-
}
|
|
41
|
-
public override getColumnOption(name: string)
|
|
42
|
-
{
|
|
43
|
-
let schema = this.getModelAllSchemas();
|
|
44
|
-
return schema[name] as BaseClickhouseModelSchemaColumnOptions;
|
|
45
|
-
}
|
|
46
|
-
public override forEachColumn(handler: (name: string, column: BaseClickhouseModelSchemaColumnOptions) => void)
|
|
47
|
-
{
|
|
48
|
-
let schema = this.getModelAllSchemas();
|
|
49
|
-
let keys = Object.keys(schema);
|
|
50
|
-
for (let i = 0; i < keys.length; i++)
|
|
51
|
-
handler(keys[i], this.getColumnOption(keys[i]));
|
|
52
|
-
}
|
|
53
|
-
public override getColumn(name: string): BaseMetaColumn
|
|
54
|
-
{
|
|
55
|
-
let option = this.getColumnOption(name);
|
|
56
|
-
let table_name = this.getName();
|
|
57
|
-
let table = new BaseMetaTable(null, table_name, table_name);
|
|
58
|
-
let type = option.type;
|
|
59
|
-
try
|
|
60
|
-
{
|
|
61
|
-
if (typeof (type) == "function")
|
|
62
|
-
type = type.name;
|
|
63
|
-
} catch (error)
|
|
64
|
-
{
|
|
65
|
-
}
|
|
66
|
-
let column = new BaseMetaColumn(table, name, name, type, option.required);
|
|
67
|
-
return column;
|
|
68
|
-
}
|
|
69
|
-
protected override getTypeSchema(option: BaseClickhouseModelSchemaColumnOptions): BaseTypeSchema
|
|
70
|
-
{
|
|
71
|
-
let required = option.required;
|
|
72
|
-
/* VariableType */
|
|
73
|
-
if (option.type === Boolean)
|
|
74
|
-
return new BoolSchema(required);
|
|
75
|
-
// else if (type instanceof Number)
|
|
76
|
-
// return new TinyIntSchema(required);
|
|
77
|
-
// else if (type instanceof DataTypes.SMALLINT)
|
|
78
|
-
// return new SmallIntSchema(required);
|
|
79
|
-
// else if (type instanceof DataTypes.MEDIUMINT)
|
|
80
|
-
// return new MediumIntSchema(required);
|
|
81
|
-
// else if (type instanceof DataTypes.INTEGER)
|
|
82
|
-
// return new IntegerSchema(required);
|
|
83
|
-
// else if (type instanceof DataTypes.BIGINT)
|
|
84
|
-
// return new BigIntSchema(required);
|
|
85
|
-
// else if (this.isFloat(type))
|
|
86
|
-
// return new FloatSchema(required, type.options.decimals);
|
|
87
|
-
else if (option.type === Number)
|
|
88
|
-
return new DoubleSchema(required, null);
|
|
89
|
-
// else if (this.isDecimal(type))
|
|
90
|
-
// return new DecimalSchema(required, type.options.scale);
|
|
91
|
-
// else if (this.isReal(type))
|
|
92
|
-
// return new RealSchema(required, type.options.decimals);
|
|
93
|
-
// else if (this.isChar(type))
|
|
94
|
-
// return new StringSchema(required, type.options.length, type.options.length);
|
|
95
|
-
else if (option.type === String)
|
|
96
|
-
return new StringSchema(required, null, null);
|
|
97
|
-
// else if (type instanceof DataTypes.TEXT)
|
|
98
|
-
// return new StringSchema(required, null, null);
|
|
99
|
-
else if (option.type === Date)
|
|
100
|
-
return new DateTimeSchema(required);
|
|
101
|
-
// else if (type instanceof DataTypes.DATEONLY)
|
|
102
|
-
// return new DateSchema(required);
|
|
103
|
-
// else if (type instanceof DataTypes.TIME)
|
|
104
|
-
// return new TimeSchema(required);
|
|
105
|
-
// else if (type instanceof DataTypes.DURATION)
|
|
106
|
-
// return new DurationSchema(required);
|
|
107
|
-
// else if (this.isEnum(type))
|
|
108
|
-
// {
|
|
109
|
-
// let name = NamingConvention.lower_case_underscore.convert(this.model.modelName + "_" + element.field, NamingConvention.Pascal_Case);
|
|
110
|
-
// return new EnumSchema(name, required, type.options.values);
|
|
111
|
-
// }
|
|
112
|
-
// else if (type instanceof DataTypes.JSON)
|
|
113
|
-
// return new AnySchema(required);
|
|
114
|
-
else if (option.type === Object)
|
|
115
|
-
{
|
|
116
|
-
let tags_type = option.tags?.type;
|
|
117
|
-
if (tags_type)
|
|
118
|
-
{
|
|
119
|
-
if (tags_type === "BaseTypeSchema")
|
|
120
|
-
return new TypeSchema(required);
|
|
121
|
-
if (tags_type === "BaseVariableSchema")
|
|
122
|
-
return new VariableSchema(required);
|
|
123
|
-
if (tags_type === "BaseTypeSchema[]")
|
|
124
|
-
return new ArraySchema(required, [new TypeSchema(required)]);
|
|
125
|
-
if (tags_type === "BaseVariableSchema[]")
|
|
126
|
-
return new ArraySchema(required, [new VariableSchema(required)]);
|
|
127
|
-
}
|
|
128
|
-
return new AnySchema(required);
|
|
129
|
-
}
|
|
130
|
-
throw new Error("Unsupported datatype for schema: " + option);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
static formatDate(date: Date): string
|
|
134
|
-
{
|
|
135
|
-
return date.toISOString().replace('T', ' ').substring(0, 19); // "YYYY-MM-DD HH:mm:ss"
|
|
136
|
-
}
|
|
137
|
-
async _getOrNull<M>(option: FindOptions): Promise<M | null>
|
|
138
|
-
{
|
|
139
|
-
let query = this.database.getQuery(this.name, option);
|
|
140
|
-
let ans = await this.database.select<M>(query);
|
|
141
|
-
if (ans.length > 0)
|
|
142
|
-
return ans[0];
|
|
143
|
-
return null;
|
|
144
|
-
}
|
|
145
|
-
async _get<M>(options: FindOptions): Promise<M>
|
|
146
|
-
{
|
|
147
|
-
let value = await this._getOrNull<M>(options);
|
|
148
|
-
if (value != null)
|
|
149
|
-
return value;
|
|
150
|
-
throw this.getNotFoundError(options.where);
|
|
151
|
-
}
|
|
152
|
-
async _list<M>(where: string[], pagination: { dont: true } | { page: number, size: number }, sorts: { dont: true } | SortItem[], options: FindOptions): Promise<{ rows: M[], count: number }>
|
|
153
|
-
{
|
|
154
|
-
let order: string[] = [];
|
|
155
|
-
if (!('dont' in pagination))
|
|
156
|
-
{
|
|
157
|
-
let p = this.database.paginate(pagination.page, pagination.size);
|
|
158
|
-
options.offset = p.offset;
|
|
159
|
-
options.limit = p.limit;
|
|
160
|
-
}
|
|
161
|
-
if (!('dont' in sorts))
|
|
162
|
-
order = this.database.getSortOptions(sorts);
|
|
163
|
-
|
|
164
|
-
if (!options.where)
|
|
165
|
-
options.where = [];
|
|
166
|
-
options.where.push(...where);
|
|
167
|
-
if (!options.order)
|
|
168
|
-
options.order = [];
|
|
169
|
-
options.order.push(...order);
|
|
170
|
-
|
|
171
|
-
let query_rows = this.database.getQuery(this.name, options);
|
|
172
|
-
let query_count = this.database.getQuery(this.name, { columns: ["Count(*) as total"], where: options.where });
|
|
173
|
-
let res_rows = await this.database.select<M>(query_rows);
|
|
174
|
-
let res_count = await this.database.select<{ total: number }>(query_count);
|
|
175
|
-
return { rows: res_rows, count: res_count[0].total };
|
|
176
|
-
}
|
|
1
|
+
import { BaseClickhouseDatabase } from "./BaseClickhouseDatabase";
|
|
2
|
+
import { BaseTable } from "namirasoft-node";
|
|
3
|
+
import { BaseMetaColumn, BaseMetaTable, SortItem } from "namirasoft-core";
|
|
4
|
+
import { AnySchema, ArraySchema, BaseTypeSchema, BoolSchema, DateTimeSchema, DoubleSchema, StringSchema, TypeSchema, VariableSchema } from "namirasoft-schema";
|
|
5
|
+
import { BaseClickhouseModelSchemaColumnOptions } from "./BaseClickhouseModelSchemaColumnOptions";
|
|
6
|
+
import { BaseClickhouseModelSchema } from "./BaseClickhouseModelSchema";
|
|
7
|
+
import { FindOptions } from "./FindOptions";
|
|
8
|
+
|
|
9
|
+
export abstract class BaseClickhouseTable<D extends BaseClickhouseDatabase> extends BaseTable<D, BaseClickhouseModelSchemaColumnOptions>
|
|
10
|
+
{
|
|
11
|
+
name!: string;
|
|
12
|
+
schema!: BaseClickhouseModelSchema;
|
|
13
|
+
constructor(database: D, name: string, schema: BaseClickhouseModelSchema)
|
|
14
|
+
{
|
|
15
|
+
super(database);
|
|
16
|
+
this.name = name;
|
|
17
|
+
this.schema = schema;
|
|
18
|
+
}
|
|
19
|
+
override getName(): string
|
|
20
|
+
{
|
|
21
|
+
return this.name;
|
|
22
|
+
}
|
|
23
|
+
override getSecureColumns(): string[]
|
|
24
|
+
{
|
|
25
|
+
let columns = super.getSecureColumns();
|
|
26
|
+
return ["_id", "__v", "deleted_at", "deletedAt", ...columns]
|
|
27
|
+
}
|
|
28
|
+
override getReadOnlyColumns(): string[]
|
|
29
|
+
{
|
|
30
|
+
let columns = super.getReadOnlyColumns();
|
|
31
|
+
return ["id", "created_at", "updated_at", "createdAt", "updatedAt", ...columns];
|
|
32
|
+
}
|
|
33
|
+
private getModelAllSchemas()
|
|
34
|
+
{
|
|
35
|
+
let otherAttributes: BaseClickhouseModelSchema = {
|
|
36
|
+
_id: { type: String, required: true },
|
|
37
|
+
__v: { type: String, required: true },
|
|
38
|
+
};
|
|
39
|
+
return { ... this.schema, ...otherAttributes };
|
|
40
|
+
}
|
|
41
|
+
public override getColumnOption(name: string)
|
|
42
|
+
{
|
|
43
|
+
let schema = this.getModelAllSchemas();
|
|
44
|
+
return schema[name] as BaseClickhouseModelSchemaColumnOptions;
|
|
45
|
+
}
|
|
46
|
+
public override forEachColumn(handler: (name: string, column: BaseClickhouseModelSchemaColumnOptions) => void)
|
|
47
|
+
{
|
|
48
|
+
let schema = this.getModelAllSchemas();
|
|
49
|
+
let keys = Object.keys(schema);
|
|
50
|
+
for (let i = 0; i < keys.length; i++)
|
|
51
|
+
handler(keys[i], this.getColumnOption(keys[i]));
|
|
52
|
+
}
|
|
53
|
+
public override getColumn(name: string): BaseMetaColumn
|
|
54
|
+
{
|
|
55
|
+
let option = this.getColumnOption(name);
|
|
56
|
+
let table_name = this.getName();
|
|
57
|
+
let table = new BaseMetaTable(null, table_name, table_name);
|
|
58
|
+
let type = option.type;
|
|
59
|
+
try
|
|
60
|
+
{
|
|
61
|
+
if (typeof (type) == "function")
|
|
62
|
+
type = type.name;
|
|
63
|
+
} catch (error)
|
|
64
|
+
{
|
|
65
|
+
}
|
|
66
|
+
let column = new BaseMetaColumn(table, name, name, type, option.required);
|
|
67
|
+
return column;
|
|
68
|
+
}
|
|
69
|
+
protected override getTypeSchema(option: BaseClickhouseModelSchemaColumnOptions): BaseTypeSchema
|
|
70
|
+
{
|
|
71
|
+
let required = option.required;
|
|
72
|
+
/* VariableType */
|
|
73
|
+
if (option.type === Boolean)
|
|
74
|
+
return new BoolSchema(required);
|
|
75
|
+
// else if (type instanceof Number)
|
|
76
|
+
// return new TinyIntSchema(required);
|
|
77
|
+
// else if (type instanceof DataTypes.SMALLINT)
|
|
78
|
+
// return new SmallIntSchema(required);
|
|
79
|
+
// else if (type instanceof DataTypes.MEDIUMINT)
|
|
80
|
+
// return new MediumIntSchema(required);
|
|
81
|
+
// else if (type instanceof DataTypes.INTEGER)
|
|
82
|
+
// return new IntegerSchema(required);
|
|
83
|
+
// else if (type instanceof DataTypes.BIGINT)
|
|
84
|
+
// return new BigIntSchema(required);
|
|
85
|
+
// else if (this.isFloat(type))
|
|
86
|
+
// return new FloatSchema(required, type.options.decimals);
|
|
87
|
+
else if (option.type === Number)
|
|
88
|
+
return new DoubleSchema(required, null);
|
|
89
|
+
// else if (this.isDecimal(type))
|
|
90
|
+
// return new DecimalSchema(required, type.options.scale);
|
|
91
|
+
// else if (this.isReal(type))
|
|
92
|
+
// return new RealSchema(required, type.options.decimals);
|
|
93
|
+
// else if (this.isChar(type))
|
|
94
|
+
// return new StringSchema(required, type.options.length, type.options.length);
|
|
95
|
+
else if (option.type === String)
|
|
96
|
+
return new StringSchema(required, null, null);
|
|
97
|
+
// else if (type instanceof DataTypes.TEXT)
|
|
98
|
+
// return new StringSchema(required, null, null);
|
|
99
|
+
else if (option.type === Date)
|
|
100
|
+
return new DateTimeSchema(required);
|
|
101
|
+
// else if (type instanceof DataTypes.DATEONLY)
|
|
102
|
+
// return new DateSchema(required);
|
|
103
|
+
// else if (type instanceof DataTypes.TIME)
|
|
104
|
+
// return new TimeSchema(required);
|
|
105
|
+
// else if (type instanceof DataTypes.DURATION)
|
|
106
|
+
// return new DurationSchema(required);
|
|
107
|
+
// else if (this.isEnum(type))
|
|
108
|
+
// {
|
|
109
|
+
// let name = NamingConvention.lower_case_underscore.convert(this.model.modelName + "_" + element.field, NamingConvention.Pascal_Case);
|
|
110
|
+
// return new EnumSchema(name, required, type.options.values);
|
|
111
|
+
// }
|
|
112
|
+
// else if (type instanceof DataTypes.JSON)
|
|
113
|
+
// return new AnySchema(required);
|
|
114
|
+
else if (option.type === Object)
|
|
115
|
+
{
|
|
116
|
+
let tags_type = option.tags?.type;
|
|
117
|
+
if (tags_type)
|
|
118
|
+
{
|
|
119
|
+
if (tags_type === "BaseTypeSchema")
|
|
120
|
+
return new TypeSchema(required);
|
|
121
|
+
if (tags_type === "BaseVariableSchema")
|
|
122
|
+
return new VariableSchema(required);
|
|
123
|
+
if (tags_type === "BaseTypeSchema[]")
|
|
124
|
+
return new ArraySchema(required, [new TypeSchema(required)]);
|
|
125
|
+
if (tags_type === "BaseVariableSchema[]")
|
|
126
|
+
return new ArraySchema(required, [new VariableSchema(required)]);
|
|
127
|
+
}
|
|
128
|
+
return new AnySchema(required);
|
|
129
|
+
}
|
|
130
|
+
throw new Error("Unsupported datatype for schema: " + option);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
static formatDate(date: Date): string
|
|
134
|
+
{
|
|
135
|
+
return date.toISOString().replace('T', ' ').substring(0, 19); // "YYYY-MM-DD HH:mm:ss"
|
|
136
|
+
}
|
|
137
|
+
async _getOrNull<M>(option: FindOptions): Promise<M | null>
|
|
138
|
+
{
|
|
139
|
+
let query = this.database.getQuery(this.name, option);
|
|
140
|
+
let ans = await this.database.select<M>(query);
|
|
141
|
+
if (ans.length > 0)
|
|
142
|
+
return ans[0];
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
async _get<M>(options: FindOptions): Promise<M>
|
|
146
|
+
{
|
|
147
|
+
let value = await this._getOrNull<M>(options);
|
|
148
|
+
if (value != null)
|
|
149
|
+
return value;
|
|
150
|
+
throw this.getNotFoundError(options.where);
|
|
151
|
+
}
|
|
152
|
+
async _list<M>(where: string[], pagination: { dont: true } | { page: number, size: number }, sorts: { dont: true } | SortItem[], options: FindOptions): Promise<{ rows: M[], count: number }>
|
|
153
|
+
{
|
|
154
|
+
let order: string[] = [];
|
|
155
|
+
if (!('dont' in pagination))
|
|
156
|
+
{
|
|
157
|
+
let p = this.database.paginate(pagination.page, pagination.size);
|
|
158
|
+
options.offset = p.offset;
|
|
159
|
+
options.limit = p.limit;
|
|
160
|
+
}
|
|
161
|
+
if (!('dont' in sorts))
|
|
162
|
+
order = this.database.getSortOptions(sorts);
|
|
163
|
+
|
|
164
|
+
if (!options.where)
|
|
165
|
+
options.where = [];
|
|
166
|
+
options.where.push(...where);
|
|
167
|
+
if (!options.order)
|
|
168
|
+
options.order = [];
|
|
169
|
+
options.order.push(...order);
|
|
170
|
+
|
|
171
|
+
let query_rows = this.database.getQuery(this.name, options);
|
|
172
|
+
let query_count = this.database.getQuery(this.name, { columns: ["Count(*) as total"], where: options.where });
|
|
173
|
+
let res_rows = await this.database.select<M>(query_rows);
|
|
174
|
+
let res_count = await this.database.select<{ total: number }>(query_count);
|
|
175
|
+
return { rows: res_rows, count: res_count[0].total };
|
|
176
|
+
}
|
|
177
177
|
}
|
|
@@ -1,108 +1,108 @@
|
|
|
1
|
-
import { BaseFilterItemBuilder_JoinTable, BaseFilterItemBuilderDatabase } from "namirasoft-node";
|
|
2
|
-
import { BaseClickhouseDatabase } from "./BaseClickhouseDatabase";
|
|
3
|
-
|
|
4
|
-
export class BaseFilterItemBuilderClickhouse extends BaseFilterItemBuilderDatabase<string[]>
|
|
5
|
-
{
|
|
6
|
-
override
|
|
7
|
-
{
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
return { condition: [condition] };
|
|
24
|
-
}
|
|
25
|
-
override
|
|
26
|
-
{
|
|
27
|
-
const escapedValue = value.replace(/'/g, `\\'`);
|
|
28
|
-
|
|
29
|
-
const condition = `${not ? 'NOT ' : ''}
|
|
30
|
-
|
|
31
|
-
return { condition: [condition] };
|
|
32
|
-
}
|
|
33
|
-
override
|
|
34
|
-
{
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
condition = `NOT
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
if (!not)
|
|
58
|
-
return { condition: [condition] };
|
|
59
|
-
return { condition: [condition], independant: { not } };
|
|
60
|
-
}
|
|
61
|
-
override
|
|
62
|
-
{
|
|
63
|
-
const
|
|
64
|
-
const condition = `${not ? 'NOT ' : ''}(${
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
return
|
|
107
|
-
}
|
|
1
|
+
import { BaseFilterItemBuilder_JoinTable, BaseFilterItemBuilderDatabase } from "namirasoft-node";
|
|
2
|
+
import { BaseClickhouseDatabase } from "./BaseClickhouseDatabase";
|
|
3
|
+
|
|
4
|
+
export class BaseFilterItemBuilderClickhouse extends BaseFilterItemBuilderDatabase<string[]>
|
|
5
|
+
{
|
|
6
|
+
override escapeValue(value: any): string
|
|
7
|
+
{
|
|
8
|
+
if (typeof value === 'string')
|
|
9
|
+
return `'${value.replace(/'/g, `\\'`)}'`;
|
|
10
|
+
if (value instanceof Date)
|
|
11
|
+
return `'${value.toISOString()}'`;
|
|
12
|
+
return value.toString();
|
|
13
|
+
}
|
|
14
|
+
override async getAdvancedSearchConditions(columns: string[], search: string)
|
|
15
|
+
{
|
|
16
|
+
let database = this.database as BaseClickhouseDatabase;
|
|
17
|
+
return [database.getAdvancedSearchConditions(columns, search)];
|
|
18
|
+
}
|
|
19
|
+
override getIn(column_name: string, not: boolean, values: string[])
|
|
20
|
+
{
|
|
21
|
+
const escapedValues = values.map(v => `'${v.replace(/'/g, `\\'`)}'`).join(', ');
|
|
22
|
+
const condition = `${column_name} ${not ? 'NOT IN' : 'IN'} (${escapedValues})`;
|
|
23
|
+
return { condition: [condition] };
|
|
24
|
+
}
|
|
25
|
+
override getLike(column_name: string, not: boolean, value: string)
|
|
26
|
+
{
|
|
27
|
+
const escapedValue = value.replace(/'/g, `\\'`);
|
|
28
|
+
|
|
29
|
+
const condition = `${not ? 'NOT ' : ''}(${column_name} LIKE '%${escapedValue}%')`;
|
|
30
|
+
|
|
31
|
+
return { condition: [condition] };
|
|
32
|
+
}
|
|
33
|
+
override getRegex(column_name: string, not: boolean, value: string)
|
|
34
|
+
{
|
|
35
|
+
const escapedValue = value.replace(/'/g, `\\'`);
|
|
36
|
+
|
|
37
|
+
const condition = `${not ? 'NOT ' : ''}match(${column_name}, '${escapedValue}')`;
|
|
38
|
+
|
|
39
|
+
return { condition: [condition] };
|
|
40
|
+
}
|
|
41
|
+
override getEmpty(column_name: string, not: boolean)
|
|
42
|
+
{
|
|
43
|
+
let condition = '';
|
|
44
|
+
|
|
45
|
+
if (!not)
|
|
46
|
+
condition = `(${column_name} IS NULL OR ${column_name} = '')`;
|
|
47
|
+
else
|
|
48
|
+
condition = `(${column_name} IS NOT NULL AND ${column_name} != '')`;
|
|
49
|
+
|
|
50
|
+
return { condition: [condition] };
|
|
51
|
+
}
|
|
52
|
+
override getExists(column_name: string, not: boolean)
|
|
53
|
+
{
|
|
54
|
+
let condition = `(${column_name} IS NOT NULL AND ${column_name} != '')`;
|
|
55
|
+
if (not)
|
|
56
|
+
condition = `NOT (${condition})`;
|
|
57
|
+
if (!not)
|
|
58
|
+
return { condition: [condition] };
|
|
59
|
+
return { condition: [condition], independant: { not } };
|
|
60
|
+
}
|
|
61
|
+
override getIncludes(column_name: string, not: boolean, values: string[])
|
|
62
|
+
{
|
|
63
|
+
const escapedValues = values.map(v => `'${v.replace(/'/g, `\\'`)}'`).join(', ');
|
|
64
|
+
const condition = `${column_name} ${not ? 'NOT IN' : 'IN'} (${escapedValues})`;
|
|
65
|
+
if (!not)
|
|
66
|
+
return { condition: [condition] };
|
|
67
|
+
return { condition: [condition], independant: { not } };
|
|
68
|
+
}
|
|
69
|
+
override getStartsWith(column_name: string, not: boolean, value: string)
|
|
70
|
+
{
|
|
71
|
+
const escapedValue = value.replace(/'/g, `\\'`);
|
|
72
|
+
const condition = `${not ? 'NOT ' : ''}(${column_name} LIKE '${escapedValue}%')`;
|
|
73
|
+
return { condition: [condition] };
|
|
74
|
+
}
|
|
75
|
+
override getEndsWith(column_name: string, not: boolean, value: string)
|
|
76
|
+
{
|
|
77
|
+
const escapedValue = value.replace(/'/g, `\\'`);
|
|
78
|
+
const condition = `${not ? 'NOT ' : ''}(${column_name} LIKE '%${escapedValue}')`;
|
|
79
|
+
return { condition: [condition] };
|
|
80
|
+
}
|
|
81
|
+
override getLT(column_name: string, value: any)
|
|
82
|
+
{
|
|
83
|
+
const condition = `${column_name} < ${this.escapeValue(value)}`;
|
|
84
|
+
return { condition: [condition] };
|
|
85
|
+
}
|
|
86
|
+
override getLTE(column_name: string, value: any)
|
|
87
|
+
{
|
|
88
|
+
const condition = `${column_name} <= ${this.escapeValue(value)}`;
|
|
89
|
+
return { condition: [condition] };
|
|
90
|
+
}
|
|
91
|
+
override getGT(column_name: string, value: any)
|
|
92
|
+
{
|
|
93
|
+
const condition = `${column_name} > ${this.escapeValue(value)}`;
|
|
94
|
+
return { condition: [condition] };
|
|
95
|
+
}
|
|
96
|
+
override getGTE(column_name: string, value: any)
|
|
97
|
+
{
|
|
98
|
+
const condition = `${column_name} >= ${this.escapeValue(value)}`;
|
|
99
|
+
return { condition: [condition] };
|
|
100
|
+
}
|
|
101
|
+
override async getInSelect(table_name: string, not: boolean, join: BaseFilterItemBuilder_JoinTable<string[]>, conditions: string[][])
|
|
102
|
+
{
|
|
103
|
+
const whereClause = conditions.length > 0 ? `WHERE ${conditions.flat().join(' AND ')}` : '';
|
|
104
|
+
const select = `SELECT ${join.secondary.column} FROM ${table_name} ${whereClause}`;
|
|
105
|
+
const condition = `${join.main.column} ${not ? 'NOT IN' : 'IN'} (${select})`;
|
|
106
|
+
return [condition];
|
|
107
|
+
}
|
|
108
108
|
}
|
package/src/FindOptions.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
export interface FindOptions
|
|
2
|
-
{
|
|
3
|
-
columns?: string[];
|
|
4
|
-
where?: string[];
|
|
5
|
-
order?: string[];
|
|
6
|
-
limit?: number;
|
|
7
|
-
offset?: number;
|
|
1
|
+
export interface FindOptions
|
|
2
|
+
{
|
|
3
|
+
columns?: string[];
|
|
4
|
+
where?: string[];
|
|
5
|
+
order?: string[];
|
|
6
|
+
limit?: number;
|
|
7
|
+
offset?: number;
|
|
8
8
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export * from "./BaseClickhouseDatabase";
|
|
2
|
-
export * from "./BaseClickhouseModelSchema";
|
|
3
|
-
export * from "./BaseClickhouseModelSchemaColumnOptions";
|
|
4
|
-
export * from "./BaseClickhouseTable";
|
|
1
|
+
export * from "./BaseClickhouseDatabase";
|
|
2
|
+
export * from "./BaseClickhouseModelSchema";
|
|
3
|
+
export * from "./BaseClickhouseModelSchemaColumnOptions";
|
|
4
|
+
export * from "./BaseClickhouseTable";
|
|
5
5
|
export * from "./BaseFilterItemBuilderClickhouse"
|