@proteinjs/db 1.0.2
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/CHANGELOG.md +16 -0
- package/LICENSE +21 -0
- package/dist/generated/index.d.ts +11 -0
- package/dist/generated/index.d.ts.map +1 -0
- package/dist/generated/index.js +59 -0
- package/dist/generated/index.js.map +1 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +37 -0
- package/dist/index.js.map +1 -0
- package/dist/src/Columns.d.ts +100 -0
- package/dist/src/Columns.d.ts.map +1 -0
- package/dist/src/Columns.js +381 -0
- package/dist/src/Columns.js.map +1 -0
- package/dist/src/Db.d.ts +46 -0
- package/dist/src/Db.d.ts.map +1 -0
- package/dist/src/Db.js +393 -0
- package/dist/src/Db.js.map +1 -0
- package/dist/src/MigrationRunner.d.ts +10 -0
- package/dist/src/MigrationRunner.d.ts.map +1 -0
- package/dist/src/MigrationRunner.js +124 -0
- package/dist/src/MigrationRunner.js.map +1 -0
- package/dist/src/QueryBuilderFactory.d.ts +8 -0
- package/dist/src/QueryBuilderFactory.d.ts.map +1 -0
- package/dist/src/QueryBuilderFactory.js +21 -0
- package/dist/src/QueryBuilderFactory.js.map +1 -0
- package/dist/src/Record.d.ts +38 -0
- package/dist/src/Record.d.ts.map +1 -0
- package/dist/src/Record.js +209 -0
- package/dist/src/Record.js.map +1 -0
- package/dist/src/RecordIterator.d.ts +13 -0
- package/dist/src/RecordIterator.d.ts.map +1 -0
- package/dist/src/RecordIterator.js +98 -0
- package/dist/src/RecordIterator.js.map +1 -0
- package/dist/src/StatementConfigFactory.d.ts +11 -0
- package/dist/src/StatementConfigFactory.d.ts.map +1 -0
- package/dist/src/StatementConfigFactory.js +31 -0
- package/dist/src/StatementConfigFactory.js.map +1 -0
- package/dist/src/Table.d.ts +98 -0
- package/dist/src/Table.d.ts.map +1 -0
- package/dist/src/Table.js +56 -0
- package/dist/src/Table.js.map +1 -0
- package/dist/src/auth/TableAuth.d.ts +29 -0
- package/dist/src/auth/TableAuth.d.ts.map +1 -0
- package/dist/src/auth/TableAuth.js +45 -0
- package/dist/src/auth/TableAuth.js.map +1 -0
- package/dist/src/auth/TableServiceAuth.d.ts +5 -0
- package/dist/src/auth/TableServiceAuth.d.ts.map +1 -0
- package/dist/src/auth/TableServiceAuth.js +37 -0
- package/dist/src/auth/TableServiceAuth.js.map +1 -0
- package/dist/src/opt/moment.d.ts +4 -0
- package/dist/src/opt/moment.d.ts.map +1 -0
- package/dist/src/opt/moment.js +9 -0
- package/dist/src/opt/moment.js.map +1 -0
- package/dist/src/reference/Reference.d.ts +26 -0
- package/dist/src/reference/Reference.d.ts.map +1 -0
- package/dist/src/reference/Reference.js +90 -0
- package/dist/src/reference/Reference.js.map +1 -0
- package/dist/src/reference/ReferenceArray.d.ts +29 -0
- package/dist/src/reference/ReferenceArray.d.ts.map +1 -0
- package/dist/src/reference/ReferenceArray.js +101 -0
- package/dist/src/reference/ReferenceArray.js.map +1 -0
- package/dist/src/schema/SchemaMetadata.d.ts +27 -0
- package/dist/src/schema/SchemaMetadata.d.ts.map +1 -0
- package/dist/src/schema/SchemaMetadata.js +227 -0
- package/dist/src/schema/SchemaMetadata.js.map +1 -0
- package/dist/src/schema/SchemaOperations.d.ts +40 -0
- package/dist/src/schema/SchemaOperations.d.ts.map +1 -0
- package/dist/src/schema/SchemaOperations.js +3 -0
- package/dist/src/schema/SchemaOperations.js.map +1 -0
- package/dist/src/schema/TableManager.d.ts +21 -0
- package/dist/src/schema/TableManager.d.ts.map +1 -0
- package/dist/src/schema/TableManager.js +254 -0
- package/dist/src/schema/TableManager.js.map +1 -0
- package/dist/src/serializers/GraphSerializer.d.ts +13 -0
- package/dist/src/serializers/GraphSerializer.d.ts.map +1 -0
- package/dist/src/serializers/GraphSerializer.js +21 -0
- package/dist/src/serializers/GraphSerializer.js.map +1 -0
- package/dist/src/serializers/QueryBuilderSerializer.d.ts +19 -0
- package/dist/src/serializers/QueryBuilderSerializer.d.ts.map +1 -0
- package/dist/src/serializers/QueryBuilderSerializer.js +32 -0
- package/dist/src/serializers/QueryBuilderSerializer.js.map +1 -0
- package/dist/src/serializers/ReferenceArraySerializer.d.ts +15 -0
- package/dist/src/serializers/ReferenceArraySerializer.d.ts.map +1 -0
- package/dist/src/serializers/ReferenceArraySerializer.js +23 -0
- package/dist/src/serializers/ReferenceArraySerializer.js.map +1 -0
- package/dist/src/serializers/ReferenceSerializer.d.ts +15 -0
- package/dist/src/serializers/ReferenceSerializer.d.ts.map +1 -0
- package/dist/src/serializers/ReferenceSerializer.js +23 -0
- package/dist/src/serializers/ReferenceSerializer.js.map +1 -0
- package/dist/src/serializers/TableSerializer.d.ts +13 -0
- package/dist/src/serializers/TableSerializer.d.ts.map +1 -0
- package/dist/src/serializers/TableSerializer.js +19 -0
- package/dist/src/serializers/TableSerializer.js.map +1 -0
- package/dist/src/services/DbService.d.ts +19 -0
- package/dist/src/services/DbService.d.ts.map +1 -0
- package/dist/src/services/DbService.js +6 -0
- package/dist/src/services/DbService.js.map +1 -0
- package/dist/src/services/MigrationRunnerService.d.ts +6 -0
- package/dist/src/services/MigrationRunnerService.d.ts.map +1 -0
- package/dist/src/services/MigrationRunnerService.js +6 -0
- package/dist/src/services/MigrationRunnerService.js.map +1 -0
- package/dist/src/source/SourceRecord.d.ts +44 -0
- package/dist/src/source/SourceRecord.d.ts.map +1 -0
- package/dist/src/source/SourceRecord.js +49 -0
- package/dist/src/source/SourceRecord.js.map +1 -0
- package/dist/src/source/SourceRecordLoader.d.ts +6 -0
- package/dist/src/source/SourceRecordLoader.d.ts.map +1 -0
- package/dist/src/source/SourceRecordLoader.js +156 -0
- package/dist/src/source/SourceRecordLoader.js.map +1 -0
- package/dist/src/source/SourceRecordRepo.d.ts +8 -0
- package/dist/src/source/SourceRecordRepo.d.ts.map +1 -0
- package/dist/src/source/SourceRecordRepo.js +20 -0
- package/dist/src/source/SourceRecordRepo.js.map +1 -0
- package/dist/src/tables/MigrationTable.d.ts +41 -0
- package/dist/src/tables/MigrationTable.d.ts.map +1 -0
- package/dist/src/tables/MigrationTable.js +86 -0
- package/dist/src/tables/MigrationTable.js.map +1 -0
- package/dist/src/tables/tables.d.ts +6 -0
- package/dist/src/tables/tables.d.ts.map +1 -0
- package/dist/src/tables/tables.js +8 -0
- package/dist/src/tables/tables.js.map +1 -0
- package/dist/test/CrudTests.d.ts +20 -0
- package/dist/test/CrudTests.d.ts.map +1 -0
- package/dist/test/CrudTests.js +251 -0
- package/dist/test/CrudTests.js.map +1 -0
- package/dist/test/TableManagerTests.d.ts +8 -0
- package/dist/test/TableManagerTests.d.ts.map +1 -0
- package/dist/test/TableManagerTests.js +687 -0
- package/dist/test/TableManagerTests.js.map +1 -0
- package/generated/index.ts +55 -0
- package/index.ts +25 -0
- package/jest.config.js +18 -0
- package/package.json +45 -0
- package/src/Columns.ts +257 -0
- package/src/Db.ts +195 -0
- package/src/MigrationRunner.ts +62 -0
- package/src/QueryBuilderFactory.ts +18 -0
- package/src/Record.ts +113 -0
- package/src/RecordIterator.ts +44 -0
- package/src/StatementConfigFactory.ts +36 -0
- package/src/Table.ts +124 -0
- package/src/auth/TableAuth.ts +60 -0
- package/src/auth/TableServiceAuth.ts +32 -0
- package/src/opt/moment.ts +4 -0
- package/src/reference/Reference.ts +45 -0
- package/src/reference/ReferenceArray.ts +56 -0
- package/src/schema/SchemaMetadata.ts +115 -0
- package/src/schema/SchemaOperations.ts +44 -0
- package/src/schema/TableManager.ts +198 -0
- package/src/serializers/GraphSerializer.ts +22 -0
- package/src/serializers/QueryBuilderSerializer.ts +39 -0
- package/src/serializers/ReferenceArraySerializer.ts +26 -0
- package/src/serializers/ReferenceSerializer.ts +26 -0
- package/src/serializers/TableSerializer.ts +20 -0
- package/src/services/DbService.ts +19 -0
- package/src/services/MigrationRunnerService.ts +7 -0
- package/src/source/SourceRecord.ts +75 -0
- package/src/source/SourceRecordLoader.ts +68 -0
- package/src/source/SourceRecordRepo.ts +17 -0
- package/src/tables/MigrationTable.ts +36 -0
- package/src/tables/tables.ts +6 -0
- package/test/CrudTests.ts +110 -0
- package/test/TableManagerTests.ts +382 -0
- package/tsconfig.json +23 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Logger } from '@proteinjs/util';
|
|
2
|
+
import { Table } from '../Table';
|
|
3
|
+
import { TableAuth } from './TableAuth';
|
|
4
|
+
|
|
5
|
+
export class TableServiceAuth {
|
|
6
|
+
private logger = new Logger(this.constructor.name);
|
|
7
|
+
|
|
8
|
+
canAccess(methodName: string, args: any[]): boolean {
|
|
9
|
+
try {
|
|
10
|
+
const table: Table<any>|undefined = args[0];
|
|
11
|
+
if (!(table instanceof Table))
|
|
12
|
+
throw new Error(`[DbServiceAuth] Expected first arg to be a table`);
|
|
13
|
+
|
|
14
|
+
const tableAuth = new TableAuth();
|
|
15
|
+
if (methodName === 'get' || methodName == 'query' || methodName == 'getRowCount')
|
|
16
|
+
tableAuth.canQuery(table, 'service');
|
|
17
|
+
else if (methodName === 'insert')
|
|
18
|
+
tableAuth.canInsert(table, 'service');
|
|
19
|
+
else if (methodName === 'update')
|
|
20
|
+
tableAuth.canUpdate(table, 'service');
|
|
21
|
+
else if (methodName === 'delete')
|
|
22
|
+
tableAuth.canDelete(table, 'service');
|
|
23
|
+
else
|
|
24
|
+
throw new Error(`User is not authorized to access unsupported Db service api: ${methodName}`);
|
|
25
|
+
} catch (error: any) {
|
|
26
|
+
this.logger.error(error.stack);
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { CustomSerializableObject } from '@proteinjs/serializer';
|
|
2
|
+
import { getDb } from '../Db';
|
|
3
|
+
import { Record } from '../Record';
|
|
4
|
+
import { tableByName } from '../Table';
|
|
5
|
+
import { ReferenceSerializerId } from '../serializers/ReferenceSerializer';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The object returned by Db functions for each field of type ReferenceColumn in a record.
|
|
9
|
+
* The reason for this is to make loading of reference records on-demand. For theoretically
|
|
10
|
+
* infinitely-nested references, this is necessary. For everything else, this is optimal.
|
|
11
|
+
*
|
|
12
|
+
* Note: updating the reference via set does not automatically persist those changes in the db.
|
|
13
|
+
* You still need to call Db.update on the record as you would when changing any other field on the record.
|
|
14
|
+
*
|
|
15
|
+
* Note: all instance members are internal state, made public for ReferenceArraySerializer.
|
|
16
|
+
* Only get() and set() should be used.
|
|
17
|
+
*/
|
|
18
|
+
export class Reference<T extends Record> implements CustomSerializableObject {
|
|
19
|
+
public __serializerId = ReferenceSerializerId;
|
|
20
|
+
|
|
21
|
+
constructor(
|
|
22
|
+
public _table: string,
|
|
23
|
+
public _id?: string,
|
|
24
|
+
public _object?: T,
|
|
25
|
+
) {}
|
|
26
|
+
|
|
27
|
+
static fromObject<T extends Record>(table: string, object: T|(Partial<T> & { id: string })) {
|
|
28
|
+
return new Reference<T>(table, object.id, object as T);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async get(): Promise<T|undefined> {
|
|
32
|
+
if (!this._object && this._id) {
|
|
33
|
+
const table = tableByName(this._table);
|
|
34
|
+
const db = getDb();
|
|
35
|
+
this._object = await db.get(table, { id: this._id });
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return this._object;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
set(object: T) {
|
|
42
|
+
this._object = object;
|
|
43
|
+
this._id = object.id;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { CustomSerializableObject } from '@proteinjs/serializer';
|
|
2
|
+
import { getDb } from '../Db';
|
|
3
|
+
import { Record } from '../Record';
|
|
4
|
+
import { tableByName } from '../Table';
|
|
5
|
+
import { ReferenceArraySerializerId } from '../serializers/ReferenceArraySerializer';
|
|
6
|
+
import { QueryBuilderFactory } from '../QueryBuilderFactory';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The object returned by Db functions for each field of type ReferenceArrayColumn in a record.
|
|
10
|
+
* The reason for this is to make loading of reference records on-demand. For theoretically
|
|
11
|
+
* infinitely-nested references, this is necessary. For everything else, this is optimal.
|
|
12
|
+
*
|
|
13
|
+
* Note: generally only get() needs to be called. Array operations performed on the returned array will be
|
|
14
|
+
* persisted in ReferenceArray.
|
|
15
|
+
*
|
|
16
|
+
* Note: updating items in the returned array does not automatically persist those changes in the db.
|
|
17
|
+
* You still need to call Db.update on the record as you would when changing any other field on the record.
|
|
18
|
+
*
|
|
19
|
+
* Note: all instance members are internal state, made public for ReferenceArraySerializer.
|
|
20
|
+
* Only get() and set() should be used.
|
|
21
|
+
*/
|
|
22
|
+
export class ReferenceArray<T extends Record> implements CustomSerializableObject {
|
|
23
|
+
public __serializerId = ReferenceArraySerializerId;
|
|
24
|
+
|
|
25
|
+
constructor(
|
|
26
|
+
public _table: string,
|
|
27
|
+
public _ids: string[],
|
|
28
|
+
public _objects?: T[],
|
|
29
|
+
) {}
|
|
30
|
+
|
|
31
|
+
static fromObjects<T extends Record>(table: string, objects: (T|(Partial<T> & { id: string }))[]) {
|
|
32
|
+
const ids = objects.map(object => object.id);
|
|
33
|
+
return new ReferenceArray<T>(table, ids, objects as T[]);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async get(): Promise<T[]> {
|
|
37
|
+
if (!this._objects) {
|
|
38
|
+
if (this._ids.length < 1) {
|
|
39
|
+
this._objects = [];
|
|
40
|
+
} else {
|
|
41
|
+
const table = tableByName(this._table);
|
|
42
|
+
const db = getDb();
|
|
43
|
+
const qb = new QueryBuilderFactory().getQueryBuilder(table);
|
|
44
|
+
qb.condition({ field: 'id', operator: 'IN', value: this._ids });
|
|
45
|
+
qb.sort([{ field: 'id', byValues: this._ids }]);
|
|
46
|
+
this._objects = await db.query(table, qb);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return this._objects;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
set(objects: T[]) {
|
|
54
|
+
this._objects = objects;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { ParameterizationConfig, QueryBuilder } from '@proteinjs/db-query';
|
|
2
|
+
import { Table } from '../Table';
|
|
3
|
+
import { DbDriver } from '../Db';
|
|
4
|
+
|
|
5
|
+
export class SchemaMetadata {
|
|
6
|
+
constructor(
|
|
7
|
+
public dbDriver: DbDriver,
|
|
8
|
+
private multiDbSupported = true
|
|
9
|
+
){}
|
|
10
|
+
|
|
11
|
+
async tableExists(table: Table<any>): Promise<boolean> {
|
|
12
|
+
const qb = new QueryBuilder('TABLES').condition({ field: 'TABLE_NAME', operator: '=', value: table.name });
|
|
13
|
+
const generateStatement = (config: ParameterizationConfig) => qb.toSql({ dbName: 'INFORMATION_SCHEMA', ...config });
|
|
14
|
+
const results = await this.dbDriver.runQuery(generateStatement);
|
|
15
|
+
return results.length > 0;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async columnExists(columnName: string, table: Table<any>): Promise<boolean> {
|
|
19
|
+
const qb = QueryBuilder.fromObject({
|
|
20
|
+
'TABLE_SCHEMA': this.multiDbSupported ? this.dbDriver.getDbName() : undefined,
|
|
21
|
+
'TABLE_NAME': table.name,
|
|
22
|
+
'COLUMN_NAME': columnName,
|
|
23
|
+
}, 'COLUMNS');
|
|
24
|
+
const generateStatement = (config: ParameterizationConfig) => qb.toSql({ dbName: 'INFORMATION_SCHEMA', ...config });
|
|
25
|
+
const results = await this.dbDriver.runQuery(generateStatement);
|
|
26
|
+
return results.length > 0;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async getColumnMetadata(table: Table<any>): Promise<{[columnName: string]: { type: string, isNullable: boolean }}> {
|
|
30
|
+
const qb = QueryBuilder.fromObject({
|
|
31
|
+
'TABLE_SCHEMA': this.multiDbSupported ? this.dbDriver.getDbName() : undefined,
|
|
32
|
+
'TABLE_NAME': table.name
|
|
33
|
+
}, 'COLUMNS');
|
|
34
|
+
const generateStatement = (config: ParameterizationConfig) => qb.toSql({ dbName: 'INFORMATION_SCHEMA', ...config });
|
|
35
|
+
const results = await this.dbDriver.runQuery(generateStatement);
|
|
36
|
+
const columnMetadata: {[columnName: string]: { type: string, isNullable: boolean }} = {};
|
|
37
|
+
for (const row of results)
|
|
38
|
+
columnMetadata[row['COLUMN_NAME']] = { type: row['DATA_TYPE'], isNullable: row['IS_NULLABLE'] === 'YES' };
|
|
39
|
+
|
|
40
|
+
return columnMetadata;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async getPrimaryKey(table: Table<any>): Promise<string[]> {
|
|
44
|
+
const qb = QueryBuilder.fromObject({
|
|
45
|
+
'TABLE_SCHEMA': this.multiDbSupported ? this.dbDriver.getDbName() : undefined,
|
|
46
|
+
'TABLE_NAME': table.name,
|
|
47
|
+
'CONSTRAINT_NAME': 'PRIMARY',
|
|
48
|
+
}, 'KEY_COLUMN_USAGE');
|
|
49
|
+
const generateStatement = (config: ParameterizationConfig) => qb.toSql({ dbName: 'INFORMATION_SCHEMA' });
|
|
50
|
+
const results = await this.dbDriver.runQuery(generateStatement);
|
|
51
|
+
const primaryKey: string[] = [];
|
|
52
|
+
for (const row of results)
|
|
53
|
+
primaryKey.push(row['COLUMN_NAME']);
|
|
54
|
+
|
|
55
|
+
return primaryKey;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async getForeignKeys(table: Table<any>): Promise<{[columnName: string]: { referencedTableName: string, referencedColumnName: string }}> {
|
|
59
|
+
const qb = QueryBuilder.fromObject({
|
|
60
|
+
'TABLE_SCHEMA': this.multiDbSupported ? this.dbDriver.getDbName() : undefined,
|
|
61
|
+
'TABLE_NAME': table.name
|
|
62
|
+
}, 'KEY_COLUMN_USAGE');
|
|
63
|
+
const generateStatement = (config: ParameterizationConfig) => qb.toSql({ dbName: 'INFORMATION_SCHEMA', ...config });
|
|
64
|
+
const results = await this.dbDriver.runQuery(generateStatement);
|
|
65
|
+
const foreignKeys: {[columnName: string]: { referencedTableName: string, referencedColumnName: string }} = {};
|
|
66
|
+
for (const row of results) {
|
|
67
|
+
if (!row['REFERENCED_TABLE_NAME'])
|
|
68
|
+
continue;
|
|
69
|
+
|
|
70
|
+
foreignKeys[row['COLUMN_NAME']] = {
|
|
71
|
+
referencedTableName: row['REFERENCED_TABLE_NAME'],
|
|
72
|
+
referencedColumnName: row['REFERENCED_COLUMN_NAME'],
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return foreignKeys;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async getUniqueColumns(table: Table<any>): Promise<string[]> {
|
|
80
|
+
const qb = QueryBuilder.fromObject({
|
|
81
|
+
'TABLE_SCHEMA': this.multiDbSupported ? this.dbDriver.getDbName() : undefined,
|
|
82
|
+
'TABLE_NAME': table.name
|
|
83
|
+
}, 'KEY_COLUMN_USAGE');
|
|
84
|
+
const generateStatement = (config: ParameterizationConfig) => qb.toSql({ dbName: 'INFORMATION_SCHEMA', ...config });
|
|
85
|
+
const results = await this.dbDriver.runQuery(generateStatement);
|
|
86
|
+
const uniqueColumns: string[] = [];
|
|
87
|
+
for (const row of results) {
|
|
88
|
+
if (!(row['CONSTRAINT_NAME'] as string).endsWith('_unique'))
|
|
89
|
+
continue;
|
|
90
|
+
|
|
91
|
+
uniqueColumns.push(row['COLUMN_NAME']);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return uniqueColumns;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async getIndexes(table: Table<any>): Promise<{[keyName: string]: string[]}> {
|
|
98
|
+
const qb = new QueryBuilder('STATISTICS')
|
|
99
|
+
.condition({ field: 'TABLE_SCHEMA', operator: '=', value: this.dbDriver.getDbName() })
|
|
100
|
+
.condition({ field: 'TABLE_NAME', operator: '=', value: table.name })
|
|
101
|
+
.sort([{ field: 'SEQ_IN_INDEX', desc: false }])
|
|
102
|
+
;
|
|
103
|
+
const generateStatement = (config: ParameterizationConfig) => qb.toSql({ dbName: 'INFORMATION_SCHEMA', ...config });
|
|
104
|
+
const results: any[] = await this.dbDriver.runQuery(generateStatement);
|
|
105
|
+
const indexes: { [keyName: string]: string[] } = {};
|
|
106
|
+
for (const row of results) {
|
|
107
|
+
if (!indexes[row['INDEX_NAME']])
|
|
108
|
+
indexes[row['INDEX_NAME']] = [];
|
|
109
|
+
|
|
110
|
+
indexes[row['INDEX_NAME']].push(row['COLUMN_NAME']);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return indexes;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Table } from '../Table';
|
|
2
|
+
|
|
3
|
+
export interface SchemaOperations {
|
|
4
|
+
createTable(table: Table<any>): Promise<void>;
|
|
5
|
+
alterTable(table: Table<any>, changes: TableChanges): Promise<void>;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
interface Index {
|
|
9
|
+
name?: string;
|
|
10
|
+
columns: string|string[];
|
|
11
|
+
unique?: boolean;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface ForeignKey {
|
|
15
|
+
table: string; // table that is referenced
|
|
16
|
+
column: string; // column that is referenced
|
|
17
|
+
referencedByColumn: string; // column the constraint is applied to
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface ColumnTypeChange {
|
|
21
|
+
name: string;
|
|
22
|
+
newType: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface ColumnNullableChange {
|
|
26
|
+
name: string;
|
|
27
|
+
nullable: boolean;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface TableChanges {
|
|
31
|
+
columnsToCreate: string[];
|
|
32
|
+
columnsToRename: string[];
|
|
33
|
+
columnsToAlter: string[];
|
|
34
|
+
columnTypeChanges: ColumnTypeChange[];
|
|
35
|
+
columnNullableChanges: ColumnNullableChange[];
|
|
36
|
+
columnsWithForeignKeysToCreate: string[];
|
|
37
|
+
foreignKeysToCreate: ForeignKey[];
|
|
38
|
+
columnsWithForeignKeysToDrop: string[];
|
|
39
|
+
foreignKeysToDrop: ForeignKey[];
|
|
40
|
+
columnsWithUniqueConstraintsToCreate: string[];
|
|
41
|
+
columnsWithUniqueConstraintsToDrop: string[];
|
|
42
|
+
indexesToCreate: Index[];
|
|
43
|
+
indexesToDrop: Index[];
|
|
44
|
+
}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { Logger } from '@proteinjs/util';
|
|
2
|
+
import { Column, Table, getTables, tableByName } from '../Table';
|
|
3
|
+
import { SchemaOperations, TableChanges } from './SchemaOperations';
|
|
4
|
+
import { SchemaMetadata } from './SchemaMetadata';
|
|
5
|
+
import { DbDriver } from '../Db';
|
|
6
|
+
|
|
7
|
+
export interface ColumnTypeFactory {
|
|
8
|
+
getType(column: Column<any, any>): string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export class TableManager {
|
|
12
|
+
private logger = new Logger(this.constructor.name);
|
|
13
|
+
public columnTypeFactory: ColumnTypeFactory;
|
|
14
|
+
public schemaOperations: SchemaOperations;
|
|
15
|
+
public schemaMetadata: SchemaMetadata;
|
|
16
|
+
|
|
17
|
+
constructor(
|
|
18
|
+
dbDriver: DbDriver,
|
|
19
|
+
columnTypeFactory: ColumnTypeFactory,
|
|
20
|
+
schemaOperations: SchemaOperations,
|
|
21
|
+
schemaMetadata?: SchemaMetadata
|
|
22
|
+
){
|
|
23
|
+
this.columnTypeFactory = columnTypeFactory;
|
|
24
|
+
this.schemaOperations = schemaOperations;
|
|
25
|
+
this.schemaMetadata = schemaMetadata ? schemaMetadata : new SchemaMetadata(dbDriver);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async tableExists(table: Table<any>) {
|
|
29
|
+
return await this.schemaMetadata.tableExists(table);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async loadTables(): Promise<void> {
|
|
33
|
+
const tables = getTables();
|
|
34
|
+
for (const table of tables)
|
|
35
|
+
await this.loadTable(table);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async loadTable(table: Table<any>): Promise<void> {
|
|
39
|
+
if (await this.tableExists(table)) {
|
|
40
|
+
const tableChanges = await this.getTableChanges(table);
|
|
41
|
+
if (this.shouldAlterTable(tableChanges)) {
|
|
42
|
+
this.logger.info(`Altering table: ${table.name}`);
|
|
43
|
+
await this.schemaOperations.alterTable(table, tableChanges);
|
|
44
|
+
this.logger.info(`Finished altering table: ${table.name}`);
|
|
45
|
+
}
|
|
46
|
+
} else {
|
|
47
|
+
this.logger.info(`Creating table: ${table.name}`);
|
|
48
|
+
await this.schemaOperations.createTable(table);
|
|
49
|
+
this.logger.info(`Finished creating table: ${table.name}`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
private shouldAlterTable(tableChanges: TableChanges) {
|
|
54
|
+
if (
|
|
55
|
+
tableChanges.columnsToCreate.length == 0 &&
|
|
56
|
+
tableChanges.columnsToRename.length == 0 &&
|
|
57
|
+
tableChanges.columnsToAlter.length == 0 &&
|
|
58
|
+
tableChanges.columnsWithForeignKeysToDrop.length == 0 &&
|
|
59
|
+
tableChanges.columnsWithUniqueConstraintsToDrop.length == 0 &&
|
|
60
|
+
tableChanges.indexesToCreate.length == 0 &&
|
|
61
|
+
tableChanges.indexesToDrop.length == 0
|
|
62
|
+
)
|
|
63
|
+
return false;
|
|
64
|
+
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
private async getTableChanges(table: Table<any>) {
|
|
69
|
+
const { indexesToCreate, indexesToDrop } = await this.getIndexOperations(table);
|
|
70
|
+
const tableChanges: TableChanges = {
|
|
71
|
+
columnsToCreate: [],
|
|
72
|
+
columnsToRename: [],
|
|
73
|
+
columnsToAlter: [],
|
|
74
|
+
columnTypeChanges: [],
|
|
75
|
+
columnNullableChanges: [],
|
|
76
|
+
columnsWithForeignKeysToCreate: [],
|
|
77
|
+
foreignKeysToCreate: [],
|
|
78
|
+
columnsWithForeignKeysToDrop: [],
|
|
79
|
+
foreignKeysToDrop: [],
|
|
80
|
+
columnsWithUniqueConstraintsToCreate: [],
|
|
81
|
+
columnsWithUniqueConstraintsToDrop: [],
|
|
82
|
+
indexesToCreate,
|
|
83
|
+
indexesToDrop,
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const columnMetadata = await this.schemaMetadata.getColumnMetadata(table);
|
|
87
|
+
const uniqueColumns = await this.schemaMetadata.getUniqueColumns(table);
|
|
88
|
+
const foreignKeys = await this.schemaMetadata.getForeignKeys(table);
|
|
89
|
+
for (const columnPropertyName in table.columns) {
|
|
90
|
+
const column = table.columns[columnPropertyName];
|
|
91
|
+
if (columnMetadata[column.name]) {
|
|
92
|
+
let alter = false;
|
|
93
|
+
const columnType = this.columnTypeFactory.getType(column);
|
|
94
|
+
const existingColumnType = columnMetadata[column.name].type;
|
|
95
|
+
if (columnType != existingColumnType) {
|
|
96
|
+
// console.log(`columnType != existingColumnType`);
|
|
97
|
+
tableChanges.columnTypeChanges.push({ name: column.name, newType: columnType });
|
|
98
|
+
alter = true;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (
|
|
102
|
+
(column.options?.nullable && !columnMetadata[column.name].isNullable) ||
|
|
103
|
+
(column.options?.nullable === false && columnMetadata[column.name].isNullable)
|
|
104
|
+
) {
|
|
105
|
+
// console.log(`column.options?.nullable`)
|
|
106
|
+
tableChanges.columnNullableChanges.push({ name: column.name, nullable: column.options.nullable === true });
|
|
107
|
+
alter = true;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (
|
|
111
|
+
column.options?.unique?.unique === false && uniqueColumns.includes(column.name)
|
|
112
|
+
) {
|
|
113
|
+
// console.log(`column.options?.unique?.unique`)
|
|
114
|
+
tableChanges.columnsWithUniqueConstraintsToDrop.push(column.name);
|
|
115
|
+
alter = true;
|
|
116
|
+
} else if (
|
|
117
|
+
column.options?.unique?.unique && !uniqueColumns.includes(column.name)
|
|
118
|
+
) {
|
|
119
|
+
tableChanges.columnsWithUniqueConstraintsToCreate.push(column.name);
|
|
120
|
+
alter = true;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (
|
|
124
|
+
!column.options?.references && foreignKeys[column.name] ||
|
|
125
|
+
column.options?.references && foreignKeys[column.name] && foreignKeys[column.name].referencedTableName != column.options.references.table
|
|
126
|
+
) {
|
|
127
|
+
// console.log(`column.options?.references`)
|
|
128
|
+
tableChanges.columnsWithForeignKeysToDrop.push(column.name);
|
|
129
|
+
tableChanges.foreignKeysToDrop.push({ table: foreignKeys[column.name].referencedTableName, column: foreignKeys[column.name].referencedColumnName, referencedByColumn: column.name });
|
|
130
|
+
alter = true;
|
|
131
|
+
} else if (
|
|
132
|
+
column.options?.references && !foreignKeys[column.name]
|
|
133
|
+
) {
|
|
134
|
+
tableChanges.columnsWithForeignKeysToCreate.push(column.name);
|
|
135
|
+
tableChanges.foreignKeysToCreate.push({ table: column.options.references.table, column: 'id', referencedByColumn: column.name });
|
|
136
|
+
alter = true;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (alter)
|
|
140
|
+
tableChanges.columnsToAlter.push(columnPropertyName);
|
|
141
|
+
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (column.oldName && columnMetadata[column.oldName]) {
|
|
146
|
+
tableChanges.columnsToRename.push(columnPropertyName);
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
tableChanges.columnsToCreate.push(columnPropertyName);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return tableChanges;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
private async getIndexOperations(table: Table<any>) {
|
|
157
|
+
const existingIndexes = await this.schemaMetadata.getIndexes(table);
|
|
158
|
+
const indexesToDrop: {
|
|
159
|
+
name?: string;
|
|
160
|
+
columns: string|string[];
|
|
161
|
+
unique?: boolean;
|
|
162
|
+
}[] = [];
|
|
163
|
+
const indexesToCreate: {
|
|
164
|
+
name?: string;
|
|
165
|
+
columns: string|string[];
|
|
166
|
+
unique?: boolean;
|
|
167
|
+
}[] = [];
|
|
168
|
+
const currentIndexMap: {[serializedColumns: string]: boolean} = {};
|
|
169
|
+
const existingIndexMap: {[serializedColumns: string]: boolean} = {};
|
|
170
|
+
for (const keyName in existingIndexes)
|
|
171
|
+
existingIndexMap[JSON.stringify(existingIndexes[keyName])] = true;
|
|
172
|
+
|
|
173
|
+
if (table.indexes) {
|
|
174
|
+
for (const index of table.indexes) {
|
|
175
|
+
const serializedColumns = JSON.stringify(index.columns);
|
|
176
|
+
currentIndexMap[serializedColumns] = true;
|
|
177
|
+
if (!existingIndexMap[serializedColumns])
|
|
178
|
+
indexesToCreate.push({ name: index.name, columns: index.columns as string[] });
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
for (const keyName in existingIndexes) {
|
|
183
|
+
const existingIndex = existingIndexes[keyName];
|
|
184
|
+
const serializedColumns = JSON.stringify(existingIndex);
|
|
185
|
+
if (
|
|
186
|
+
!currentIndexMap[serializedColumns] &&
|
|
187
|
+
keyName != 'PRIMARY' &&
|
|
188
|
+
keyName != 'PRIMARY_KEY' &&
|
|
189
|
+
!keyName.endsWith('_unique') &&
|
|
190
|
+
!keyName.endsWith('_foreign') &&
|
|
191
|
+
!keyName.startsWith('IDX_')
|
|
192
|
+
)
|
|
193
|
+
indexesToDrop.push({ name: keyName, columns: existingIndex });
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return { indexesToCreate, indexesToDrop };
|
|
197
|
+
}
|
|
198
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { ThirdPartyLibCustomSerializer } from '@proteinjs/serializer';
|
|
2
|
+
import { graphSerializer, Graph } from '@proteinjs/util';
|
|
3
|
+
|
|
4
|
+
type SerializedGraph = {
|
|
5
|
+
value: string,
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export class GraphSerializer implements ThirdPartyLibCustomSerializer {
|
|
9
|
+
id = '@proteinjs/serializer/GraphSerializer';
|
|
10
|
+
|
|
11
|
+
matches(obj: any) {
|
|
12
|
+
return obj instanceof Graph;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
serialize(graph: Graph): SerializedGraph {
|
|
16
|
+
return { value: graphSerializer.serialize(graph) };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
deserialize(serializedGraph: SerializedGraph): Graph {
|
|
20
|
+
return graphSerializer.deserialize(serializedGraph.value);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { CustomSerializer } from '@proteinjs/serializer';
|
|
2
|
+
import { Graph } from '@proteinjs/util';
|
|
3
|
+
import { QueryBuilder } from '@proteinjs/db-query';
|
|
4
|
+
|
|
5
|
+
type SerializedQueryBuilder = {
|
|
6
|
+
tableName: string,
|
|
7
|
+
graph: Graph,
|
|
8
|
+
idCounter: number,
|
|
9
|
+
rootId: string,
|
|
10
|
+
currentContextIds: string[],
|
|
11
|
+
paginationNodeId?: string,
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const QueryBuilderSerializerId = '@proteinjs/db/QueryBuilderSerializer';
|
|
15
|
+
|
|
16
|
+
export class QueryBuilderSerializer implements CustomSerializer {
|
|
17
|
+
id = QueryBuilderSerializerId;
|
|
18
|
+
|
|
19
|
+
serialize(qb: QueryBuilder<any>): SerializedQueryBuilder {
|
|
20
|
+
return {
|
|
21
|
+
tableName: qb.tableName,
|
|
22
|
+
graph: qb.graph,
|
|
23
|
+
idCounter: qb.idCounter,
|
|
24
|
+
rootId: qb.rootId,
|
|
25
|
+
currentContextIds: qb.currentContextIds,
|
|
26
|
+
paginationNodeId: qb.paginationNodeId,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
deserialize(serializedQb: SerializedQueryBuilder): QueryBuilder<any> {
|
|
31
|
+
const qb = new QueryBuilder(serializedQb.tableName);
|
|
32
|
+
qb.graph = serializedQb.graph;
|
|
33
|
+
qb.idCounter = serializedQb.idCounter;
|
|
34
|
+
qb.rootId = serializedQb.rootId;
|
|
35
|
+
qb.currentContextIds = serializedQb.currentContextIds;
|
|
36
|
+
qb.paginationNodeId = serializedQb.paginationNodeId;
|
|
37
|
+
return qb;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { CustomSerializer } from '@proteinjs/serializer';
|
|
2
|
+
import { ReferenceArray } from '../reference/ReferenceArray';
|
|
3
|
+
|
|
4
|
+
type SerializedReferenceArray = {
|
|
5
|
+
_table: string,
|
|
6
|
+
_ids: string[],
|
|
7
|
+
_objects: any,
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const ReferenceArraySerializerId = '@proteinjs/db/ReferenceArraySerializer';
|
|
11
|
+
|
|
12
|
+
export class ReferenceArraySerializer implements CustomSerializer {
|
|
13
|
+
id = ReferenceArraySerializerId;
|
|
14
|
+
|
|
15
|
+
serialize(referenceArray: ReferenceArray<any>): SerializedReferenceArray {
|
|
16
|
+
return {
|
|
17
|
+
_table: referenceArray._table,
|
|
18
|
+
_ids: referenceArray._ids,
|
|
19
|
+
_objects: referenceArray._objects,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
deserialize(serializedReferenceArray: SerializedReferenceArray): ReferenceArray<any> {
|
|
24
|
+
return new ReferenceArray(serializedReferenceArray._table, serializedReferenceArray._ids, serializedReferenceArray._objects);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { CustomSerializer } from '@proteinjs/serializer';
|
|
2
|
+
import { Reference } from '../reference/Reference';
|
|
3
|
+
|
|
4
|
+
type SerializedReference = {
|
|
5
|
+
_table: string,
|
|
6
|
+
_id?: string,
|
|
7
|
+
_object?: any,
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const ReferenceSerializerId = '@proteinjs/db/ReferenceSerializer';
|
|
11
|
+
|
|
12
|
+
export class ReferenceSerializer implements CustomSerializer {
|
|
13
|
+
id = ReferenceSerializerId;
|
|
14
|
+
|
|
15
|
+
serialize(reference: Reference<any>): SerializedReference {
|
|
16
|
+
return {
|
|
17
|
+
_table: reference._table,
|
|
18
|
+
_id: reference._id,
|
|
19
|
+
_object: reference._object,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
deserialize(serializedReference: SerializedReference): Reference<any> {
|
|
24
|
+
return new Reference(serializedReference._table, serializedReference._id, serializedReference._object);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { CustomSerializer } from '@proteinjs/serializer';
|
|
2
|
+
import { Table, tableByName } from '../Table';
|
|
3
|
+
|
|
4
|
+
type SerializedTable = {
|
|
5
|
+
tableName: string,
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const TableSerializerId = '@proteinjs/db/TableSerializer';
|
|
9
|
+
|
|
10
|
+
export class TableSerializer implements CustomSerializer {
|
|
11
|
+
id = TableSerializerId;
|
|
12
|
+
|
|
13
|
+
serialize(table: Table<any>): SerializedTable {
|
|
14
|
+
return { tableName: table.name };
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
deserialize(serializedTable: SerializedTable): Table<any> {
|
|
18
|
+
return tableByName(serializedTable.tableName);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Service, serviceFactory } from '@proteinjs/service';
|
|
2
|
+
import { Table } from '../Table';
|
|
3
|
+
import { Record } from '../Record';
|
|
4
|
+
import { QueryBuilder } from '@proteinjs/db-query';
|
|
5
|
+
|
|
6
|
+
export const getDbService = serviceFactory<DbService>('@proteinjs/db/DbService');
|
|
7
|
+
|
|
8
|
+
export type Query<T> = ObjectQuery<T> | QueryBuilder<T>
|
|
9
|
+
export type ObjectQuery<T> = Partial<{[P in keyof T]: any}>
|
|
10
|
+
|
|
11
|
+
export interface DbService<R extends Record = Record> extends Service {
|
|
12
|
+
tableExists<T extends R>(table: Table<T>): Promise<boolean>;
|
|
13
|
+
get<T extends R>(table: Table<T>, query: Query<T>): Promise<T>;
|
|
14
|
+
insert<T extends R>(table: Table<T>, record: Omit<T, keyof R>): Promise<T>;
|
|
15
|
+
update<T extends R>(table: Table<T>, record: Partial<T>, query?: Query<T>): Promise<number>;
|
|
16
|
+
delete<T extends R>(table: Table<T>, query: Query<T>): Promise<number>;
|
|
17
|
+
query<T extends R>(table: Table<T>, query: Query<T>): Promise<T[]>;
|
|
18
|
+
getRowCount<T extends R>(table: Table<T>, query?: Query<T>): Promise<number>;
|
|
19
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { Service, serviceFactory } from '@proteinjs/service';
|
|
2
|
+
|
|
3
|
+
export const getMigrationRunnerService = serviceFactory<MigrationRunnerService>('@proteinjs/db/MigrationRunnerService');
|
|
4
|
+
|
|
5
|
+
export interface MigrationRunnerService extends Service {
|
|
6
|
+
runMigration(id: string): Promise<void>;
|
|
7
|
+
}
|