@l-v-yonsama/multi-platform-database-drivers 0.1.1
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/.editorconfig +15 -0
- package/.eslintignore +1 -0
- package/.eslintrc.json +30 -0
- package/.github/workflows/npm-publish-github-packages.yml +50 -0
- package/.prettierrc +12 -0
- package/README.md +7 -0
- package/__tests__/db/drivers/AwsS3Driver.test.ts +97 -0
- package/__tests__/db/drivers/MySQLDriver.test.ts +140 -0
- package/__tests__/db/drivers/PostgresDriver.test.ts +139 -0
- package/__tests__/db/drivers/RedisDriver.test.ts +156 -0
- package/jest.config.js +18 -0
- package/package.json +76 -0
- package/src/db/DBError.ts +26 -0
- package/src/db/drivers/AwsS3Driver.ts +539 -0
- package/src/db/drivers/BaseDriver.ts +256 -0
- package/src/db/drivers/MongoDriver.js +110 -0
- package/src/db/drivers/MySQLDriver.ts +318 -0
- package/src/db/drivers/PostgresDriver.ts +363 -0
- package/src/db/drivers/RedisDriver.ts +277 -0
- package/src/db/manager.ts +95 -0
- package/src/db/resource/DbResource.ts +579 -0
- package/src/db/resource/MultipleResultSetDataHolder.ts +119 -0
- package/src/db/resource/ResourceUtil.ts +366 -0
- package/src/db/resource/ResultSetDataHolder.ts +806 -0
- package/src/db/resource/types/DBType.ts +43 -0
- package/src/db/resource/types/GeneralColumnType.ts +321 -0
- package/src/db/resource/types/MySQLColumnType.ts +134 -0
- package/src/db/resource/types/ODBCVendorType.ts +3 -0
- package/src/db/resource/types/PostgresColumnType.ts +94 -0
- package/src/db/resource/types/RedisKeyType.ts +48 -0
- package/src/db/resource/types/ResourceType.ts +10 -0
- package/src/main.ts +1 -0
- package/src/service/request/general_db_request.ts +20 -0
- package/src/service/request/redis_request.ts +21 -0
- package/src/service/request/s3_request.ts +15 -0
- package/src/service/response/GeneralReponse.ts +10 -0
- package/src/types/DateModifiedType.ts +75 -0
- package/src/types/FileKindType.ts +80 -0
- package/src/util/file_util.ts +178 -0
- package/tsconfig.json +23 -0
- package/tsconfig.release.json +23 -0
- package/unit-test.yml +41 -0
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import DBError from '../DBError';
|
|
2
|
+
import {
|
|
3
|
+
DbConnection,
|
|
4
|
+
DbResource,
|
|
5
|
+
ColumnResolver,
|
|
6
|
+
DbColumn,
|
|
7
|
+
SchemaAndTableHints,
|
|
8
|
+
TableRows,
|
|
9
|
+
Proposal,
|
|
10
|
+
} from '../resource/DbResource';
|
|
11
|
+
import ResultSetDataHolder from '../resource/ResultSetDataHolder';
|
|
12
|
+
import * as tunnel from 'tunnel-ssh';
|
|
13
|
+
import getPort, { portNumbers } from 'get-port';
|
|
14
|
+
import ResourceUtil from '../resource/ResourceUtil';
|
|
15
|
+
import { ResourceType } from '../resource/types/ResourceType';
|
|
16
|
+
import { DBType } from '../resource/types/DBType';
|
|
17
|
+
import * as fs from 'fs';
|
|
18
|
+
|
|
19
|
+
export default abstract class BaseDriver {
|
|
20
|
+
public isConnected: boolean;
|
|
21
|
+
protected conRes: DbConnection;
|
|
22
|
+
protected sshServer: any;
|
|
23
|
+
protected sshLocalPort?: number;
|
|
24
|
+
|
|
25
|
+
constructor(conRes: DbConnection) {
|
|
26
|
+
this.conRes = conRes;
|
|
27
|
+
this.isConnected = false;
|
|
28
|
+
// log.info(this.getName(), '★CREATED', this.conRes.id);
|
|
29
|
+
}
|
|
30
|
+
getName(): string {
|
|
31
|
+
return this.constructor.name;
|
|
32
|
+
}
|
|
33
|
+
getConnectionRes(): DbConnection {
|
|
34
|
+
return this.conRes;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
initBaseStatus(): void {
|
|
38
|
+
this.isConnected = false;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
isNeedsSsh(): boolean {
|
|
42
|
+
return this.conRes.hasSshSetting();
|
|
43
|
+
}
|
|
44
|
+
isQuery(sql: string): boolean {
|
|
45
|
+
sql = sql.toLocaleLowerCase();
|
|
46
|
+
sql = sql.replace(/-- .+/, '');
|
|
47
|
+
sql = sql.replace(/(\r\n|\r|\n|\t| )+/g, ' ').trim();
|
|
48
|
+
// console.log('final sql =[' + sql + ']');
|
|
49
|
+
if (sql.match(/select[ ]+[^ ]+[ ]+from/)) {
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
createColumnResolver(sql?: string): ColumnResolver {
|
|
56
|
+
if (sql) {
|
|
57
|
+
const hints = this.parseSchemaAndTableHints(sql);
|
|
58
|
+
return { hints };
|
|
59
|
+
}
|
|
60
|
+
return { hints: { list: [] } };
|
|
61
|
+
}
|
|
62
|
+
parseSchemaAndTableHints(sql: string): SchemaAndTableHints {
|
|
63
|
+
const ret: SchemaAndTableHints = { list: [] };
|
|
64
|
+
const myRegexp = /(FROM|UPDATE)[\s]+(([^\s()]+)\.)?([^\s()]+)/gim;
|
|
65
|
+
let match = myRegexp.exec(sql);
|
|
66
|
+
while (match != null) {
|
|
67
|
+
// sql=SELECT * FROM SSS.TTT
|
|
68
|
+
// [1]=SSS.TTT
|
|
69
|
+
// [2]=SSS.
|
|
70
|
+
// [3]=SSS
|
|
71
|
+
// [4]=TTT
|
|
72
|
+
ret.list.push({ schema: match[3], table: match[4] });
|
|
73
|
+
match = myRegexp.exec(sql);
|
|
74
|
+
}
|
|
75
|
+
return ret;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
getProposals(): Proposal[] {
|
|
79
|
+
if (this.conRes && DBType.isRDB(this.conRes.db_type)) {
|
|
80
|
+
const retList: Proposal[] = [];
|
|
81
|
+
this.conRes.getChildren().forEach((db) => {
|
|
82
|
+
db.getChildren().forEach((schema) => {
|
|
83
|
+
schema.getChildren().forEach((table) => {
|
|
84
|
+
let table_comment = table.comment;
|
|
85
|
+
if (!table_comment) {
|
|
86
|
+
table_comment = table.name;
|
|
87
|
+
}
|
|
88
|
+
retList.push({
|
|
89
|
+
s: schema.name,
|
|
90
|
+
name: table.name,
|
|
91
|
+
comment: table.comment,
|
|
92
|
+
type: table.getResouceType(),
|
|
93
|
+
});
|
|
94
|
+
table.getChildren().forEach((column) => {
|
|
95
|
+
if (column.comment) {
|
|
96
|
+
retList.push({
|
|
97
|
+
s: schema.name,
|
|
98
|
+
t: table.name,
|
|
99
|
+
name: column.name,
|
|
100
|
+
comment: `${table_comment}.${column.comment}`,
|
|
101
|
+
type: column.getResouceType(),
|
|
102
|
+
});
|
|
103
|
+
} else {
|
|
104
|
+
retList.push({
|
|
105
|
+
s: schema.name,
|
|
106
|
+
t: table.name,
|
|
107
|
+
name: column.name,
|
|
108
|
+
comment: `${table_comment}.${column.name}`,
|
|
109
|
+
type: column.getResouceType(),
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
return retList;
|
|
117
|
+
}
|
|
118
|
+
return [];
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
resolveColumn(
|
|
122
|
+
column: string,
|
|
123
|
+
resolver?: ColumnResolver,
|
|
124
|
+
): DbColumn | undefined {
|
|
125
|
+
if (resolver === undefined) {
|
|
126
|
+
return undefined;
|
|
127
|
+
}
|
|
128
|
+
if (this.conRes) {
|
|
129
|
+
for (let i = 0; i < resolver.hints.list.length; i++) {
|
|
130
|
+
const hints = resolver.hints.list[i];
|
|
131
|
+
const t = ResourceUtil.findResource(
|
|
132
|
+
this.conRes,
|
|
133
|
+
ResourceType.Table,
|
|
134
|
+
hints.table,
|
|
135
|
+
);
|
|
136
|
+
if (t) {
|
|
137
|
+
// log.info(LOG_PREFIX, "#resolveColumn found table", hints.table);
|
|
138
|
+
return <DbColumn>t.getChildByName(column, { quote_ident: true });
|
|
139
|
+
} else {
|
|
140
|
+
// log.warn(LOG_PREFIX, "#resolveColumn not found table", hints.table);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
} else {
|
|
144
|
+
// log.warn(LOG_PREFIX, '#resolveColumn no connection res.')
|
|
145
|
+
}
|
|
146
|
+
return undefined;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
resolveColumnComment(column: string, resolver?: ColumnResolver): string {
|
|
150
|
+
const c = this.resolveColumn(column, resolver);
|
|
151
|
+
if (c && c.comment) {
|
|
152
|
+
return c.comment;
|
|
153
|
+
}
|
|
154
|
+
return '';
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
async asyncConnectToSshServer(): Promise<string> {
|
|
158
|
+
this.sshLocalPort = await getPort({ port: portNumbers(13000, 15100) });
|
|
159
|
+
// log.info(LOG_PREFIX, 'SSH Local host port is ', this.sshLocalPort)
|
|
160
|
+
return new Promise<string>((resolve, reject) => {
|
|
161
|
+
const setting = Object.assign({}, this.conRes.ssh, {
|
|
162
|
+
localHost: '127.0.0.1',
|
|
163
|
+
localPort: this.sshLocalPort,
|
|
164
|
+
});
|
|
165
|
+
if (setting.auth_method === 'private_key') {
|
|
166
|
+
setting.privateKey = fs.readFileSync(setting.privateKeyPath, 'utf8');
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
this.sshServer = tunnel(setting, function (err: Error) {
|
|
170
|
+
if (err) {
|
|
171
|
+
reject(err);
|
|
172
|
+
} else {
|
|
173
|
+
resolve('');
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
// Use a listener to handle errors outside the callback
|
|
177
|
+
this.sshServer.on('error', function (err: Error) {
|
|
178
|
+
console.error('Something bad happened:', err);
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
async asyncConnect(): Promise<string> {
|
|
184
|
+
let errorReason = '';
|
|
185
|
+
try {
|
|
186
|
+
this.initBaseStatus();
|
|
187
|
+
if (this.conRes) {
|
|
188
|
+
if (this.isNeedsSsh()) {
|
|
189
|
+
await this.asyncConnectToSshServer();
|
|
190
|
+
}
|
|
191
|
+
errorReason = await this.connectSub();
|
|
192
|
+
} else {
|
|
193
|
+
errorReason = 'Connection property is nothing';
|
|
194
|
+
}
|
|
195
|
+
} catch (e) {
|
|
196
|
+
errorReason = e.message;
|
|
197
|
+
}
|
|
198
|
+
this.isConnected = errorReason === '';
|
|
199
|
+
return errorReason;
|
|
200
|
+
}
|
|
201
|
+
async asyncClose(): Promise<string> {
|
|
202
|
+
let errorReason = '';
|
|
203
|
+
try {
|
|
204
|
+
if (this.conRes) {
|
|
205
|
+
if (this.isConnected) {
|
|
206
|
+
errorReason = await this.closeSub();
|
|
207
|
+
} else {
|
|
208
|
+
// log.info('not connected, skip close.')
|
|
209
|
+
}
|
|
210
|
+
} else {
|
|
211
|
+
errorReason = 'Connection property is nothing';
|
|
212
|
+
}
|
|
213
|
+
} catch (e) {
|
|
214
|
+
errorReason = e.message;
|
|
215
|
+
} finally {
|
|
216
|
+
if (this.sshServer) {
|
|
217
|
+
this.sshServer.close();
|
|
218
|
+
this.sshServer = undefined;
|
|
219
|
+
}
|
|
220
|
+
this.initBaseStatus();
|
|
221
|
+
}
|
|
222
|
+
return errorReason;
|
|
223
|
+
}
|
|
224
|
+
abstract connectSub(): Promise<string>;
|
|
225
|
+
abstract closeSub(): Promise<string>;
|
|
226
|
+
abstract getResouces(options: {
|
|
227
|
+
progress_callback?: Function | undefined;
|
|
228
|
+
params?: any;
|
|
229
|
+
}): Promise<Array<DbResource>>;
|
|
230
|
+
abstract test(with_connect: boolean): Promise<string>;
|
|
231
|
+
abstract countTables(
|
|
232
|
+
tables: SchemaAndTableHints,
|
|
233
|
+
options: any,
|
|
234
|
+
): Promise<TableRows[]>;
|
|
235
|
+
abstract requestSql(
|
|
236
|
+
sql: string,
|
|
237
|
+
options?: RequestSqlOptions,
|
|
238
|
+
): Promise<ResultSetDataHolder>;
|
|
239
|
+
createDBError(message: string, sourceError: any): DBError {
|
|
240
|
+
return new DBError(
|
|
241
|
+
message,
|
|
242
|
+
sourceError.code,
|
|
243
|
+
sourceError.errno,
|
|
244
|
+
sourceError.sqlMessage,
|
|
245
|
+
sourceError.sqlState,
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
export interface RequestSqlOptions {
|
|
251
|
+
binds?: string[];
|
|
252
|
+
needs_column_resolve?: boolean;
|
|
253
|
+
progress_callback?: Function;
|
|
254
|
+
max_rows?: number;
|
|
255
|
+
auto_connection?: boolean;
|
|
256
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import * as DbRes from '../resource/DbResource';
|
|
2
|
+
|
|
3
|
+
export default class MongoDriver {
|
|
4
|
+
constructor() {
|
|
5
|
+
this.client = require('mongodb').MongoClient;
|
|
6
|
+
this.isConnected = false;
|
|
7
|
+
this.dbMap = {};
|
|
8
|
+
}
|
|
9
|
+
getName() {
|
|
10
|
+
return 'MongoDriver';
|
|
11
|
+
}
|
|
12
|
+
setProperties(prop) {
|
|
13
|
+
this.name = prop.name;
|
|
14
|
+
this.url = prop.url;
|
|
15
|
+
}
|
|
16
|
+
connect() {
|
|
17
|
+
const self = this;
|
|
18
|
+
this.isConnected = false;
|
|
19
|
+
return new Promise((resolve, reject) => {
|
|
20
|
+
console.log('connect to ', this.url);
|
|
21
|
+
this.client.connect(this.url, function (err, db) {
|
|
22
|
+
if (err) {
|
|
23
|
+
console.error('Connection failure.', err, db);
|
|
24
|
+
reject(new Error('[Connection failure]:' + err.message));
|
|
25
|
+
} else {
|
|
26
|
+
console.info('Connection success.');
|
|
27
|
+
self.isConnected = true;
|
|
28
|
+
self.db = db;
|
|
29
|
+
var adminDb = self.db.admin();
|
|
30
|
+
adminDb.listDatabases(function (err, dbs) {
|
|
31
|
+
console.log('listed..');
|
|
32
|
+
if (err) {
|
|
33
|
+
console.log('has error..', err);
|
|
34
|
+
reject(new Error(err.message));
|
|
35
|
+
} else {
|
|
36
|
+
dbs.databases.forEach((database) => {
|
|
37
|
+
self.dbMap[database.name] = database;
|
|
38
|
+
});
|
|
39
|
+
resolve({ ok: true });
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
requestSql(sql) {
|
|
47
|
+
return new Promise((resolve, reject) => {
|
|
48
|
+
const self = this;
|
|
49
|
+
console.log('sql=', sql);
|
|
50
|
+
const db = self.db.db('yon_test');
|
|
51
|
+
var collection = db.collection('content_rs');
|
|
52
|
+
collection
|
|
53
|
+
.find({})
|
|
54
|
+
.toArray()
|
|
55
|
+
.then((docs) => {
|
|
56
|
+
console.log('done', docs);
|
|
57
|
+
resolve(docs);
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
getResouces() {
|
|
62
|
+
const self = this;
|
|
63
|
+
console.log('driver.js 43 create promsie');
|
|
64
|
+
return new Promise((resolve, reject) => {
|
|
65
|
+
let dbResources = [];
|
|
66
|
+
console.log('start refreshResouces ');
|
|
67
|
+
let promiseList = [];
|
|
68
|
+
Object.values(self.dbMap).forEach((database) => {
|
|
69
|
+
let schema = new DbRes.DbSchema();
|
|
70
|
+
schema.name = database.name;
|
|
71
|
+
schema.comment = 'sizeOnDisk: ' + database.sizeOnDisk;
|
|
72
|
+
dbResources.push(schema);
|
|
73
|
+
const db1 = self.db.db(database.name);
|
|
74
|
+
console.log('db1=', db1);
|
|
75
|
+
promiseList.push(
|
|
76
|
+
new Promise((resolve, reject) => {
|
|
77
|
+
db1.listCollections({}).toArray(function (err, items) {
|
|
78
|
+
if (!err) {
|
|
79
|
+
items.forEach((t) => {
|
|
80
|
+
let table = new DbRes.DbTable();
|
|
81
|
+
table.name = t.name;
|
|
82
|
+
table.comment = 'Index: ' + t.idIndex;
|
|
83
|
+
schema.children.push(table);
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
resolve(items);
|
|
87
|
+
});
|
|
88
|
+
}),
|
|
89
|
+
);
|
|
90
|
+
});
|
|
91
|
+
console.log('loopend...');
|
|
92
|
+
Promise.all(promiseList)
|
|
93
|
+
.then((results) => {
|
|
94
|
+
console.log('okkkkkkk', results);
|
|
95
|
+
resolve(dbResources);
|
|
96
|
+
})
|
|
97
|
+
.catch((e) => {
|
|
98
|
+
console.log('sippai', e);
|
|
99
|
+
resolve(dbResources);
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
close() {
|
|
104
|
+
this.isConnected = false;
|
|
105
|
+
if (this.db) {
|
|
106
|
+
console.log('closed...');
|
|
107
|
+
this.db.close();
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
import BaseDriver, { RequestSqlOptions } from './BaseDriver';
|
|
2
|
+
import * as mysql from 'mysql2/promise';
|
|
3
|
+
import ResultSetDataHolder, { RdhKey } from '../resource/ResultSetDataHolder';
|
|
4
|
+
import {
|
|
5
|
+
DbConnection,
|
|
6
|
+
DbResource,
|
|
7
|
+
DbDatabase,
|
|
8
|
+
DbSchema,
|
|
9
|
+
DbTable,
|
|
10
|
+
DbColumn,
|
|
11
|
+
ColumnResolver,
|
|
12
|
+
TableRows,
|
|
13
|
+
SchemaAndTableHints,
|
|
14
|
+
} from '../resource/DbResource';
|
|
15
|
+
import { MySQLColumnType } from '../resource/types/MySQLColumnType';
|
|
16
|
+
import { EnumValues } from 'enum-values';
|
|
17
|
+
import { GeneralColumnType } from '../resource/types/GeneralColumnType';
|
|
18
|
+
import { ResultSetHeader } from 'mysql2/promise';
|
|
19
|
+
|
|
20
|
+
export default class MySQLDriver extends BaseDriver {
|
|
21
|
+
private client: mysql.Pool | undefined;
|
|
22
|
+
|
|
23
|
+
constructor(conRes: DbConnection) {
|
|
24
|
+
super(conRes);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
fieldInfo2Key(fieldInfo, resolver?: ColumnResolver): RdhKey {
|
|
28
|
+
if (resolver) {
|
|
29
|
+
resolver.hints.list.push({ table: fieldInfo.orgTable });
|
|
30
|
+
}
|
|
31
|
+
const name = EnumValues.getNameFromValue(
|
|
32
|
+
MySQLColumnType,
|
|
33
|
+
MySQLColumnType.parseByFieldInfo(fieldInfo),
|
|
34
|
+
);
|
|
35
|
+
const key = new RdhKey(
|
|
36
|
+
fieldInfo.name,
|
|
37
|
+
GeneralColumnType.parse(name),
|
|
38
|
+
super.resolveColumnComment(fieldInfo.orgName, resolver),
|
|
39
|
+
);
|
|
40
|
+
if (key.type === GeneralColumnType.UNKNOWN) {
|
|
41
|
+
// log.error(LOG_PREFIX, 'Unknownt=', fieldInfo)
|
|
42
|
+
}
|
|
43
|
+
// console.log('key=', key, EnumValues.getNameFromValue(GeneralColumnType, key.type));
|
|
44
|
+
return key;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async connectSub(): Promise<string> {
|
|
48
|
+
let errorMessage = '';
|
|
49
|
+
const options = {
|
|
50
|
+
connectionLimit: 4,
|
|
51
|
+
multipleStatements: true,
|
|
52
|
+
port: this.conRes.port,
|
|
53
|
+
host: this.conRes.host,
|
|
54
|
+
user: this.conRes.user,
|
|
55
|
+
password: this.conRes.password,
|
|
56
|
+
database: this.conRes.database,
|
|
57
|
+
};
|
|
58
|
+
this.client = mysql.createPool(options);
|
|
59
|
+
try {
|
|
60
|
+
errorMessage = await this.test();
|
|
61
|
+
} catch (e) {
|
|
62
|
+
errorMessage = e.message;
|
|
63
|
+
}
|
|
64
|
+
return errorMessage;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async test(with_connect = false): Promise<string> {
|
|
68
|
+
let errorReason = '';
|
|
69
|
+
if (with_connect) {
|
|
70
|
+
errorReason = await this.asyncConnect();
|
|
71
|
+
}
|
|
72
|
+
if (!errorReason) {
|
|
73
|
+
const rdh = await this.requestSql('SELECT 1 from DUAL');
|
|
74
|
+
if (rdh && rdh.errorMessage) {
|
|
75
|
+
return rdh.errorMessage;
|
|
76
|
+
}
|
|
77
|
+
if (with_connect) {
|
|
78
|
+
await this.asyncClose();
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return errorReason;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// public
|
|
85
|
+
async requestSql(
|
|
86
|
+
sql: string,
|
|
87
|
+
options?: RequestSqlOptions,
|
|
88
|
+
): Promise<ResultSetDataHolder> {
|
|
89
|
+
// console.log('requestSql', sql);
|
|
90
|
+
let rdh = new ResultSetDataHolder([]);
|
|
91
|
+
|
|
92
|
+
if (this.client) {
|
|
93
|
+
let binds: string[] = [];
|
|
94
|
+
if (options && options.binds) {
|
|
95
|
+
binds = options.binds;
|
|
96
|
+
}
|
|
97
|
+
let resolver: ColumnResolver;
|
|
98
|
+
if (options && options.needs_column_resolve === true) {
|
|
99
|
+
resolver = this.createColumnResolver(sql);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const [rows, fields] = await this.client.execute(sql, binds);
|
|
103
|
+
try {
|
|
104
|
+
if (fields === undefined) {
|
|
105
|
+
// execute...
|
|
106
|
+
// Ok Packet {
|
|
107
|
+
// fieldCount: 0,
|
|
108
|
+
// affectedRows: 1,
|
|
109
|
+
// insertId: 0,
|
|
110
|
+
// serverStatus: 2,
|
|
111
|
+
// warningCount: 0,
|
|
112
|
+
// message: '',
|
|
113
|
+
// protocol41: true,
|
|
114
|
+
// changedRows: 0 }
|
|
115
|
+
const results = rows as ResultSetHeader;
|
|
116
|
+
|
|
117
|
+
rdh = new ResultSetDataHolder([
|
|
118
|
+
'fieldCount',
|
|
119
|
+
'affectedRows',
|
|
120
|
+
'insertId',
|
|
121
|
+
'serverStatus',
|
|
122
|
+
'warningStatus',
|
|
123
|
+
'changedRows',
|
|
124
|
+
]);
|
|
125
|
+
rdh.addRow({
|
|
126
|
+
fieldCount: results.fieldCount,
|
|
127
|
+
affectedRows: results.affectedRows,
|
|
128
|
+
insertId: results.insertId,
|
|
129
|
+
serverStatus: results.serverStatus,
|
|
130
|
+
warningStatus: results.warningStatus,
|
|
131
|
+
changedRows: results.changedRows,
|
|
132
|
+
});
|
|
133
|
+
} else {
|
|
134
|
+
rdh = new ResultSetDataHolder(
|
|
135
|
+
fields === undefined
|
|
136
|
+
? []
|
|
137
|
+
: fields.map((f) => this.fieldInfo2Key(f, resolver)),
|
|
138
|
+
);
|
|
139
|
+
(rows as any).forEach((result: any) => {
|
|
140
|
+
rdh.addRow(result);
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
} catch (err) {
|
|
144
|
+
rdh = ResultSetDataHolder.create(err);
|
|
145
|
+
}
|
|
146
|
+
} else {
|
|
147
|
+
new Error('No connection');
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return rdh;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async countTables(
|
|
154
|
+
tables: SchemaAndTableHints,
|
|
155
|
+
options: any,
|
|
156
|
+
): Promise<TableRows[]> {
|
|
157
|
+
const list = new Array<TableRows>();
|
|
158
|
+
let counter = 1;
|
|
159
|
+
for (const st of tables.list) {
|
|
160
|
+
let prefix = '';
|
|
161
|
+
if (st.schema) {
|
|
162
|
+
prefix = st.schema + '.';
|
|
163
|
+
}
|
|
164
|
+
if (options && options.progress_callback) {
|
|
165
|
+
if (counter % 5 === 0) {
|
|
166
|
+
options.progress_callback(`Count ${prefix}${st.table}`, 70);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
const sql = `SELECT COUNT(*) as count FROM ${prefix}${st.table}`;
|
|
170
|
+
try {
|
|
171
|
+
const [results] = await this.client.query(sql, []);
|
|
172
|
+
if (results && (results as any).length > 0) {
|
|
173
|
+
const row = results[0];
|
|
174
|
+
const obj: TableRows = Object.assign({ count: row.count }, st);
|
|
175
|
+
list.push(obj);
|
|
176
|
+
}
|
|
177
|
+
} catch (e) {
|
|
178
|
+
console.error(e);
|
|
179
|
+
}
|
|
180
|
+
counter++;
|
|
181
|
+
}
|
|
182
|
+
return list;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
async getResouces(options: {
|
|
186
|
+
progress_callback?: Function | undefined;
|
|
187
|
+
params?: any;
|
|
188
|
+
}): Promise<Array<DbResource>> {
|
|
189
|
+
if (!this.conRes) {
|
|
190
|
+
return [];
|
|
191
|
+
}
|
|
192
|
+
const dbResources = new Array<DbResource>();
|
|
193
|
+
const dbDatabase = new DbDatabase(this.conRes.database);
|
|
194
|
+
dbResources.push(dbDatabase);
|
|
195
|
+
let progress = 10;
|
|
196
|
+
if (options.progress_callback) {
|
|
197
|
+
options.progress_callback(
|
|
198
|
+
`${dbResources.length} Databases found.`,
|
|
199
|
+
progress,
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const dbSchemas = await this.getSchemas(dbDatabase);
|
|
204
|
+
dbSchemas.forEach((res) => {
|
|
205
|
+
dbDatabase.addChild(res);
|
|
206
|
+
});
|
|
207
|
+
progress = 30;
|
|
208
|
+
if (options.progress_callback) {
|
|
209
|
+
options.progress_callback(`${dbSchemas.length} Schemas found.`, progress);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// const parallels = [];
|
|
213
|
+
const incrPerSchema = Math.round(20 / dbSchemas.length);
|
|
214
|
+
for (const dbSchema of dbSchemas) {
|
|
215
|
+
const dbTables = await this.getTables(dbSchema);
|
|
216
|
+
dbTables.forEach((res) => dbSchema.addChild(res));
|
|
217
|
+
if (options.progress_callback) {
|
|
218
|
+
progress += incrPerSchema;
|
|
219
|
+
options.progress_callback(
|
|
220
|
+
`${dbTables.length} Tables found in Schema[${dbSchema.getName()}].`,
|
|
221
|
+
progress,
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
for (const dbSchema of dbSchemas) {
|
|
226
|
+
if (options.progress_callback) {
|
|
227
|
+
progress += incrPerSchema;
|
|
228
|
+
options.progress_callback(
|
|
229
|
+
`Commnets finding now... in Schema[${dbSchema.getName()}].`,
|
|
230
|
+
progress,
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
for (const dbTable of dbSchema.getChildren()) {
|
|
234
|
+
const dbColumns = await this.getColumns(<DbTable>dbTable);
|
|
235
|
+
dbColumns.forEach((res: DbColumn) => dbTable.addChild(res));
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
return dbResources;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
242
|
+
async getSchemas(dbDatabase: DbDatabase): Promise<Array<DbSchema>> {
|
|
243
|
+
const rdh = await this.requestSql(`SELECT SCHEMA_NAME AS name
|
|
244
|
+
FROM INFORMATION_SCHEMA.SCHEMATA
|
|
245
|
+
WHERE LOWER(SCHEMA_NAME) NOT IN ('information_schema', 'sys', 'performance_schema')
|
|
246
|
+
ORDER BY name`);
|
|
247
|
+
return rdh.rows.map((r) => {
|
|
248
|
+
const res = new DbSchema(r.values.name);
|
|
249
|
+
return res;
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
async getTables(dbSchema: DbSchema): Promise<Array<DbTable>> {
|
|
254
|
+
const rdh = await this
|
|
255
|
+
.requestSql(`SELECT TABLE_NAME as name, CASE TABLE_TYPE
|
|
256
|
+
WHEN 'BASE TABLE' THEN 'TABLE'
|
|
257
|
+
WHEN 'SYSTEM VIEW' THEN 'VIEW'
|
|
258
|
+
ELSE 'TABLE' END AS table_type,
|
|
259
|
+
TABLE_COMMENT as comment
|
|
260
|
+
FROM INFORMATION_SCHEMA.TABLES
|
|
261
|
+
WHERE TABLE_SCHEMA = '${dbSchema.getName()}' `);
|
|
262
|
+
|
|
263
|
+
return rdh.rows.map((r) => {
|
|
264
|
+
const res = new DbTable(
|
|
265
|
+
r.values.name,
|
|
266
|
+
r.values.table_type,
|
|
267
|
+
r.values.comment,
|
|
268
|
+
);
|
|
269
|
+
return res;
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
async getColumns(dbTable: DbTable): Promise<Array<DbColumn>> {
|
|
274
|
+
const binds = [dbTable.getParent().getName(), dbTable.getName()];
|
|
275
|
+
const rdh = await this.requestSql(
|
|
276
|
+
`SELECT COLUMN_NAME as name,
|
|
277
|
+
DATA_TYPE as col_type,
|
|
278
|
+
CASE WHEN IS_NULLABLE = 'YES' THEN 1 ELSE 0 END as nullable,
|
|
279
|
+
COLUMN_KEY as col_key,
|
|
280
|
+
COLUMN_DEFAULT as col_default,
|
|
281
|
+
EXTRA as col_extra,
|
|
282
|
+
COLUMN_COMMENT as comment
|
|
283
|
+
FROM INFORMATION_SCHEMA.COLUMNS
|
|
284
|
+
WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?`,
|
|
285
|
+
{ binds },
|
|
286
|
+
);
|
|
287
|
+
|
|
288
|
+
return rdh.rows.map((r) => {
|
|
289
|
+
const type_name = EnumValues.getNameFromValue(
|
|
290
|
+
MySQLColumnType,
|
|
291
|
+
MySQLColumnType.parse(r.values.col_type),
|
|
292
|
+
);
|
|
293
|
+
const res = new DbColumn(
|
|
294
|
+
r.values.name,
|
|
295
|
+
GeneralColumnType.parse(type_name),
|
|
296
|
+
{
|
|
297
|
+
nullable: r.values.nullable === 1,
|
|
298
|
+
key: r.values.col_key,
|
|
299
|
+
default: r.values.col_default,
|
|
300
|
+
extra: r.values.col_extra,
|
|
301
|
+
},
|
|
302
|
+
r.values.comment,
|
|
303
|
+
);
|
|
304
|
+
return res;
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
async closeSub(): Promise<string> {
|
|
309
|
+
try {
|
|
310
|
+
if (this.client) {
|
|
311
|
+
await this.client.end();
|
|
312
|
+
}
|
|
313
|
+
return '';
|
|
314
|
+
} catch (e) {
|
|
315
|
+
return e.message;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|