@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,363 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
2
|
+
import BaseDriver, { RequestSqlOptions } from './BaseDriver';
|
|
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 { default as pg } from 'pg';
|
|
16
|
+
import { PostgresColumnType } from '../resource/types/PostgresColumnType';
|
|
17
|
+
import { EnumValues } from 'enum-values';
|
|
18
|
+
import { GeneralColumnType } from '../resource/types/GeneralColumnType';
|
|
19
|
+
|
|
20
|
+
export default class PostgresDriver extends BaseDriver {
|
|
21
|
+
private pool: pg.Pool;
|
|
22
|
+
|
|
23
|
+
constructor(conRes: DbConnection) {
|
|
24
|
+
super(conRes);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// name: 'name',
|
|
28
|
+
// tableID: 12822,
|
|
29
|
+
// columnID: 2,
|
|
30
|
+
// dataTypeID: 1043,
|
|
31
|
+
// dataTypeSize: -1,
|
|
32
|
+
// dataTypeModifier: -1,
|
|
33
|
+
// format: 'text' }
|
|
34
|
+
fieldInfo2Key(fieldInfo: pg.FieldDef, resolver?: ColumnResolver): RdhKey {
|
|
35
|
+
if (fieldInfo.name.startsWith('c_')) {
|
|
36
|
+
console.log(
|
|
37
|
+
`★ ${fieldInfo.name.substring(2).toUpperCase()} = ${
|
|
38
|
+
fieldInfo.dataTypeID
|
|
39
|
+
}`,
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
const name = EnumValues.getNameFromValue(
|
|
43
|
+
PostgresColumnType,
|
|
44
|
+
PostgresColumnType.parse(fieldInfo.dataTypeID),
|
|
45
|
+
);
|
|
46
|
+
const key = new RdhKey(
|
|
47
|
+
fieldInfo.name,
|
|
48
|
+
GeneralColumnType.parse(name),
|
|
49
|
+
super.resolveColumnComment(fieldInfo.name, resolver),
|
|
50
|
+
);
|
|
51
|
+
return key;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async connectSub(): Promise<string> {
|
|
55
|
+
let errorReason = '';
|
|
56
|
+
|
|
57
|
+
const options = Object.assign(
|
|
58
|
+
{
|
|
59
|
+
port: 5432,
|
|
60
|
+
host: '127.0.0.1',
|
|
61
|
+
database: 'postgres',
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
max: 1,
|
|
65
|
+
idleTimeoutMillis: 3000,
|
|
66
|
+
connectionTimeoutMillis: 1000,
|
|
67
|
+
port: this.conRes.port,
|
|
68
|
+
host: this.conRes.host,
|
|
69
|
+
user: this.conRes.user,
|
|
70
|
+
password: this.conRes.password,
|
|
71
|
+
database: this.conRes.database,
|
|
72
|
+
},
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
this.pool = new pg.Pool(options);
|
|
76
|
+
// the pool with emit an error on behalf of any idle clients
|
|
77
|
+
// it contains if a backend error or network partition happens
|
|
78
|
+
// this.pool.on('error', (err, client) => {
|
|
79
|
+
// // log.error('Unexpected error on idle client', err);
|
|
80
|
+
// });
|
|
81
|
+
// this.pool.on('acquire', function (client) {
|
|
82
|
+
// // log.info('acquire', client);
|
|
83
|
+
// });
|
|
84
|
+
// this.pool.on('connect', function (client) {
|
|
85
|
+
// // log.info('connect', client);
|
|
86
|
+
// });
|
|
87
|
+
errorReason = await this.test();
|
|
88
|
+
|
|
89
|
+
return errorReason;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async test(with_connect = false): Promise<string> {
|
|
93
|
+
let errorReason = '';
|
|
94
|
+
try {
|
|
95
|
+
if (with_connect) {
|
|
96
|
+
errorReason = await this.asyncConnect();
|
|
97
|
+
}
|
|
98
|
+
const rdh = await this.requestSql('SELECT NOW()');
|
|
99
|
+
if (rdh && rdh.errorMessage) {
|
|
100
|
+
errorReason = rdh.errorMessage;
|
|
101
|
+
}
|
|
102
|
+
} catch (e) {
|
|
103
|
+
errorReason = e.message;
|
|
104
|
+
} finally {
|
|
105
|
+
if (with_connect) {
|
|
106
|
+
await this.asyncClose();
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return errorReason;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// public
|
|
113
|
+
async requestSql(
|
|
114
|
+
sql: string,
|
|
115
|
+
options?: RequestSqlOptions,
|
|
116
|
+
): Promise<ResultSetDataHolder> {
|
|
117
|
+
// log.info("sql2=", sql);
|
|
118
|
+
let rdh = new ResultSetDataHolder([]);
|
|
119
|
+
|
|
120
|
+
try {
|
|
121
|
+
let binds: string[] = [];
|
|
122
|
+
if (options && options.binds) {
|
|
123
|
+
binds = options.binds;
|
|
124
|
+
}
|
|
125
|
+
const results = await this.pool.query(sql, binds);
|
|
126
|
+
// command: 'SELECT',
|
|
127
|
+
// rowCount: 5,
|
|
128
|
+
// oid: null,
|
|
129
|
+
// rows:
|
|
130
|
+
// [ anonymous { name: 'pg_catalog' },
|
|
131
|
+
// anonymous { name: 'pg_temp_1' },
|
|
132
|
+
// anonymous { name: 'pg_toast' },
|
|
133
|
+
// anonymous { name: 'pg_toast_temp_1' },
|
|
134
|
+
// anonymous { name: 'public' } ],
|
|
135
|
+
// fields: [ ],
|
|
136
|
+
// console.log('done.', results.fields)
|
|
137
|
+
if (results) {
|
|
138
|
+
let resolver: ColumnResolver;
|
|
139
|
+
if (options && options.needs_column_resolve === true) {
|
|
140
|
+
resolver = this.createColumnResolver(sql);
|
|
141
|
+
}
|
|
142
|
+
const fields = results.fields;
|
|
143
|
+
rdh = new ResultSetDataHolder(
|
|
144
|
+
fields === undefined
|
|
145
|
+
? []
|
|
146
|
+
: fields.map((f) => this.fieldInfo2Key(f, resolver)),
|
|
147
|
+
);
|
|
148
|
+
if (results.rows) {
|
|
149
|
+
results.rows.forEach((result: any) => {
|
|
150
|
+
rdh.addRow(result);
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
} catch (err) {
|
|
155
|
+
rdh = ResultSetDataHolder.create(err);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return rdh;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async countTables(
|
|
162
|
+
tables: SchemaAndTableHints,
|
|
163
|
+
options: any,
|
|
164
|
+
): Promise<TableRows[]> {
|
|
165
|
+
const list = new Array<TableRows>();
|
|
166
|
+
let counter = 1;
|
|
167
|
+
for (const st of tables.list) {
|
|
168
|
+
let prefix = '';
|
|
169
|
+
if (st.schema) {
|
|
170
|
+
prefix = st.schema + '.';
|
|
171
|
+
}
|
|
172
|
+
if (options && options.progress_callback) {
|
|
173
|
+
if (counter % 5 === 0) {
|
|
174
|
+
options.progress_callback(`Count ${prefix}${st.table}`, 70);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
const sql = `SELECT COUNT(*) as count FROM ${prefix}${st.table}`;
|
|
178
|
+
try {
|
|
179
|
+
const results = await this.pool.query(sql, []);
|
|
180
|
+
if (results && results.rows && results.rows.length > 0) {
|
|
181
|
+
const row = results.rows[0];
|
|
182
|
+
const obj: TableRows = Object.assign({ count: row.count }, st);
|
|
183
|
+
list.push(obj);
|
|
184
|
+
}
|
|
185
|
+
// eslint-disable-next-line no-empty
|
|
186
|
+
} catch (e) {}
|
|
187
|
+
counter++;
|
|
188
|
+
}
|
|
189
|
+
return list;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
async getResouces(options: {
|
|
193
|
+
progress_callback?: Function | undefined;
|
|
194
|
+
params?: any;
|
|
195
|
+
}): Promise<Array<DbResource>> {
|
|
196
|
+
if (!this.conRes) {
|
|
197
|
+
return [];
|
|
198
|
+
}
|
|
199
|
+
const dbResources = new Array<DbResource>();
|
|
200
|
+
const db_list = await this.asyncGetDatabases(this.conRes.database);
|
|
201
|
+
db_list.forEach((db) => dbResources.push(db));
|
|
202
|
+
const dbDatabase = db_list.find((d) => d.name === this.conRes.database);
|
|
203
|
+
|
|
204
|
+
const dbSchemas = await this.getSchemas(dbDatabase);
|
|
205
|
+
dbSchemas.forEach((res) => {
|
|
206
|
+
dbDatabase.addChild(res);
|
|
207
|
+
});
|
|
208
|
+
// const parallels = [];
|
|
209
|
+
for (const dbSchema of dbSchemas) {
|
|
210
|
+
const dbTables = await this.getTables(dbSchema);
|
|
211
|
+
dbTables.forEach((res) => dbSchema.addChild(res));
|
|
212
|
+
}
|
|
213
|
+
for (const dbSchema of dbSchemas) {
|
|
214
|
+
for (const dbTable of dbSchema.getChildren()) {
|
|
215
|
+
const dbColumns = await this.getColumns(<DbTable>dbTable);
|
|
216
|
+
dbColumns.forEach((res) => dbTable.addChild(res));
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
return dbResources;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
async asyncGetDatabases(
|
|
223
|
+
connection_database: string,
|
|
224
|
+
): Promise<Array<DbDatabase>> {
|
|
225
|
+
const rdh = await this
|
|
226
|
+
.requestSql(`SELECT datname AS name, pg_encoding_to_char(encoding) AS comment
|
|
227
|
+
FROM pg_database ORDER BY datname`);
|
|
228
|
+
return rdh.rows.map((r) => {
|
|
229
|
+
const res = new DbDatabase(r.values.name);
|
|
230
|
+
res.comment = r.values.comment;
|
|
231
|
+
res.disabled = res.name !== connection_database;
|
|
232
|
+
return res;
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
async getSchemas(dbDatabase: DbDatabase): Promise<Array<DbSchema>> {
|
|
237
|
+
const rdh = await this.requestSql(`SELECT SCHEMA_NAME AS name
|
|
238
|
+
FROM INFORMATION_SCHEMA.SCHEMATA
|
|
239
|
+
WHERE LOWER(SCHEMA_NAME) NOT IN ('information_schema', 'sys', 'performance_schema', 'pg_catalog', 'pg_toast', 'pg_temp_1', 'pg_toast_temp_1')
|
|
240
|
+
ORDER BY name`);
|
|
241
|
+
|
|
242
|
+
return rdh.rows.map((r) => {
|
|
243
|
+
const res = new DbSchema(r.values.name);
|
|
244
|
+
return res;
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
async getTables(dbSchema: DbSchema): Promise<Array<DbTable>> {
|
|
249
|
+
let rdh = await this
|
|
250
|
+
.requestSql(`select quote_ident(m.relname) as qname, COALESCE(d.description, '') as comment
|
|
251
|
+
from pg_stat_all_tables as m
|
|
252
|
+
LEFT JOIN pg_description as d ON (m.relid = d.objoid AND d.objsubid=0)
|
|
253
|
+
WHERE m.schemaname='${dbSchema.getName()}'
|
|
254
|
+
ORDER BY m.relname`);
|
|
255
|
+
|
|
256
|
+
const list = rdh.rows.map((r) => {
|
|
257
|
+
const res = new DbTable(r.values.qname, 'TABLE', r.values.comment);
|
|
258
|
+
return res;
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
rdh = await this
|
|
262
|
+
.requestSql(`select quote_ident(viewname) as qname, definition from pg_catalog.pg_views
|
|
263
|
+
where schemaname = '${dbSchema.getName()}'
|
|
264
|
+
order by viewname`);
|
|
265
|
+
|
|
266
|
+
return list.concat(
|
|
267
|
+
rdh.rows.map((r) => {
|
|
268
|
+
const res = new DbTable(r.values.qname, 'VIEW', '');
|
|
269
|
+
return res;
|
|
270
|
+
}),
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
async getColumns(dbTable: DbTable): Promise<Array<DbColumn>> {
|
|
275
|
+
const binds = [dbTable.getParent().getName(), dbTable.getName()];
|
|
276
|
+
const rdh = await this.requestSql(
|
|
277
|
+
`select
|
|
278
|
+
col.COLUMN_NAME as name,
|
|
279
|
+
quote_ident(col.COLUMN_NAME) as qname,
|
|
280
|
+
data_type as col_type,
|
|
281
|
+
case
|
|
282
|
+
when IS_NULLABLE = 'YES' then 1
|
|
283
|
+
else 0
|
|
284
|
+
end as nullable,
|
|
285
|
+
case
|
|
286
|
+
when pk.column_name is not null then 'PRI'
|
|
287
|
+
else null
|
|
288
|
+
end as col_key,
|
|
289
|
+
COLUMN_DEFAULT as col_default,
|
|
290
|
+
null as col_extra,
|
|
291
|
+
(
|
|
292
|
+
select
|
|
293
|
+
pg_catalog.col_description(oid,
|
|
294
|
+
col.ordinal_position::int)
|
|
295
|
+
from
|
|
296
|
+
pg_catalog.pg_class c
|
|
297
|
+
where
|
|
298
|
+
c.relname = col.table_name) as comment
|
|
299
|
+
from
|
|
300
|
+
INFORMATION_SCHEMA.columns col
|
|
301
|
+
left join (
|
|
302
|
+
select
|
|
303
|
+
tc.table_catalog,
|
|
304
|
+
tc.table_schema,
|
|
305
|
+
tc.table_name,
|
|
306
|
+
ccu.column_name
|
|
307
|
+
from
|
|
308
|
+
information_schema.table_constraints tc
|
|
309
|
+
inner join
|
|
310
|
+
information_schema.constraint_column_usage ccu
|
|
311
|
+
on
|
|
312
|
+
(tc.table_catalog = ccu.table_catalog
|
|
313
|
+
and
|
|
314
|
+
tc.table_schema = ccu.table_schema
|
|
315
|
+
and
|
|
316
|
+
tc.table_name = ccu.table_name
|
|
317
|
+
and
|
|
318
|
+
tc.constraint_name = ccu.constraint_name)
|
|
319
|
+
where
|
|
320
|
+
tc.constraint_type = 'PRIMARY KEY'
|
|
321
|
+
) pk on
|
|
322
|
+
(col.table_catalog = pk.table_catalog
|
|
323
|
+
and col.table_schema = pk.table_schema
|
|
324
|
+
and col.table_name = pk.table_name
|
|
325
|
+
and col.column_name = pk.column_name)
|
|
326
|
+
where
|
|
327
|
+
col.table_schema = $1 AND quote_ident(col.table_name) = $2
|
|
328
|
+
order by
|
|
329
|
+
col.ordinal_position`,
|
|
330
|
+
{ binds },
|
|
331
|
+
);
|
|
332
|
+
|
|
333
|
+
return rdh.rows.map((r) => {
|
|
334
|
+
const type_name = EnumValues.getNameFromValue(
|
|
335
|
+
PostgresColumnType,
|
|
336
|
+
PostgresColumnType.parse(r.values.col_type),
|
|
337
|
+
);
|
|
338
|
+
const res = new DbColumn(
|
|
339
|
+
r.values.qname,
|
|
340
|
+
GeneralColumnType.parse(type_name),
|
|
341
|
+
{
|
|
342
|
+
nullable: r.values.nullable === 1,
|
|
343
|
+
key: r.values.col_key,
|
|
344
|
+
default: r.values.col_default,
|
|
345
|
+
extra: r.values.col_extra,
|
|
346
|
+
},
|
|
347
|
+
r.values.comment,
|
|
348
|
+
);
|
|
349
|
+
return res;
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
async closeSub(): Promise<string> {
|
|
354
|
+
try {
|
|
355
|
+
if (this.pool) {
|
|
356
|
+
await this.pool.end();
|
|
357
|
+
}
|
|
358
|
+
return '';
|
|
359
|
+
} catch (e) {
|
|
360
|
+
return e.message;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
2
|
+
import BaseDriver, { RequestSqlOptions } from './BaseDriver';
|
|
3
|
+
import {
|
|
4
|
+
DbConnection,
|
|
5
|
+
DbResource,
|
|
6
|
+
RedisDatabase,
|
|
7
|
+
DbKey,
|
|
8
|
+
SchemaAndTableHints,
|
|
9
|
+
TableRows,
|
|
10
|
+
} from '../resource/DbResource';
|
|
11
|
+
import { Redis } from 'ioredis';
|
|
12
|
+
import ResultSetDataHolder from '../resource/ResultSetDataHolder';
|
|
13
|
+
import { RedisKeyType } from '../resource/types/RedisKeyType';
|
|
14
|
+
import {
|
|
15
|
+
RedisCommandType,
|
|
16
|
+
RedisRequest,
|
|
17
|
+
} from '../../service/request/redis_request';
|
|
18
|
+
|
|
19
|
+
export default class RedisDriver extends BaseDriver {
|
|
20
|
+
client: Redis | undefined;
|
|
21
|
+
databases = 16;
|
|
22
|
+
|
|
23
|
+
constructor(conRes: DbConnection) {
|
|
24
|
+
super(conRes);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async connectSub(): Promise<string> {
|
|
28
|
+
try {
|
|
29
|
+
const options: any = Object.assign(
|
|
30
|
+
{
|
|
31
|
+
port: 6379, // Redis port
|
|
32
|
+
host: '127.0.0.1', // Redis host
|
|
33
|
+
password: 'auth',
|
|
34
|
+
db: 0,
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
port: this.conRes.port,
|
|
38
|
+
host: this.conRes.host,
|
|
39
|
+
password: this.conRes.password,
|
|
40
|
+
db: this.conRes.database,
|
|
41
|
+
retryStrategy: function () {
|
|
42
|
+
return 'No!';
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
);
|
|
46
|
+
if (this.isNeedsSsh()) {
|
|
47
|
+
options.host = '127.0.0.1';
|
|
48
|
+
options.port = this.sshLocalPort;
|
|
49
|
+
}
|
|
50
|
+
options.connectTimeout = 5_000;
|
|
51
|
+
if (this.conRes.hasUrl()) {
|
|
52
|
+
// Connect to 127.0.0.1:6380, db 4, using password "authpassword":
|
|
53
|
+
// "redis://:authpassword@127.0.0.1:6380/4"
|
|
54
|
+
this.client = new Redis(this.conRes.url);
|
|
55
|
+
} else {
|
|
56
|
+
this.client = new Redis(options);
|
|
57
|
+
}
|
|
58
|
+
// dbs= [ [ null, '# Serverxxxx' ], [ null, [ 'databases', '16' ] ] ]
|
|
59
|
+
try {
|
|
60
|
+
// ReplyError: EXECABORT Transaction discarded because of previous errors.
|
|
61
|
+
const dbs = await this.client
|
|
62
|
+
.multi()
|
|
63
|
+
.info()
|
|
64
|
+
.config('GET', 'databases')
|
|
65
|
+
.exec();
|
|
66
|
+
if (dbs && dbs.length > 1 && dbs[1].length > 1) {
|
|
67
|
+
const dbs2 = dbs[1][1] as any; // dbs2= [ 'databases', '16' ]
|
|
68
|
+
if (dbs2 && dbs2.length > 1) {
|
|
69
|
+
this.databases = parseInt(dbs2[1], 10);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
} catch (e) {
|
|
73
|
+
this.databases = 16;
|
|
74
|
+
}
|
|
75
|
+
} catch (e) {
|
|
76
|
+
return e.message;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return '';
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async test(with_connect = false): Promise<string> {
|
|
83
|
+
let errorReason = '';
|
|
84
|
+
try {
|
|
85
|
+
if (with_connect) {
|
|
86
|
+
const con_result = await this.asyncConnect();
|
|
87
|
+
if (con_result) {
|
|
88
|
+
return con_result;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
await this.client.ping();
|
|
92
|
+
if (with_connect) {
|
|
93
|
+
await this.asyncClose();
|
|
94
|
+
}
|
|
95
|
+
} catch (e) {
|
|
96
|
+
errorReason = e.message;
|
|
97
|
+
}
|
|
98
|
+
return errorReason;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async requestSql(
|
|
102
|
+
sql: string,
|
|
103
|
+
options?: RequestSqlOptions,
|
|
104
|
+
): Promise<ResultSetDataHolder> {
|
|
105
|
+
return ResultSetDataHolder.createEmpty();
|
|
106
|
+
}
|
|
107
|
+
async countTables(
|
|
108
|
+
tables: SchemaAndTableHints,
|
|
109
|
+
options: any,
|
|
110
|
+
): Promise<TableRows[]> {
|
|
111
|
+
return new Array<TableRows>();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async executeCommand(req: RedisRequest): Promise<DbKey | string | number> {
|
|
115
|
+
let ret: DbKey | string | number = '';
|
|
116
|
+
if (!this.client) {
|
|
117
|
+
return ret;
|
|
118
|
+
}
|
|
119
|
+
await this.client.select(req.index);
|
|
120
|
+
let r: any = '';
|
|
121
|
+
switch (req.command) {
|
|
122
|
+
case RedisCommandType.GetValue:
|
|
123
|
+
{
|
|
124
|
+
r = await this.getValueByKey(this.client, req.key, req.type);
|
|
125
|
+
const ttl = await this.client.ttl(req.key);
|
|
126
|
+
ret = new DbKey(req.key, req.type, ttl);
|
|
127
|
+
if (ret.ttl > 0) {
|
|
128
|
+
ret.ttl_confirmation_datetime = new Date().getTime();
|
|
129
|
+
}
|
|
130
|
+
ret.val = r;
|
|
131
|
+
}
|
|
132
|
+
break;
|
|
133
|
+
case RedisCommandType.SetValue:
|
|
134
|
+
if (req.options) {
|
|
135
|
+
await this.client.set(req.key, req.options.val);
|
|
136
|
+
}
|
|
137
|
+
break;
|
|
138
|
+
case RedisCommandType.Flushall:
|
|
139
|
+
ret = await this.client.flushall();
|
|
140
|
+
break;
|
|
141
|
+
case RedisCommandType.Flushdb:
|
|
142
|
+
ret = await this.client.flushdb();
|
|
143
|
+
break;
|
|
144
|
+
case RedisCommandType.Dbsize:
|
|
145
|
+
ret = await this.client.dbsize();
|
|
146
|
+
break;
|
|
147
|
+
case RedisCommandType.Info:
|
|
148
|
+
ret = await this.client.info(<string>req.options.section);
|
|
149
|
+
break;
|
|
150
|
+
default:
|
|
151
|
+
console.error('undefined.', req.command);
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
return ret;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
scan(
|
|
158
|
+
db_index: number,
|
|
159
|
+
key: string,
|
|
160
|
+
count: number,
|
|
161
|
+
with_value: boolean,
|
|
162
|
+
progressCallback: Function,
|
|
163
|
+
): Promise<Array<DbKey>> {
|
|
164
|
+
return new Promise((resolve) => {
|
|
165
|
+
const currentClient = this.client;
|
|
166
|
+
this.client.select(db_index);
|
|
167
|
+
const stream = this.client.scanStream({
|
|
168
|
+
match: key,
|
|
169
|
+
count,
|
|
170
|
+
});
|
|
171
|
+
const ret = new Array<DbKey>();
|
|
172
|
+
stream.on('data', (resultKeys: string[]) => {
|
|
173
|
+
resultKeys.forEach((key) => {
|
|
174
|
+
ret.push(new DbKey(key));
|
|
175
|
+
});
|
|
176
|
+
progressCallback(
|
|
177
|
+
'Key scanning now.',
|
|
178
|
+
Math.round((ret.length * 100) / count),
|
|
179
|
+
);
|
|
180
|
+
if (ret.length > count) {
|
|
181
|
+
(<any>stream).close(); // ScanStream.close()
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
stream.on('end', () => {
|
|
185
|
+
progressCallback('Key scanning done. get type and ttl.', 80);
|
|
186
|
+
if (ret.length > count) {
|
|
187
|
+
ret.splice(count - 1, ret.length - count);
|
|
188
|
+
}
|
|
189
|
+
const parallels: any[] = [];
|
|
190
|
+
ret.forEach((keyRes) => {
|
|
191
|
+
parallels.push(
|
|
192
|
+
(async (): Promise<void> => {
|
|
193
|
+
keyRes.type = RedisKeyType.parse(
|
|
194
|
+
await currentClient.type(keyRes.getName()),
|
|
195
|
+
);
|
|
196
|
+
keyRes.ttl = await currentClient.ttl(keyRes.getName());
|
|
197
|
+
if (keyRes.ttl > 0) {
|
|
198
|
+
keyRes.ttl_confirmation_datetime = new Date().getTime();
|
|
199
|
+
}
|
|
200
|
+
if (with_value) {
|
|
201
|
+
keyRes.val = await this.getValueByKey(
|
|
202
|
+
currentClient,
|
|
203
|
+
keyRes.getName(),
|
|
204
|
+
keyRes.type,
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
})(),
|
|
208
|
+
);
|
|
209
|
+
});
|
|
210
|
+
(async (): Promise<void> => {
|
|
211
|
+
await Promise.all(parallels);
|
|
212
|
+
progressCallback('Key scanning done.', 100);
|
|
213
|
+
resolve(ret);
|
|
214
|
+
})();
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
async getValueByKey(
|
|
219
|
+
client: Redis,
|
|
220
|
+
key: string,
|
|
221
|
+
type: RedisKeyType,
|
|
222
|
+
): Promise<any> {
|
|
223
|
+
switch (type) {
|
|
224
|
+
case RedisKeyType.string:
|
|
225
|
+
return await client.get(key);
|
|
226
|
+
case RedisKeyType.list:
|
|
227
|
+
return await client.lrange(key, 0, -1);
|
|
228
|
+
case RedisKeyType.set:
|
|
229
|
+
return await client.smembers(key);
|
|
230
|
+
case RedisKeyType.zset:
|
|
231
|
+
return await client.zrange(key, 0, -1);
|
|
232
|
+
case RedisKeyType.hash:
|
|
233
|
+
return await client.hgetall(key);
|
|
234
|
+
default:
|
|
235
|
+
console.log('whattype??', type);
|
|
236
|
+
}
|
|
237
|
+
return undefined;
|
|
238
|
+
}
|
|
239
|
+
async getResouces(options: {
|
|
240
|
+
progress_callback?: Function | undefined;
|
|
241
|
+
params?: any;
|
|
242
|
+
}): Promise<Array<DbResource>> {
|
|
243
|
+
if (!this.conRes) {
|
|
244
|
+
return [];
|
|
245
|
+
}
|
|
246
|
+
const dbResources = new Array<DbResource>();
|
|
247
|
+
|
|
248
|
+
const keyspace = await this.client.info('keyspace');
|
|
249
|
+
// db0:keys=7,expires=0,avg_ttl=0
|
|
250
|
+
// db3:keys=1,expires=1,avg_ttl=4996199
|
|
251
|
+
const re = /db([0-9]+):keys=([0-9]+),expires=([0-9]+),avg_ttl=([0-9]+)/g;
|
|
252
|
+
let m: string[];
|
|
253
|
+
while ((m = re.exec(keyspace))) {
|
|
254
|
+
const db = m[1];
|
|
255
|
+
const keys = parseInt(m[2], 10);
|
|
256
|
+
const expires = parseInt(m[3], 10);
|
|
257
|
+
const avg_ttl = parseInt(m[4], 10);
|
|
258
|
+
const dbRes = new RedisDatabase(db, keys, expires, avg_ttl);
|
|
259
|
+
dbResources.push(dbRes);
|
|
260
|
+
}
|
|
261
|
+
for (let i = 0; i < this.databases; i++) {
|
|
262
|
+
const name = `${i}`;
|
|
263
|
+
if (!dbResources.some((r) => r.getName() === name)) {
|
|
264
|
+
const dbRes = new RedisDatabase(name, 0, 0, 0);
|
|
265
|
+
dbResources.push(dbRes);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return dbResources;
|
|
270
|
+
}
|
|
271
|
+
async closeSub(): Promise<string> {
|
|
272
|
+
if (this.client) {
|
|
273
|
+
await this.client.quit();
|
|
274
|
+
}
|
|
275
|
+
return '';
|
|
276
|
+
}
|
|
277
|
+
}
|