pg-mvc-service 1.0.0
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 -0
- package/dist/PoolManager.js +57 -0
- package/dist/Service.js +257 -0
- package/dist/clients/AwsS3Client.js +249 -0
- package/dist/clients/Base64Client.js +153 -0
- package/dist/clients/EncryptClient.js +85 -0
- package/dist/clients/StringClient.js +13 -0
- package/dist/documents/Swagger.js +94 -0
- package/dist/exceptions/Exception.js +53 -0
- package/dist/index.js +16 -0
- package/dist/models/MigrateDatabase.js +138 -0
- package/dist/models/MigrateRollback.js +146 -0
- package/dist/models/MigrateTable.js +51 -0
- package/dist/models/SqlUtils/SelectExpression.js +92 -0
- package/dist/models/SqlUtils/ValidateValueUtil.js +250 -0
- package/dist/models/SqlUtils/WhereExpression.js +256 -0
- package/dist/models/TableDoc.js +353 -0
- package/dist/models/TableModel.js +636 -0
- package/dist/models/Type.js +2 -0
- package/dist/models/Utils/DateTimeUtil.js +134 -0
- package/dist/models/Utils/NumberUtil.js +28 -0
- package/dist/models/Utils/StringUtil.js +31 -0
- package/dist/models/ValidateClient.js +164 -0
- package/dist/models/index.js +14 -0
- package/dist/reqestResponse/ReqResType.js +196 -0
- package/dist/reqestResponse/RequestType.js +742 -0
- package/dist/reqestResponse/ResponseType.js +380 -0
- package/index.d.ts +306 -0
- package/package.json +36 -0
- package/src/PoolManager.ts +48 -0
- package/src/Service.ts +251 -0
- package/src/clients/AwsS3Client.ts +229 -0
- package/src/clients/Base64Client.ts +155 -0
- package/src/clients/EncryptClient.ts +100 -0
- package/src/clients/StringClient.ts +14 -0
- package/src/documents/Swagger.ts +111 -0
- package/src/exceptions/Exception.ts +54 -0
- package/src/index.ts +7 -0
- package/src/models/MigrateDatabase.ts +135 -0
- package/src/models/MigrateRollback.ts +151 -0
- package/src/models/MigrateTable.ts +56 -0
- package/src/models/SqlUtils/SelectExpression.ts +97 -0
- package/src/models/SqlUtils/ValidateValueUtil.ts +270 -0
- package/src/models/SqlUtils/WhereExpression.ts +286 -0
- package/src/models/TableDoc.ts +360 -0
- package/src/models/TableModel.ts +713 -0
- package/src/models/Type.ts +59 -0
- package/src/models/Utils/DateTimeUtil.ts +146 -0
- package/src/models/Utils/NumberUtil.ts +23 -0
- package/src/models/Utils/StringUtil.ts +33 -0
- package/src/models/ValidateClient.ts +182 -0
- package/src/models/index.ts +7 -0
- package/src/reqestResponse/ReqResType.ts +242 -0
- package/src/reqestResponse/RequestType.ts +851 -0
- package/src/reqestResponse/ResponseType.ts +418 -0
- package/tsconfig.json +14 -0
|
@@ -0,0 +1,713 @@
|
|
|
1
|
+
import { Pool, PoolClient } from 'pg';
|
|
2
|
+
import { TAggregateFuncType, TColumn, TColumnArrayType, TColumnAttribute, TColumnDetail, TColumnInfo, TColumnType, TKeyFormat, TNestedCondition, TOperator, TOption, TQuery, TSelectExpression, TSortKeyword, TSqlValue } from "./Type";
|
|
3
|
+
import ValidateValueUtil from './SqlUtils/ValidateValueUtil';
|
|
4
|
+
import SelectExpression from './SqlUtils/SelectExpression';
|
|
5
|
+
import WhereExpression from './SqlUtils/WhereExpression';
|
|
6
|
+
import ValidateClient from './ValidateClient';
|
|
7
|
+
|
|
8
|
+
export class TableModel {
|
|
9
|
+
|
|
10
|
+
protected readonly dbName: string = "default";
|
|
11
|
+
get DbName(): string { return this.dbName; }
|
|
12
|
+
protected readonly tableName: string = "";
|
|
13
|
+
get TableName(): string {
|
|
14
|
+
if (this.tableName === "") {
|
|
15
|
+
throw new Error("Please set the tableName for TableModel.");
|
|
16
|
+
}
|
|
17
|
+
return this.tableName;
|
|
18
|
+
}
|
|
19
|
+
protected readonly tableDescription: string = "";
|
|
20
|
+
get TableDescription(): string { return this.tableDescription; }
|
|
21
|
+
protected readonly comment: string = "";
|
|
22
|
+
get Comment(): string { return this.comment; }
|
|
23
|
+
protected readonly columns: { [key: string]: TColumn } = {};
|
|
24
|
+
get Columns(): { [key: string]: TColumn } {
|
|
25
|
+
if (Object.keys(this.columns).length === 0) {
|
|
26
|
+
throw new Error("Please set the columns for TableModel.");
|
|
27
|
+
}
|
|
28
|
+
return this.columns;
|
|
29
|
+
}
|
|
30
|
+
public getColumn(key: string): TColumnDetail {
|
|
31
|
+
if (key in this.Columns === false) {
|
|
32
|
+
throw new Error(`${this.TableName} does not contain ${key}.`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
...this.Columns[key],
|
|
37
|
+
columnName: key,
|
|
38
|
+
tableName: this.TableName,
|
|
39
|
+
expression: `"${this.TableAlias}".${key}`
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
protected readonly references: Array<{table: string, columns: Array<{target: string, ref: string}>}> = [];
|
|
43
|
+
get References(): Array<{table: string, columns: Array<{target: string, ref: string}>}> { return this.references; }
|
|
44
|
+
public GetReferences(columnName: string): Array<{table: string, columns: Array<{target: string, ref: string}>}> {
|
|
45
|
+
const _ = this.getColumn(columnName); // 存在チェック用
|
|
46
|
+
const references: Array<{table: string, columns: Array<{target: string, ref: string}>}> = [];
|
|
47
|
+
for (const ref of this.References) {
|
|
48
|
+
if (ref.columns.filter(col => col.target === columnName).length > 0) {
|
|
49
|
+
references.push(ref);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return references;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
protected readonly tableAlias?: string;
|
|
57
|
+
get TableAlias(): string {
|
|
58
|
+
return this.tableAlias === undefined ? this.TableName : this.tableAlias;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
public IsOutputLog: boolean = false;
|
|
62
|
+
public SortKeyword: TSortKeyword = 'asc';
|
|
63
|
+
public Offset?: number;
|
|
64
|
+
public Limit?: number;
|
|
65
|
+
public PageCount: number = 10;
|
|
66
|
+
|
|
67
|
+
private selectExpressions: Array<string> = [];
|
|
68
|
+
private joinConditions: Array<{
|
|
69
|
+
type: 'inner' | 'left',
|
|
70
|
+
model: TableModel,
|
|
71
|
+
conditions: Array<TNestedCondition>
|
|
72
|
+
}> = [];
|
|
73
|
+
private whereExpressions: Array<string> = [];
|
|
74
|
+
private groupExpression: Array<string> = [];
|
|
75
|
+
private sortExpression: Array<string> = [];
|
|
76
|
+
private vars: Array<any> = [];
|
|
77
|
+
|
|
78
|
+
private get createSqlFromJoinWhere(): string {
|
|
79
|
+
let sql = ` FROM ${this.TableName} as "${this.TableAlias}"`;
|
|
80
|
+
|
|
81
|
+
for (const join of this.joinConditions) {
|
|
82
|
+
sql += join.type === 'left' ? ' LEFT OUTER JOIN' : ' INNER JOIN';
|
|
83
|
+
sql += ` ${join.model.TableName} as "${join.model.TableAlias}" ON `;
|
|
84
|
+
const query = WhereExpression.createCondition(join.conditions, this, this.vars.length + 1);
|
|
85
|
+
sql += query.sql;
|
|
86
|
+
if (query.vars !== undefined) {
|
|
87
|
+
this.vars = [...this.vars, ...query.vars]
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (this.whereExpressions.length > 0) {
|
|
92
|
+
sql += " WHERE " + this.whereExpressions.join(" AND ");
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (this.groupExpression.length > 0) {
|
|
96
|
+
sql += ` GROUP BY ${this.groupExpression.join(',')}`;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return sql;
|
|
100
|
+
}
|
|
101
|
+
private get createSqlFromJoinWhereSortLimit(): string {
|
|
102
|
+
let sql = this.createSqlFromJoinWhere;
|
|
103
|
+
|
|
104
|
+
if (this.sortExpression.length > 0) {
|
|
105
|
+
sql += ` ORDER BY ${this.sortExpression.join(",")}`;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (this.Limit !== undefined) {
|
|
109
|
+
sql += ` LIMIT ${this.Limit}`;
|
|
110
|
+
}
|
|
111
|
+
if (this.Offset !== undefined) {
|
|
112
|
+
sql += ` OFFSET ${this.Offset}`;
|
|
113
|
+
}
|
|
114
|
+
return sql;
|
|
115
|
+
}
|
|
116
|
+
set OffsetPage(value: number) {
|
|
117
|
+
if (value > 0) {
|
|
118
|
+
this.Limit = this.PageCount;
|
|
119
|
+
this.Offset = (value - 1) * this.PageCount;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
private client: PoolClient | Pool;
|
|
124
|
+
constructor(client: Pool);
|
|
125
|
+
constructor(client: Pool, tableAlias: string);
|
|
126
|
+
constructor(client: PoolClient);
|
|
127
|
+
constructor(client: PoolClient, tableAlias: string);
|
|
128
|
+
constructor(client: Pool | PoolClient, tableAlias?: string) {
|
|
129
|
+
this.client = client;
|
|
130
|
+
if (tableAlias !== undefined && tableAlias.trim() !== '') {
|
|
131
|
+
this.tableAlias = tableAlias;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
public findId<T = {[key: string]: any}>(id: any, selectColumns: Array<string> | "*" | null, selectExpressions: Array<TSelectExpression> | null, keyFormat: TKeyFormat): Promise<T | null>;
|
|
136
|
+
public findId<T = {[key: string]: any}>(id: any, selectColumns: Array<string> | "*" | null, selectExpressions: Array<TSelectExpression> | null): Promise<T | null>;
|
|
137
|
+
public findId<T = {[key: string]: any}>(id: any, selectColumns: Array<string> | "*" | null): Promise<T | null>;
|
|
138
|
+
public findId<T = {[key: string]: any}>(id: any): Promise<T | null>;
|
|
139
|
+
public async findId<T = {[key: string]: any}>(id: any, selectColumns: Array<string> | "*" | null = "*", selectExpressions: Array<TSelectExpression> | null = null, keyFormat: TKeyFormat = 'snake'): Promise<T | null> {
|
|
140
|
+
ValidateValueUtil.validateId(this.Columns, id);
|
|
141
|
+
|
|
142
|
+
let selects: Array<string> = [];
|
|
143
|
+
if (selectColumns == "*") {
|
|
144
|
+
for (const key of Object.keys(this.Columns)) {
|
|
145
|
+
selects.push(SelectExpression.create({model: this, name: key}, null, null, keyFormat));
|
|
146
|
+
}
|
|
147
|
+
} else if (selectColumns != null) {
|
|
148
|
+
for (const key of selectColumns) {
|
|
149
|
+
selects.push(SelectExpression.create({model: this, name: key}, null, null, keyFormat));
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (selectExpressions != null) {
|
|
154
|
+
for (const expression of selectExpressions) {
|
|
155
|
+
selects.push(`${expression.expression} as "${expression.alias}"`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const sql = `SELECT ${selects.join(',')} FROM ${this.TableName} WHERE id = $1`;
|
|
160
|
+
let datas = await this.executeQuery(sql, [id]);
|
|
161
|
+
|
|
162
|
+
return datas.rowCount == 0 ? null : datas.rows[0] as T;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
public find<T = {[key: string]: any}>(pk: {[key: string]: any}, selectColumns: Array<string> | "*" | null, selectExpressions: Array<TSelectExpression> | null, keyFormat: TKeyFormat): Promise<T | null>;
|
|
166
|
+
public find<T = {[key: string]: any}>(pk: {[key: string]: any}, selectColumns: Array<string> | "*" | null, selectExpressions: Array<TSelectExpression> | null): Promise<T | null>;
|
|
167
|
+
public find<T = {[key: string]: any}>(pk: {[key: string]: any}, selectColumns: Array<string> | "*" | null): Promise<T | null>;
|
|
168
|
+
public find<T = {[key: string]: any}>(pk: {[key: string]: any}): Promise<T | null>;
|
|
169
|
+
public async find<T = {[key: string]: any}>(pk: {[key: string]: any}, selectColumns: Array<string> | "*" | null = "*", selectExpressions: Array<TSelectExpression> | null = null, keyFormat: TKeyFormat = 'snake'): Promise<T | null> {
|
|
170
|
+
|
|
171
|
+
let selects: Array<string> = [];
|
|
172
|
+
if (selectColumns == "*") {
|
|
173
|
+
for (const key of Object.keys(this.Columns)) {
|
|
174
|
+
selects.push(SelectExpression.create({model: this, name: key}, null, null, keyFormat));
|
|
175
|
+
}
|
|
176
|
+
} else if (selectColumns != null) {
|
|
177
|
+
for (const key of selectColumns) {
|
|
178
|
+
selects.push(SelectExpression.create({model: this, name: key}, null, null, keyFormat));
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (selectExpressions != null) {
|
|
183
|
+
for (const expression of selectExpressions) {
|
|
184
|
+
selects.push(`${expression.expression} as "${expression.alias}"`);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const conditions = [];
|
|
189
|
+
const vars = [];
|
|
190
|
+
for (const [keyColumn, column] of Object.entries(this.Columns)) {
|
|
191
|
+
if (column.attribute !== 'primary') {
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (pk[keyColumn] === undefined || pk[keyColumn] === null) {
|
|
196
|
+
throw new Error(`No value is set for the primary key "${this.TableName}".${keyColumn}. Please set it in the first argument.`);
|
|
197
|
+
}
|
|
198
|
+
ValidateValueUtil.validateValue(column, pk[keyColumn]);
|
|
199
|
+
vars.push(pk[keyColumn]);
|
|
200
|
+
conditions.push(`${keyColumn} = $${vars.length}`);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const sql = `SELECT ${selects.join(',')} FROM ${this.TableName} WHERE ${conditions.join(' AND ')}`;
|
|
204
|
+
let datas = await this.executeQuery(sql, vars);
|
|
205
|
+
|
|
206
|
+
return datas.rowCount == 0 ? null : datas.rows[0] as T;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
public select(): void;
|
|
210
|
+
public select(columls: Array<string | {name: string, alias?: string, func?: TAggregateFuncType}> | '*'): void;
|
|
211
|
+
public select(columls: Array<string | {name: string, alias?: string, func?: TAggregateFuncType}> | '*', model: TableModel): void;
|
|
212
|
+
public select(columls: Array<string | {name: string, alias?: string, func?: TAggregateFuncType}> | '*', keyFormat: TKeyFormat): void;
|
|
213
|
+
public select(columls: Array<string | {name: string, alias?: string, func?: TAggregateFuncType}> | '*', model: TableModel, keyFormat: TKeyFormat): void;
|
|
214
|
+
public select(expression: string, alias: string): void;
|
|
215
|
+
public select(param1: Array<string | {name: string, alias?: string, func?: TAggregateFuncType}> | "*" | string = "*", param2?: TableModel | string | TKeyFormat, param3?: TKeyFormat) {
|
|
216
|
+
if (param1 === "*") {
|
|
217
|
+
let model: TableModel = this;
|
|
218
|
+
let keyFormat: TKeyFormat = 'snake';
|
|
219
|
+
if (param2 instanceof TableModel) {
|
|
220
|
+
model = param2;
|
|
221
|
+
if (param3 === 'snake' || param3 === 'lowerCamel') {
|
|
222
|
+
keyFormat = param3;
|
|
223
|
+
}
|
|
224
|
+
} else if (param2 === 'snake' || param2 === 'lowerCamel') {
|
|
225
|
+
keyFormat = param2;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
for (const key of Object.keys(model.Columns)) {
|
|
229
|
+
this.selectExpressions.push(SelectExpression.create({model: model, name: key}, null, null, keyFormat));
|
|
230
|
+
}
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (Array.isArray(param1)) {
|
|
235
|
+
let model: TableModel = this;
|
|
236
|
+
let keyFormat: TKeyFormat = 'snake';
|
|
237
|
+
if (param2 instanceof TableModel) {
|
|
238
|
+
model = param2;
|
|
239
|
+
if (param3 === 'snake' || param3 === 'lowerCamel') {
|
|
240
|
+
keyFormat = param3;
|
|
241
|
+
}
|
|
242
|
+
} else if (param2 === 'snake' || param2 === 'lowerCamel') {
|
|
243
|
+
keyFormat = param2;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
for (const key of param1) {
|
|
247
|
+
if (typeof key === 'string') {
|
|
248
|
+
this.selectExpressions.push(SelectExpression.create({model: model, name: key}, null, null, keyFormat));
|
|
249
|
+
} else {
|
|
250
|
+
this.selectExpressions.push(SelectExpression.create({model: model, name: key.name}, key.func ?? null, key.alias ?? null, keyFormat));
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (typeof param1 === 'string') {
|
|
257
|
+
const expression = param1;
|
|
258
|
+
if (typeof param2 !== 'string' || param2.trim() === '') {
|
|
259
|
+
throw new Error('If the first argument is a string, the second argument must be a non-empty string.');
|
|
260
|
+
}
|
|
261
|
+
const alias = param2;
|
|
262
|
+
this.selectExpressions.push(`(${expression}) as "${alias}"`);
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* 指定された条件に基づいてテーブルを結合します。
|
|
269
|
+
* @param joinType 結合の種類を指定します
|
|
270
|
+
* @param joinBaseModel 結合する対象のBaseModelインスタンスを指定します。
|
|
271
|
+
* @param conditions 結合条件を指定します。条件はオブジェクトまたは文字列で指定できます。
|
|
272
|
+
*/
|
|
273
|
+
public join(joinType: 'left' | 'inner', joinModel: TableModel, conditions: Array<TNestedCondition>): void {
|
|
274
|
+
this.joinConditions.push({type: joinType, model: joinModel, conditions: conditions});
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
public where(expression: string): void;
|
|
278
|
+
public where(conditions: Array<TNestedCondition>): void;
|
|
279
|
+
public where(left: string, operator: TOperator, right: TSqlValue | Array<TSqlValue> | TColumnInfo | null): void;
|
|
280
|
+
public where(left: TColumnInfo, operator: TOperator, right: TSqlValue | Array<TSqlValue> | TColumnInfo | null): void;
|
|
281
|
+
public where(left: string | TColumnInfo | Array<TNestedCondition>, operator?: TOperator, right?: TSqlValue | Array<TSqlValue> | TColumnInfo | null): void {
|
|
282
|
+
if (typeof left === 'string') {
|
|
283
|
+
if (operator === undefined || right === undefined) {
|
|
284
|
+
this.whereExpressions.push(left);
|
|
285
|
+
} else {
|
|
286
|
+
const query = WhereExpression.create({model: this, name: left}, operator, right, this.vars.length + 1);
|
|
287
|
+
this.whereExpressions.push(query.sql);
|
|
288
|
+
if (query.vars !== undefined) {
|
|
289
|
+
this.vars = [...this.vars, ...query.vars];
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if ('model' in left && 'name' in left) {
|
|
296
|
+
if (operator === undefined || right === undefined) {
|
|
297
|
+
throw new Error(`If left is TColumnInfo, please set operator and right.`);
|
|
298
|
+
} else {
|
|
299
|
+
const query = WhereExpression.create(left, operator, right, this.vars.length + 1);
|
|
300
|
+
this.whereExpressions.push(query.sql);
|
|
301
|
+
if (query.vars !== undefined) {
|
|
302
|
+
this.vars = [...this.vars, ...query.vars];
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (Array.isArray(left)) {
|
|
309
|
+
const query = WhereExpression.createCondition(left, this, this.vars.length + 1);
|
|
310
|
+
this.whereExpressions.push(query.sql);
|
|
311
|
+
if (query.vars !== undefined) {
|
|
312
|
+
this.vars = [...this.vars, ...query.vars];
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
public groupBy(column: string | TColumnInfo): void {
|
|
318
|
+
if (typeof column === 'string') {
|
|
319
|
+
column = {model: this, name: column};
|
|
320
|
+
}
|
|
321
|
+
this.groupExpression.push(column.model.getColumn(column.name).expression);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
public orderBy(column: string | TColumnInfo, sortKeyword: TSortKeyword) {
|
|
325
|
+
if (typeof column === 'string') {
|
|
326
|
+
column = { model: this, name: column };
|
|
327
|
+
}
|
|
328
|
+
this.sortExpression.push(`${column.model.getColumn(column.name).expression} ${sortKeyword}`);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
public orderByList(column: string | TColumnInfo, list: Array<string | number | boolean | null>, sortKeyword: TSortKeyword): void {
|
|
332
|
+
if (list.length === 0) {
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
if (typeof(column) == 'string') {
|
|
337
|
+
column = {model: this, name: column};;
|
|
338
|
+
}
|
|
339
|
+
const columnInfo = column.model.getColumn(column.name);
|
|
340
|
+
|
|
341
|
+
const orderConditions: Array<string> = [];
|
|
342
|
+
for (let i = 0;i < list.length;i++) {
|
|
343
|
+
const value = list[i];
|
|
344
|
+
if (value === null) {
|
|
345
|
+
if (columnInfo.attribute === 'nullable') {
|
|
346
|
+
orderConditions.push(`WHEN ${columnInfo.expression} is null THEN ${i}`);
|
|
347
|
+
continue;
|
|
348
|
+
}
|
|
349
|
+
throw new Error(`${this.TableName}.${columnInfo.columnName} is a non-nullable column.`);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
ValidateValueUtil.validateValue(columnInfo, value);
|
|
353
|
+
switch (columnInfo.type) {
|
|
354
|
+
case 'number':
|
|
355
|
+
orderConditions.push(`WHEN ${columnInfo.expression} = ${value} THEN ${i}`);
|
|
356
|
+
break;
|
|
357
|
+
case 'uuid':
|
|
358
|
+
case 'string':
|
|
359
|
+
orderConditions.push(`WHEN ${columnInfo.expression} = '${value}' THEN ${i}`);
|
|
360
|
+
break;
|
|
361
|
+
case 'bool':
|
|
362
|
+
const boolValue = value === true || value === 'true' || value === 1;
|
|
363
|
+
orderConditions.push(`WHEN ${columnInfo.expression} = ${boolValue} THEN ${i}`);
|
|
364
|
+
break;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
if (orderConditions.length === 0) {
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
this.sortExpression.push(`CASE ${orderConditions.join(' ')} ELSE ${list.length} END ${sortKeyword}`);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
public orderBySentence(query: string, sortKeyword: TSortKeyword): void {
|
|
376
|
+
this.sortExpression.push(`${query} ${sortKeyword}`);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
public async executeSelect<T = {[key: string]: any}>(): Promise<Array<T>> {
|
|
380
|
+
if (this.selectExpressions.length === 0) {
|
|
381
|
+
this.select();
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
let sql = ` SELECT ${this.selectExpressions.join(",")} ${this.createSqlFromJoinWhereSortLimit}`;
|
|
385
|
+
let data = await this.executeQuery(sql, this.vars);
|
|
386
|
+
return data.rows as Array<T>;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
public async executeSelectWithCount<T = any>(): Promise<{ datas: Array<T>, count: number, lastPage: number}> {
|
|
390
|
+
if (this.selectExpressions.length == 0) {
|
|
391
|
+
this.select();
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
let sql = ` SELECT ${this.selectExpressions.join(",")} ${this.createSqlFromJoinWhereSortLimit}`;
|
|
395
|
+
let countSql = ` SELECT COUNT(*) as "count" ${this.createSqlFromJoinWhere}`;
|
|
396
|
+
let tempVars = [...this.vars];
|
|
397
|
+
const data = await this.executeQuery(sql, tempVars);
|
|
398
|
+
|
|
399
|
+
const countData = await this.executeQuery(countSql, tempVars);
|
|
400
|
+
return { datas: data.rows as Array<T>, count: Number(countData.rows[0].count), lastPage: Math.ceil(Number(countData.rows[0].count) / this.PageCount)};
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
protected readonly errorMessages: Record<TColumnType | TColumnArrayType | 'length' | 'null' | 'notInput' | 'fk' | 'idNotExist', string> = {
|
|
404
|
+
'string': '{name} should be entered as a string or number type.',
|
|
405
|
+
'string[]': '{name} should be entered as an array of string or number types.',
|
|
406
|
+
'uuid': '{name} should be entered as a UUID.',
|
|
407
|
+
'uuid[]': '{name} should be entered as an array of UUIDs.',
|
|
408
|
+
'number': '{name} should be entered as a number.',
|
|
409
|
+
'number[]': '{name} should be entered as an array of numbers.',
|
|
410
|
+
'bool': '{name} should be entered as a bool type, "true", "false", 0, or 1.',
|
|
411
|
+
'bool[]': '{name} should be entered as an array of bool types, "true", "false", 0, or 1.',
|
|
412
|
+
'date': '{name} should be entered in "YYYY-MM-DD" or "YYYY-MM-DD hh:mi:ss" format or as a Date type.',
|
|
413
|
+
'date[]': '{name} should be entered as an array of dates in "YYYY-MM-DD" or "YYYY-MM-DD hh:mi:ss" format or as Date types.',
|
|
414
|
+
'time': '{name} should be entered in "hh:mi" format or "hh:mi:ss" format.',
|
|
415
|
+
'time[]': '{name} should be entered as an array of times in "hh:mi" format or "hh:mi:ss" format.',
|
|
416
|
+
'timestamp': '{name} should be entered in "YYYY-MM-DD" format, "YYYY-MM-DD hh:mi:ss" format, "YYYY-MM-DDThh:mi:ss" format, or as a Date type.',
|
|
417
|
+
'timestamp[]': '{name} should be entered as an array of timestamps in "YYYY-MM-DD" format, "YYYY-MM-DD hh:mi:ss" format, "YYYY-MM-DDThh:mi:ss" format, or as Date types.',
|
|
418
|
+
'length': '{name} should be entered within {length} characters.',
|
|
419
|
+
'null': '{name} is not allowed to be null.',
|
|
420
|
+
'notInput': 'Please enter {name}.',
|
|
421
|
+
'fk': 'The value of {name} does not exist in the table.',
|
|
422
|
+
'idNotExist': 'The specified ID({id}) does not exist in the table.',
|
|
423
|
+
}
|
|
424
|
+
public throwValidationError(code: string, message: string): never {
|
|
425
|
+
throw new Error(message);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
protected async validateOptions(options: TOption, isInsert: boolean): Promise<void> {
|
|
429
|
+
if (Object.keys(options).length === 0) {
|
|
430
|
+
throw new Error('At least one key-value pair is required in options.');
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
for (const [key, value] of Object.entries(options)) {
|
|
434
|
+
const column = this.getColumn(key);
|
|
435
|
+
if (isInsert === false && column.attribute === 'primary') {
|
|
436
|
+
throw new Error(`${this.TableName}.${key} cannot be modified because it is a primary key.`);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
const name = (column.alias === undefined || column.alias === '') ? key : column.alias;
|
|
440
|
+
if (value === null) {
|
|
441
|
+
if (column.attribute === 'nullable') {
|
|
442
|
+
continue;
|
|
443
|
+
}
|
|
444
|
+
this.throwValidationError("001", this.errorMessages.null.replace('{name}', name));
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
if (ValidateValueUtil.isErrorValue(column.type, value)) {
|
|
448
|
+
this.throwValidationError("002", this.errorMessages[column.type].replace('{name}', name));
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
if (column.type === 'string') {
|
|
452
|
+
if (Number.isInteger(column.length) === false) {
|
|
453
|
+
throw new Error(`For strings, please specify the length of the column.(column: ${column.columnName})`);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
if (value.toString().length > column.length) {
|
|
457
|
+
this.throwValidationError("003", this.errorMessages.length.replace('{name}', name).replace('{length}', column.toString()));
|
|
458
|
+
}
|
|
459
|
+
} else if (column.type === 'string[]') {
|
|
460
|
+
if (Number.isInteger(column.length) === false) {
|
|
461
|
+
throw new Error(`For strings, please specify the length of the column.(column: ${column.columnName})`);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// ValidateValueUtil.isErrorValue(column.type, value)で型チェックしてるのでas []にしている
|
|
465
|
+
for (const v of value as Array<string | number | boolean>) {
|
|
466
|
+
if (v.toString().length > column.length) {
|
|
467
|
+
this.throwValidationError("004", this.errorMessages.length.replace('{name}', name).replace('{length}', column.length.toString()));
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// 外部キー制約チェック
|
|
474
|
+
if (isInsert) {
|
|
475
|
+
for (const ref of this.References) {
|
|
476
|
+
const refValues = ref.columns.map(col => options[col.target]);
|
|
477
|
+
// 全ての値がnullの場合はスキップ
|
|
478
|
+
if (refValues.every(value => value === null || value === undefined)) {
|
|
479
|
+
continue;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// 一部の値がnullの場合はエラー
|
|
483
|
+
if (refValues.some(value => value === null || value === undefined)) {
|
|
484
|
+
const name = ref.columns.map(col => this.getColumn(col.target).alias ?? this.getColumn(col.target).columnName).join(',');
|
|
485
|
+
this.throwValidationError("004", this.errorMessages.fk.replace('{name}', name));
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
let refIndex = 1;
|
|
489
|
+
const sql = `SELECT COUNT(*) as count FROM ${ref.table} WHERE ${ref.columns.map(col => `${col.ref} = $${refIndex++}`)}`;
|
|
490
|
+
const datas = await this.clientQuery(sql, refValues);
|
|
491
|
+
if (datas.rows[0].count == "0") {
|
|
492
|
+
const name = ref.columns.map(col => this.getColumn(col.target).alias ?? this.getColumn(col.target).columnName).join(',');
|
|
493
|
+
this.throwValidationError("004", this.errorMessages.fk.replace('{name}', name));
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
protected async validateInsert(options: TOption) : Promise<void> {
|
|
500
|
+
for (const key in this.Columns) {
|
|
501
|
+
const column = this.getColumn(key);
|
|
502
|
+
const name = (column.alias === undefined || column.alias === '') ? key : column.alias;
|
|
503
|
+
if (options[key] === undefined || options[key] === null) {
|
|
504
|
+
// Null許容されていないカラムにNULLを入れようとしているか?
|
|
505
|
+
if (column.attribute === "primary" || column.attribute === "noDefault") {
|
|
506
|
+
this.throwValidationError("101", this.errorMessages.notInput.replace('{name}', name));
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
protected async validateUpdate(options: TOption) : Promise<void> { }
|
|
513
|
+
protected async validateUpdateId(id: any, options: TOption) : Promise<void> { }
|
|
514
|
+
protected async validateDelete() : Promise<void> { }
|
|
515
|
+
protected async validateDeleteId(id: any) : Promise<void> { }
|
|
516
|
+
|
|
517
|
+
public async executeInsert(options: TOption) : Promise<void> {
|
|
518
|
+
await this.validateOptions(options, true);
|
|
519
|
+
await this.validateInsert(options);
|
|
520
|
+
|
|
521
|
+
const columns: Array<string> = [];
|
|
522
|
+
const vars: Array<any> = [];
|
|
523
|
+
|
|
524
|
+
for (const [key, value] of Object.entries(options)) {
|
|
525
|
+
if (value === undefined) {
|
|
526
|
+
throw new Error(`The insert option ${key} is undefined.`);
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
columns.push(key);
|
|
530
|
+
vars.push(value)
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
const params = vars.map((_, index) => `$${index + 1}`);
|
|
534
|
+
const sql = `INSERT INTO ${this.TableName} (${columns.join(",")}) VALUES (${params.join(",")});`;
|
|
535
|
+
await this.executeQuery(sql, vars);
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
public async executeUpdate(options: TOption) : Promise<number> {
|
|
539
|
+
await this.validateOptions(options, false);
|
|
540
|
+
await this.validateUpdate(options);
|
|
541
|
+
|
|
542
|
+
const updateExpressions: Array<string> = [];
|
|
543
|
+
for (const [key, value] of Object.entries(options)) {
|
|
544
|
+
const column = this.getColumn(key);
|
|
545
|
+
ValidateValueUtil.validateValue(column, value);
|
|
546
|
+
this.vars.push(value);
|
|
547
|
+
updateExpressions.push(`${key} = $${this.vars.length}`)
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
let sql = `UPDATE ${this.TableName} "${this.TableAlias}" SET ${updateExpressions.join(',')} `;
|
|
551
|
+
|
|
552
|
+
if (this.joinConditions.length > 0) {
|
|
553
|
+
const tables: Array<string> = [];
|
|
554
|
+
for (const join of this.joinConditions) {
|
|
555
|
+
tables.push(`${join.model.TableName} as "${join.model.TableAlias}"`);
|
|
556
|
+
|
|
557
|
+
const query = WhereExpression.createCondition(join.conditions, this, this.vars.length);
|
|
558
|
+
this.whereExpressions.push(query.sql);
|
|
559
|
+
if (query.vars !== undefined) {
|
|
560
|
+
this.vars = [...this.vars, ...query.vars]
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
sql += `FROM ${tables.join(',')} `;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
if (this.whereExpressions.length > 0) {
|
|
567
|
+
sql += "WHERE " + this.whereExpressions.join(" AND ");
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
const data = await this.executeQuery(sql, this.vars);
|
|
571
|
+
return data.rowCount;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
public async executeUpdateId(id: any, options: TOption) : Promise<void> {
|
|
575
|
+
ValidateValueUtil.validateId(this.Columns, id);
|
|
576
|
+
await this.validateOptions(options, false);
|
|
577
|
+
await this.validateUpdateId(id, options);
|
|
578
|
+
await this.validateUpdate(options);
|
|
579
|
+
|
|
580
|
+
const updateExpressions: Array<string> = [];
|
|
581
|
+
const vars: Array<any> = [];
|
|
582
|
+
|
|
583
|
+
for (const [key, value] of Object.entries(options)) {
|
|
584
|
+
if (value === undefined) {
|
|
585
|
+
throw new Error(`The update option ${key} is undefined.`);
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
const column = this.getColumn(key);
|
|
589
|
+
if (column.attribute === 'primary') {
|
|
590
|
+
throw new Error(`The primary key ${this.TableName}.${key} cannot be changed.`);
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
vars.push(value);
|
|
594
|
+
updateExpressions.push(`${key} = $${vars.length}`);
|
|
595
|
+
}
|
|
596
|
+
vars.push(id);
|
|
597
|
+
|
|
598
|
+
const sql = `UPDATE ${this.TableName} SET ${updateExpressions.join(',')} WHERE id = $${vars.length}`;
|
|
599
|
+
const data = await this.executeQuery(sql, vars);
|
|
600
|
+
if (data.rowCount !== 1) {
|
|
601
|
+
this.throwValidationError("201", this.errorMessages.idNotExist.replace('{id}', id));
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
public async executeDelete() : Promise<number> {
|
|
606
|
+
this.validateDelete();
|
|
607
|
+
let sql = `DELETE FROM ${this.TableName} "${this.TableAlias}" `;
|
|
608
|
+
|
|
609
|
+
if (this.joinConditions.length > 0) {
|
|
610
|
+
const tables: Array<string> = [];
|
|
611
|
+
for (const join of this.joinConditions) {
|
|
612
|
+
tables.push(`${join.model.TableName} as "${join.model.TableAlias}"`);
|
|
613
|
+
|
|
614
|
+
const query = WhereExpression.createCondition(join.conditions, this, this.vars.length);
|
|
615
|
+
this.whereExpressions.push(query.sql);
|
|
616
|
+
if (query.vars !== undefined) {
|
|
617
|
+
this.vars = [...this.vars, ...query.vars]
|
|
618
|
+
}
|
|
619
|
+
sql += ` USING ${tables.join(',')} `;
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
if (this.whereExpressions.length > 0) {
|
|
624
|
+
sql += "WHERE " + this.whereExpressions.join(" AND ");
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
const datas = await this.executeQuery(sql, this.vars);
|
|
628
|
+
return datas.rowCount;
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
public async executeDeleteId(id: any) : Promise<void> {
|
|
632
|
+
ValidateValueUtil.validateId(this.Columns, id);
|
|
633
|
+
await this.validateDeleteId(id);
|
|
634
|
+
let sql = `DELETE FROM ${this.TableName} WHERE id = $1`;
|
|
635
|
+
|
|
636
|
+
const datas = await this.executeQuery(sql, [id]);
|
|
637
|
+
if (datas.rowCount !== 1) {
|
|
638
|
+
this.throwValidationError("301", this.errorMessages.idNotExist.replace('{id}', id));
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
protected executeQuery(param1: string, vars?: Array<any>) : Promise<any>;
|
|
643
|
+
protected executeQuery(param1: TQuery) : Promise<any>;
|
|
644
|
+
protected async executeQuery(param1: string | TQuery, vars?: Array<any>) : Promise<any> {
|
|
645
|
+
|
|
646
|
+
// 初期化項目
|
|
647
|
+
this.selectExpressions = [];
|
|
648
|
+
this.whereExpressions = [];
|
|
649
|
+
this.joinConditions = [];
|
|
650
|
+
this.sortExpression = [];
|
|
651
|
+
this.SortKeyword = 'asc';
|
|
652
|
+
this.groupExpression = [];
|
|
653
|
+
this.vars = [];
|
|
654
|
+
this.Offset = undefined;
|
|
655
|
+
this.Limit = undefined;
|
|
656
|
+
this.PageCount = 10;
|
|
657
|
+
|
|
658
|
+
let sql = '';
|
|
659
|
+
if (typeof param1 === 'string') {
|
|
660
|
+
sql = param1;
|
|
661
|
+
} else {
|
|
662
|
+
sql = param1.sql;
|
|
663
|
+
vars = param1.vars;
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
|
|
667
|
+
return await this.clientQuery(sql, vars);
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
private async clientQuery(sql: string, vars?: Array<any>) {
|
|
671
|
+
if (this.IsOutputLog) {
|
|
672
|
+
console.log("--- Debug Sql ----------");
|
|
673
|
+
console.log(sql);
|
|
674
|
+
console.log(vars);
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
const data = await this.client.query(sql, vars ?? []);
|
|
678
|
+
if (this.IsOutputLog) {
|
|
679
|
+
console.log("- 実行結果");
|
|
680
|
+
if (data.rowCount == 0) {
|
|
681
|
+
console.log("データなし");
|
|
682
|
+
} else {
|
|
683
|
+
let log = "";
|
|
684
|
+
for (let i = 0;i < data.fields.length;i++) {
|
|
685
|
+
log += i == 0 ? "" : ",";
|
|
686
|
+
log += data.fields[i].name;
|
|
687
|
+
}
|
|
688
|
+
console.log(log);
|
|
689
|
+
|
|
690
|
+
for (let i = 0;i < data.rows.length;i++) {
|
|
691
|
+
log = "";
|
|
692
|
+
for (let j = 0;j < data.fields.length;j++) {
|
|
693
|
+
let key = data.fields[j].name;
|
|
694
|
+
log += j == 0 ? "" : ",";
|
|
695
|
+
log += data.rows[i][key];
|
|
696
|
+
}
|
|
697
|
+
console.log(log);
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
return data;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
private validateClient?: ValidateClient;
|
|
706
|
+
get ValidateClient(): ValidateClient {
|
|
707
|
+
if (this.validateClient === undefined) {
|
|
708
|
+
this.validateClient = new ValidateClient(this);
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
return this.validateClient;
|
|
712
|
+
}
|
|
713
|
+
}
|