@sqb/connect 4.2.1 → 4.5.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 +67 -0
- package/{dist/client/Adapter.js → cjs/client/adapter.js} +0 -0
- package/{dist/client/CursorStream.js → cjs/client/cursor-stream.js} +5 -2
- package/{dist/client/Cursor.js → cjs/client/cursor.js} +18 -11
- package/{dist → cjs}/client/extensions.js +0 -0
- package/{dist/client/FieldInfoMap.js → cjs/client/field-info-map.js} +2 -0
- package/{dist → cjs}/client/helpers.js +2 -2
- package/{dist/client/SqbClient.js → cjs/client/sqb-client.js} +16 -10
- package/{dist/client/SqbConnection.js → cjs/client/sqb-connection.js} +17 -14
- package/{dist → cjs}/client/types.js +0 -0
- package/cjs/index.js +42 -0
- package/{dist → cjs}/orm/backward.js +8 -8
- package/{dist → cjs}/orm/base-entity.js +8 -7
- package/{dist → cjs}/orm/commands/command.helper.js +9 -9
- package/{dist → cjs}/orm/commands/count.command.js +2 -2
- package/{dist → cjs}/orm/commands/create.command.js +12 -12
- package/{dist → cjs}/orm/commands/destroy.command.js +2 -2
- package/{dist → cjs}/orm/commands/find.command.js +100 -88
- package/{dist → cjs}/orm/commands/row-converter.js +10 -7
- package/{dist → cjs}/orm/commands/update.command.js +10 -10
- package/{dist → cjs}/orm/decorators/column.decorator.js +3 -3
- package/{dist → cjs}/orm/decorators/embedded.decorator.js +3 -3
- package/cjs/orm/decorators/entity.decorator.js +179 -0
- package/{dist → cjs}/orm/decorators/events.decorator.js +13 -13
- package/{dist → cjs}/orm/decorators/foreignkey.decorator.js +3 -3
- package/{dist → cjs}/orm/decorators/index.decorator.js +5 -5
- package/cjs/orm/decorators/link.decorator.js +70 -0
- package/{dist → cjs}/orm/decorators/primarykey.decorator.js +7 -7
- package/{dist → cjs}/orm/decorators/transform.decorator.js +5 -5
- package/cjs/orm/model/association-field-metadata.js +24 -0
- package/{dist → cjs}/orm/model/association-node.js +5 -2
- package/{dist → cjs}/orm/model/association.js +31 -39
- package/{dist/orm/model/column-element-metadata.js → cjs/orm/model/column-field-metadata.js} +8 -8
- package/{dist/orm/model/embedded-element-metadata.js → cjs/orm/model/embedded-field-metadata.js} +8 -8
- package/{dist → cjs}/orm/model/entity-metadata.js +90 -91
- package/{dist/orm/model/element-metadata.js → cjs/orm/model/field-metadata.js} +0 -0
- package/{dist → cjs}/orm/model/index-metadata.js +0 -0
- package/{dist → cjs}/orm/model/link-chain.js +12 -15
- package/{dist → cjs}/orm/orm.const.js +0 -0
- package/{dist → cjs}/orm/orm.type.js +0 -0
- package/{dist → cjs}/orm/repository.class.js +25 -22
- package/{dist → cjs}/orm/util/apply-mixins.js +0 -0
- package/{dist → cjs}/orm/util/extract-keyvalues.js +5 -5
- package/{dist → cjs}/orm/util/orm.helper.js +10 -10
- package/{dist/orm/util/serialize-element.js → cjs/orm/util/serialize-field.js} +2 -2
- package/cjs/package.json +3 -0
- package/{dist → cjs}/types.js +0 -0
- package/{dist/client/Adapter.d.ts → esm/client/adapter.d.ts} +1 -1
- package/esm/client/adapter.js +1 -0
- package/{dist/client/CursorStream.d.ts → esm/client/cursor-stream.d.ts} +1 -1
- package/esm/client/cursor-stream.js +96 -0
- package/{dist/client/Cursor.d.ts → esm/client/cursor.d.ts} +5 -5
- package/esm/client/cursor.js +252 -0
- package/{dist → esm}/client/extensions.d.ts +1 -1
- package/esm/client/extensions.js +9 -0
- package/{dist/client/FieldInfoMap.d.ts → esm/client/field-info-map.d.ts} +1 -1
- package/esm/client/field-info-map.js +50 -0
- package/{dist → esm}/client/helpers.d.ts +3 -3
- package/esm/client/helpers.js +125 -0
- package/{dist/client/SqbClient.d.ts → esm/client/sqb-client.d.ts} +4 -10
- package/esm/client/sqb-client.js +166 -0
- package/{dist/client/SqbConnection.d.ts → esm/client/sqb-connection.d.ts} +4 -4
- package/esm/client/sqb-connection.js +270 -0
- package/{dist → esm}/client/types.d.ts +5 -13
- package/esm/client/types.js +1 -0
- package/esm/index.d.ts +33 -0
- package/esm/index.js +32 -0
- package/{dist → esm}/orm/backward.d.ts +1 -1
- package/esm/orm/backward.js +17 -0
- package/{dist → esm}/orm/base-entity.d.ts +1 -1
- package/esm/orm/base-entity.js +25 -0
- package/{dist → esm}/orm/commands/command.helper.d.ts +2 -2
- package/esm/orm/commands/command.helper.js +135 -0
- package/{dist → esm}/orm/commands/count.command.d.ts +3 -3
- package/esm/orm/commands/count.command.js +26 -0
- package/{dist → esm}/orm/commands/create.command.d.ts +2 -2
- package/esm/orm/commands/create.command.js +85 -0
- package/{dist → esm}/orm/commands/destroy.command.d.ts +3 -3
- package/esm/orm/commands/destroy.command.js +26 -0
- package/{dist → esm}/orm/commands/find.command.d.ts +9 -9
- package/esm/orm/commands/find.command.js +318 -0
- package/{dist → esm}/orm/commands/row-converter.d.ts +5 -5
- package/esm/orm/commands/row-converter.js +186 -0
- package/{dist → esm}/orm/commands/update.command.d.ts +3 -3
- package/esm/orm/commands/update.command.js +80 -0
- package/esm/orm/decorators/column.decorator.d.ts +4 -0
- package/esm/orm/decorators/column.decorator.js +19 -0
- package/esm/orm/decorators/embedded.decorator.d.ts +3 -0
- package/esm/orm/decorators/embedded.decorator.js +12 -0
- package/{dist → esm}/orm/decorators/entity.decorator.d.ts +17 -17
- package/esm/orm/decorators/entity.decorator.js +175 -0
- package/{dist → esm}/orm/decorators/events.decorator.d.ts +0 -0
- package/esm/orm/decorators/events.decorator.js +55 -0
- package/{dist → esm}/orm/decorators/foreignkey.decorator.d.ts +1 -1
- package/esm/orm/decorators/foreignkey.decorator.js +9 -0
- package/{dist → esm}/orm/decorators/index.decorator.d.ts +1 -1
- package/esm/orm/decorators/index.decorator.js +17 -0
- package/esm/orm/decorators/link.decorator.d.ts +13 -0
- package/esm/orm/decorators/link.decorator.js +66 -0
- package/{dist → esm}/orm/decorators/primarykey.decorator.d.ts +1 -1
- package/esm/orm/decorators/primarykey.decorator.js +20 -0
- package/{dist → esm}/orm/decorators/transform.decorator.d.ts +1 -1
- package/esm/orm/decorators/transform.decorator.js +19 -0
- package/esm/orm/model/association-field-metadata.d.ts +12 -0
- package/esm/orm/model/association-field-metadata.js +20 -0
- package/{dist → esm}/orm/model/association-node.d.ts +1 -1
- package/esm/orm/model/association-node.js +24 -0
- package/{dist → esm}/orm/model/association.d.ts +6 -7
- package/esm/orm/model/association.js +118 -0
- package/{dist/orm/model/column-element-metadata.d.ts → esm/orm/model/column-field-metadata.d.ts} +10 -14
- package/esm/orm/model/column-field-metadata.js +28 -0
- package/esm/orm/model/embedded-field-metadata.d.ts +14 -0
- package/esm/orm/model/embedded-field-metadata.js +25 -0
- package/esm/orm/model/entity-metadata.d.ts +50 -0
- package/esm/orm/model/entity-metadata.js +337 -0
- package/esm/orm/model/field-metadata.d.ts +16 -0
- package/esm/orm/model/field-metadata.js +1 -0
- package/{dist → esm}/orm/model/index-metadata.d.ts +0 -0
- package/esm/orm/model/index-metadata.js +1 -0
- package/esm/orm/model/link-chain.d.ts +13 -0
- package/esm/orm/model/link-chain.js +43 -0
- package/{dist → esm}/orm/orm.const.d.ts +0 -0
- package/esm/orm/orm.const.js +2 -0
- package/{dist → esm}/orm/orm.type.d.ts +3 -4
- package/esm/orm/orm.type.js +1 -0
- package/{dist → esm}/orm/repository.class.d.ts +23 -23
- package/esm/orm/repository.class.js +238 -0
- package/{dist → esm}/orm/util/apply-mixins.d.ts +0 -0
- package/esm/orm/util/apply-mixins.js +9 -0
- package/{dist → esm}/orm/util/extract-keyvalues.d.ts +1 -1
- package/esm/orm/util/extract-keyvalues.js +57 -0
- package/esm/orm/util/orm.helper.d.ts +13 -0
- package/esm/orm/util/orm.helper.js +28 -0
- package/esm/orm/util/serialize-field.d.ts +2 -0
- package/esm/orm/util/serialize-field.js +39 -0
- package/esm/types.d.ts +7 -0
- package/esm/types.js +1 -0
- package/package.json +54 -41
- package/dist/index.d.ts +0 -33
- package/dist/index.js +0 -42
- package/dist/orm/decorators/column.decorator.d.ts +0 -4
- package/dist/orm/decorators/embedded.decorator.d.ts +0 -3
- package/dist/orm/decorators/entity.decorator.js +0 -179
- package/dist/orm/decorators/link.decorator.d.ts +0 -23
- package/dist/orm/decorators/link.decorator.js +0 -88
- package/dist/orm/model/association-element-metadata.d.ts +0 -10
- package/dist/orm/model/association-element-metadata.js +0 -15
- package/dist/orm/model/element-metadata.d.ts +0 -7
- package/dist/orm/model/embedded-element-metadata.d.ts +0 -14
- package/dist/orm/model/entity-metadata.d.ts +0 -50
- package/dist/orm/model/link-chain.d.ts +0 -15
- package/dist/orm/util/orm.helper.d.ts +0 -13
- package/dist/orm/util/serialize-element.d.ts +0 -2
- package/dist/types.d.ts +0 -2
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { And, Eq, Exists, Field, InnerJoin, isCompOperator, isLogicalOperator, LeftOuterJoin, Raw, Select } from '@sqb/builder';
|
|
2
|
+
import { EmbeddedFieldMetadata } from '../model/embedded-field-metadata.js';
|
|
3
|
+
import { EntityMetadata } from '../model/entity-metadata.js';
|
|
4
|
+
import { isAssociationField, isColumnField, isEmbeddedField } from '../util/orm.helper.js';
|
|
5
|
+
export async function joinAssociationGetFirst(joinInfos, association, parentAlias, innerJoin) {
|
|
6
|
+
const joins = await joinAssociation(joinInfos, association, parentAlias, innerJoin);
|
|
7
|
+
return joins[0];
|
|
8
|
+
}
|
|
9
|
+
export async function joinAssociationGetLast(joinInfos, association, parentAlias, innerJoin) {
|
|
10
|
+
const joins = await joinAssociation(joinInfos, association, parentAlias, innerJoin);
|
|
11
|
+
return joins[joins.length - 1];
|
|
12
|
+
}
|
|
13
|
+
export async function joinAssociation(joinInfos, association, parentAlias, innerJoin) {
|
|
14
|
+
let joinInfo;
|
|
15
|
+
let node = association;
|
|
16
|
+
const result = [];
|
|
17
|
+
while (node) {
|
|
18
|
+
joinInfo = joinInfos.find(j => j.association === node);
|
|
19
|
+
if (!joinInfo) {
|
|
20
|
+
const targetEntity = await node.resolveTarget();
|
|
21
|
+
const sourceEntity = await node.resolveSource();
|
|
22
|
+
const keyCol = await node.resolveSourceProperty();
|
|
23
|
+
const targetCol = await node.resolveTargetProperty();
|
|
24
|
+
const joinAlias = 'J' + (joinInfos.length + 1);
|
|
25
|
+
const join = innerJoin ? InnerJoin(targetEntity.tableName + ' as ' + joinAlias) :
|
|
26
|
+
LeftOuterJoin(targetEntity.tableName + ' as ' + joinAlias);
|
|
27
|
+
join.on(Eq(Field(joinAlias + '.' + targetCol.fieldName, targetCol.dataType, targetCol.isArray), Field(parentAlias + '.' + keyCol.fieldName, keyCol.dataType, keyCol.isArray)));
|
|
28
|
+
if (node.conditions)
|
|
29
|
+
await prepareFilter(targetEntity, node.conditions, join._conditions, joinAlias);
|
|
30
|
+
joinInfo = {
|
|
31
|
+
association: node,
|
|
32
|
+
sourceEntity,
|
|
33
|
+
targetEntity,
|
|
34
|
+
joinAlias,
|
|
35
|
+
join
|
|
36
|
+
};
|
|
37
|
+
joinInfos.push(joinInfo);
|
|
38
|
+
}
|
|
39
|
+
result.push(joinInfo);
|
|
40
|
+
if (!node.next)
|
|
41
|
+
break;
|
|
42
|
+
parentAlias = joinInfo.joinAlias;
|
|
43
|
+
node = node.next;
|
|
44
|
+
}
|
|
45
|
+
return result;
|
|
46
|
+
}
|
|
47
|
+
export async function prepareFilter(entityDef, filter, trgOp, tableAlias = 'T') {
|
|
48
|
+
let srcOp;
|
|
49
|
+
if (isLogicalOperator(filter))
|
|
50
|
+
srcOp = filter;
|
|
51
|
+
else {
|
|
52
|
+
srcOp = And();
|
|
53
|
+
if (Array.isArray(filter))
|
|
54
|
+
srcOp.add(...filter);
|
|
55
|
+
else
|
|
56
|
+
srcOp.add(filter);
|
|
57
|
+
}
|
|
58
|
+
for (const item of srcOp._items) {
|
|
59
|
+
if (isLogicalOperator(item)) {
|
|
60
|
+
const ctor = Object.getPrototypeOf(item).constructor;
|
|
61
|
+
const logOp = new ctor();
|
|
62
|
+
await prepareFilter(entityDef, item, logOp, tableAlias);
|
|
63
|
+
trgOp.add(logOp);
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
if (isCompOperator(item)) {
|
|
67
|
+
if (typeof item._left === 'string') {
|
|
68
|
+
const itemPath = item._left.split('.');
|
|
69
|
+
const l = itemPath.length;
|
|
70
|
+
let pt;
|
|
71
|
+
let _curEntity = entityDef;
|
|
72
|
+
let _curAlias = tableAlias;
|
|
73
|
+
let _curPrefix = '';
|
|
74
|
+
let _curSuffix = '';
|
|
75
|
+
let subSelect;
|
|
76
|
+
for (let i = 0; i < l; i++) {
|
|
77
|
+
pt = itemPath[i];
|
|
78
|
+
const col = EntityMetadata.getField(_curEntity, pt);
|
|
79
|
+
if (!col)
|
|
80
|
+
throw new Error(`Unknown property (${item._left}) defined in filter`);
|
|
81
|
+
// if last item on path
|
|
82
|
+
if (i === l - 1) {
|
|
83
|
+
if (!isColumnField(col))
|
|
84
|
+
throw new Error(`Invalid column expression (${item._left}) defined in filter`);
|
|
85
|
+
const ctor = Object.getPrototypeOf(item).constructor;
|
|
86
|
+
trgOp.add(new ctor(Field(_curAlias + '.' + _curPrefix + col.fieldName + _curSuffix, col.dataType, col.isArray), item._right));
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
if (isColumnField(col))
|
|
90
|
+
throw new Error(`Invalid column (${item._left}) defined in filter`);
|
|
91
|
+
if (isEmbeddedField(col)) {
|
|
92
|
+
_curEntity = await EmbeddedFieldMetadata.resolveType(col);
|
|
93
|
+
_curPrefix = _curPrefix + (col.fieldNamePrefix || '');
|
|
94
|
+
_curSuffix = (col.fieldNameSuffix || '') + _curSuffix;
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
if (!isAssociationField(col))
|
|
98
|
+
throw new Error(`Invalid column (${item._left}) defined in filter`);
|
|
99
|
+
let node;
|
|
100
|
+
_curEntity = await col.association.resolveTarget();
|
|
101
|
+
if (!subSelect) {
|
|
102
|
+
const keyCol = await col.association.resolveSourceProperty();
|
|
103
|
+
const targetCol = await col.association.resolveTargetProperty();
|
|
104
|
+
subSelect = Select(Raw('1'))
|
|
105
|
+
.from(_curEntity.tableName + ' K');
|
|
106
|
+
subSelect.where(Eq(Field('K.' + targetCol.fieldName, targetCol.dataType, targetCol.isArray), Field(tableAlias + '.' + keyCol.fieldName, keyCol.dataType, keyCol.isArray)));
|
|
107
|
+
trgOp.add(Exists(subSelect));
|
|
108
|
+
trgOp = subSelect._where;
|
|
109
|
+
if (col.association.conditions) {
|
|
110
|
+
await prepareFilter(_curEntity, col.association.conditions, trgOp, 'K');
|
|
111
|
+
}
|
|
112
|
+
node = col.association.next;
|
|
113
|
+
_curAlias = 'K';
|
|
114
|
+
}
|
|
115
|
+
else
|
|
116
|
+
node = col.association;
|
|
117
|
+
while (node) {
|
|
118
|
+
const targetEntity = await node.resolveTarget();
|
|
119
|
+
const sourceColumn = await node.resolveSourceProperty();
|
|
120
|
+
const targetColumn = await node.resolveTargetProperty();
|
|
121
|
+
const joinAlias = 'J' + (((subSelect?._joins?.length) || 0) + 1);
|
|
122
|
+
subSelect.join(InnerJoin(targetEntity.tableName + ' ' + joinAlias)
|
|
123
|
+
.on(Eq(Field(joinAlias + '.' + targetColumn.fieldName, targetColumn.dataType, targetColumn.isArray), Field(_curAlias + '.' + sourceColumn.fieldName, sourceColumn.dataType, sourceColumn.isArray))));
|
|
124
|
+
_curEntity = targetEntity;
|
|
125
|
+
_curAlias = joinAlias;
|
|
126
|
+
node = node.next;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
trgOp.add(item);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { SqbConnection } from '../../client/
|
|
2
|
-
import { EntityMetadata } from '../model/entity-metadata';
|
|
3
|
-
import { Repository } from '../repository.class';
|
|
1
|
+
import { SqbConnection } from '../../client/sqb-connection.js';
|
|
2
|
+
import { EntityMetadata } from '../model/entity-metadata.js';
|
|
3
|
+
import { Repository } from '../repository.class.js';
|
|
4
4
|
export declare type CountCommandArgs = {
|
|
5
5
|
entity: EntityMetadata;
|
|
6
6
|
connection: SqbConnection;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { And, Count, Select } from '@sqb/builder';
|
|
2
|
+
import { prepareFilter } from './command.helper.js';
|
|
3
|
+
export class CountCommand {
|
|
4
|
+
// istanbul ignore next
|
|
5
|
+
constructor() {
|
|
6
|
+
throw new Error('This class is abstract');
|
|
7
|
+
}
|
|
8
|
+
static async execute(args) {
|
|
9
|
+
const { connection, entity, filter, params } = args;
|
|
10
|
+
let where;
|
|
11
|
+
if (filter) {
|
|
12
|
+
where = And();
|
|
13
|
+
await prepareFilter(entity, filter, where, 'T');
|
|
14
|
+
}
|
|
15
|
+
const query = Select(Count()).from(entity.tableName + ' T');
|
|
16
|
+
if (where)
|
|
17
|
+
query.where(where);
|
|
18
|
+
// Execute query
|
|
19
|
+
const resp = await connection.execute(query, {
|
|
20
|
+
params,
|
|
21
|
+
objectRows: false,
|
|
22
|
+
cursor: false,
|
|
23
|
+
});
|
|
24
|
+
return (resp && resp.rows && resp.rows[0][0]) || 0;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { SqbConnection } from '../../client/
|
|
2
|
-
import { EntityMetadata } from '../model/entity-metadata';
|
|
1
|
+
import { SqbConnection } from '../../client/sqb-connection.js';
|
|
2
|
+
import { EntityMetadata } from '../model/entity-metadata.js';
|
|
3
3
|
export declare type CreateCommandArgs = {
|
|
4
4
|
entity: EntityMetadata;
|
|
5
5
|
connection: SqbConnection;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { Insert, Param } from '@sqb/builder';
|
|
2
|
+
import { ColumnFieldMetadata } from '../model/column-field-metadata.js';
|
|
3
|
+
import { EmbeddedFieldMetadata } from '../model/embedded-field-metadata.js';
|
|
4
|
+
import { EntityMetadata } from '../model/entity-metadata.js';
|
|
5
|
+
import { isColumnField, isEmbeddedField } from '../util/orm.helper.js';
|
|
6
|
+
export class CreateCommand {
|
|
7
|
+
// istanbul ignore next
|
|
8
|
+
constructor() {
|
|
9
|
+
throw new Error('This class is abstract');
|
|
10
|
+
}
|
|
11
|
+
static async execute(args) {
|
|
12
|
+
const { entity } = args;
|
|
13
|
+
const tableName = entity.tableName;
|
|
14
|
+
if (!tableName)
|
|
15
|
+
throw new Error(`${entity.ctor.name} is not decorated with @Entity decorator`);
|
|
16
|
+
// Create a context
|
|
17
|
+
const ctx = {
|
|
18
|
+
entity,
|
|
19
|
+
queryParams: {},
|
|
20
|
+
queryValues: {},
|
|
21
|
+
colCount: 0
|
|
22
|
+
};
|
|
23
|
+
// Prepare
|
|
24
|
+
await this._prepareParams(ctx, entity, args.values);
|
|
25
|
+
if (!ctx.colCount)
|
|
26
|
+
throw new Error('No field given to create new entity instance');
|
|
27
|
+
const query = Insert(tableName, ctx.queryValues);
|
|
28
|
+
if (args.returning) {
|
|
29
|
+
const primaryIndexColumns = EntityMetadata.getPrimaryIndexColumns(entity);
|
|
30
|
+
if (primaryIndexColumns.length)
|
|
31
|
+
query.returning(...primaryIndexColumns.map(col => col.fieldName));
|
|
32
|
+
}
|
|
33
|
+
const qr = await args.connection.execute(query, {
|
|
34
|
+
params: ctx.queryParams,
|
|
35
|
+
objectRows: false,
|
|
36
|
+
cursor: false
|
|
37
|
+
});
|
|
38
|
+
if (args.returning && qr.fields && qr.rows?.length) {
|
|
39
|
+
const keyValues = {};
|
|
40
|
+
for (const f of qr.fields.values()) {
|
|
41
|
+
const el = EntityMetadata.getColumnFieldByFieldName(entity, f.fieldName);
|
|
42
|
+
if (el)
|
|
43
|
+
keyValues[el.name] = qr.rows[0][f.index];
|
|
44
|
+
}
|
|
45
|
+
return keyValues;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
static async _prepareParams(ctx, entity, values, prefix, suffix) {
|
|
49
|
+
let v;
|
|
50
|
+
prefix = prefix || '';
|
|
51
|
+
suffix = suffix || '';
|
|
52
|
+
for (const col of Object.values(entity.fields)) {
|
|
53
|
+
v = values[col.name];
|
|
54
|
+
if (isColumnField(col)) {
|
|
55
|
+
if (col.noInsert)
|
|
56
|
+
continue;
|
|
57
|
+
if (v == null && col.default !== undefined) {
|
|
58
|
+
v = typeof col.default === 'function' ?
|
|
59
|
+
col.default(values) : col.default;
|
|
60
|
+
}
|
|
61
|
+
if (typeof col.serialize === 'function')
|
|
62
|
+
v = col.serialize(v, col.name);
|
|
63
|
+
if (v == null) {
|
|
64
|
+
if (col.notNull && !col.autoGenerated)
|
|
65
|
+
throw new Error(`${entity.name}.${col.name} is required and can't be null`);
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
ColumnFieldMetadata.checkEnumValue(col, v);
|
|
69
|
+
const fieldName = prefix + col.fieldName + suffix;
|
|
70
|
+
const k = '$input_' + fieldName;
|
|
71
|
+
ctx.queryValues[fieldName] = Param({
|
|
72
|
+
name: k,
|
|
73
|
+
dataType: col.dataType,
|
|
74
|
+
isArray: col.isArray
|
|
75
|
+
});
|
|
76
|
+
ctx.queryParams[k] = v;
|
|
77
|
+
ctx.colCount++;
|
|
78
|
+
}
|
|
79
|
+
else if (v != null && isEmbeddedField(col)) {
|
|
80
|
+
const type = await EmbeddedFieldMetadata.resolveType(col);
|
|
81
|
+
await this._prepareParams(ctx, type, v, col.fieldNamePrefix, col.fieldNameSuffix);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { SqbConnection } from '../../client/
|
|
2
|
-
import { EntityMetadata } from '../model/entity-metadata';
|
|
3
|
-
import { Repository } from '../repository.class';
|
|
1
|
+
import { SqbConnection } from '../../client/sqb-connection.js';
|
|
2
|
+
import { EntityMetadata } from '../model/entity-metadata.js';
|
|
3
|
+
import { Repository } from '../repository.class.js';
|
|
4
4
|
export declare type DestroyCommandArgs = {
|
|
5
5
|
entity: EntityMetadata;
|
|
6
6
|
connection: SqbConnection;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { And, Delete } from '@sqb/builder';
|
|
2
|
+
import { prepareFilter } from './command.helper.js';
|
|
3
|
+
export class DestroyCommand {
|
|
4
|
+
// istanbul ignore next
|
|
5
|
+
constructor() {
|
|
6
|
+
throw new Error('This class is abstract');
|
|
7
|
+
}
|
|
8
|
+
static async execute(args) {
|
|
9
|
+
const { connection, entity, filter, params } = args;
|
|
10
|
+
let where;
|
|
11
|
+
if (filter) {
|
|
12
|
+
where = And();
|
|
13
|
+
await prepareFilter(entity, filter, where);
|
|
14
|
+
}
|
|
15
|
+
const query = Delete(entity.tableName + ' T');
|
|
16
|
+
if (where)
|
|
17
|
+
query.where(...where._items);
|
|
18
|
+
// Execute query
|
|
19
|
+
const resp = await connection.execute(query, {
|
|
20
|
+
params,
|
|
21
|
+
objectRows: false,
|
|
22
|
+
cursor: false,
|
|
23
|
+
});
|
|
24
|
+
return (resp && resp.rowsAffected) || 0;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { SqbConnection } from '../../client/
|
|
2
|
-
import { AssociationNode } from '../model/association-node';
|
|
3
|
-
import { EntityMetadata } from '../model/entity-metadata';
|
|
4
|
-
import
|
|
5
|
-
import { RowConverter } from './row-converter';
|
|
1
|
+
import { SqbConnection } from '../../client/sqb-connection.js';
|
|
2
|
+
import { AssociationNode } from '../model/association-node.js';
|
|
3
|
+
import { EntityMetadata } from '../model/entity-metadata.js';
|
|
4
|
+
import { Repository } from '../repository.class.js';
|
|
5
|
+
import { RowConverter } from './row-converter.js';
|
|
6
6
|
export declare type FindCommandArgs = {
|
|
7
7
|
entity: EntityMetadata;
|
|
8
8
|
connection: SqbConnection;
|
|
@@ -25,20 +25,20 @@ export declare class FindCommand {
|
|
|
25
25
|
maxEagerFetch?: number;
|
|
26
26
|
}): Promise<FindCommand>;
|
|
27
27
|
static execute(args: FindCommandArgs): Promise<any[]>;
|
|
28
|
-
|
|
28
|
+
addFields(opts?: {
|
|
29
29
|
tableAlias?: string;
|
|
30
30
|
converter?: RowConverter;
|
|
31
31
|
entity?: EntityMetadata;
|
|
32
|
-
|
|
32
|
+
pick?: string[];
|
|
33
|
+
omit?: string[];
|
|
33
34
|
include?: string[];
|
|
34
|
-
exclude?: string[];
|
|
35
35
|
sort?: string[];
|
|
36
36
|
prefix?: string;
|
|
37
37
|
suffix?: string;
|
|
38
38
|
}): Promise<void>;
|
|
39
39
|
private _selectColumn;
|
|
40
40
|
filter(filter: any): Promise<void>;
|
|
41
|
-
sort(
|
|
41
|
+
sort(sortFields: string[]): Promise<void>;
|
|
42
42
|
execute(args: Pick<FindCommandArgs, 'connection' | 'distinct' | 'offset' | 'limit' | 'params' | 'onTransformRow'>): Promise<any[]>;
|
|
43
43
|
private _getEntityFromAlias;
|
|
44
44
|
}
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
import { And, In, Param, Select } from '@sqb/builder';
|
|
2
|
+
import { Entity } from '../decorators/entity.decorator.js';
|
|
3
|
+
import { AssociationNode } from '../model/association-node.js';
|
|
4
|
+
import { EmbeddedFieldMetadata } from '../model/embedded-field-metadata.js';
|
|
5
|
+
import { EntityMetadata } from '../model/entity-metadata.js';
|
|
6
|
+
import { isAssociationField, isColumnField, isEmbeddedField } from '../util/orm.helper.js';
|
|
7
|
+
import { joinAssociationGetLast, prepareFilter } from './command.helper.js';
|
|
8
|
+
import { RowConverter } from './row-converter.js';
|
|
9
|
+
const SORT_ORDER_PATTERN = /^([-+])?(.*)$/;
|
|
10
|
+
export class FindCommand {
|
|
11
|
+
maxEagerFetch = 100000;
|
|
12
|
+
maxSubQueries = 5;
|
|
13
|
+
mainEntity;
|
|
14
|
+
resultEntity;
|
|
15
|
+
converter;
|
|
16
|
+
mainAlias = 'T';
|
|
17
|
+
resultAlias = 'T';
|
|
18
|
+
_joins = [];
|
|
19
|
+
_selectColumns = {};
|
|
20
|
+
_filter = And();
|
|
21
|
+
_sort;
|
|
22
|
+
constructor(selectEntity, outputEntity) {
|
|
23
|
+
this.mainEntity = selectEntity;
|
|
24
|
+
this.resultEntity = outputEntity;
|
|
25
|
+
this.converter = new RowConverter(outputEntity.ctor);
|
|
26
|
+
}
|
|
27
|
+
static async create(source, opts = {}) {
|
|
28
|
+
let command;
|
|
29
|
+
let listingEntity;
|
|
30
|
+
if (source instanceof AssociationNode) {
|
|
31
|
+
const node = source;
|
|
32
|
+
listingEntity = await node.resolveTarget();
|
|
33
|
+
const resultEntity = await node.getLast().resolveTarget();
|
|
34
|
+
command = new FindCommand(listingEntity, resultEntity);
|
|
35
|
+
if (node.conditions)
|
|
36
|
+
await command.filter(node.conditions);
|
|
37
|
+
if (node.next) {
|
|
38
|
+
const join = await joinAssociationGetLast(command._joins, node.next, command.mainAlias);
|
|
39
|
+
command.resultAlias = join.joinAlias;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
listingEntity = source;
|
|
44
|
+
command = new FindCommand(listingEntity, listingEntity);
|
|
45
|
+
}
|
|
46
|
+
if (!listingEntity.tableName)
|
|
47
|
+
throw new Error(`${listingEntity.ctor.name} is not decorated with @Entity decorator`);
|
|
48
|
+
if (typeof opts.maxSubQueries === 'number')
|
|
49
|
+
command.maxSubQueries = opts.maxSubQueries;
|
|
50
|
+
if (typeof opts.maxEagerFetch === 'number')
|
|
51
|
+
command.maxEagerFetch = opts.maxEagerFetch;
|
|
52
|
+
return command;
|
|
53
|
+
}
|
|
54
|
+
static async execute(args) {
|
|
55
|
+
const command = await FindCommand.create(args.entity, {
|
|
56
|
+
maxSubQueries: args.maxSubQueries,
|
|
57
|
+
maxEagerFetch: args.maxEagerFetch,
|
|
58
|
+
});
|
|
59
|
+
await command.addFields({
|
|
60
|
+
pick: args.pick,
|
|
61
|
+
include: args.include,
|
|
62
|
+
omit: args.omit,
|
|
63
|
+
sort: args.sort
|
|
64
|
+
});
|
|
65
|
+
if (args.filter)
|
|
66
|
+
await command.filter(args.filter);
|
|
67
|
+
if (args.sort)
|
|
68
|
+
await command.sort(args.sort);
|
|
69
|
+
return await command.execute(args);
|
|
70
|
+
}
|
|
71
|
+
async addFields(opts = {}) {
|
|
72
|
+
const tableAlias = opts.tableAlias || this.resultAlias;
|
|
73
|
+
const entity = opts.entity || this._getEntityFromAlias(tableAlias);
|
|
74
|
+
const converter = opts.converter || this.converter;
|
|
75
|
+
const _pick = opts.pick ?
|
|
76
|
+
opts.pick.map(x => x.toLowerCase()) : undefined;
|
|
77
|
+
const _omit = opts.omit ?
|
|
78
|
+
opts.omit.map(x => x.toLowerCase()) : undefined;
|
|
79
|
+
const _include = opts.include ?
|
|
80
|
+
opts.include.map(x => x.toLowerCase()) : undefined;
|
|
81
|
+
let requestedFields;
|
|
82
|
+
if (_pick)
|
|
83
|
+
requestedFields = [..._pick];
|
|
84
|
+
else {
|
|
85
|
+
requestedFields = Entity.getFieldNames(entity.ctor).reduce((a, x) => {
|
|
86
|
+
const f = Entity.getField(entity.ctor, x);
|
|
87
|
+
if (f && !f.exclusive)
|
|
88
|
+
a.push(x.toLowerCase());
|
|
89
|
+
return a;
|
|
90
|
+
}, []);
|
|
91
|
+
}
|
|
92
|
+
if (_include)
|
|
93
|
+
requestedFields.push(..._include);
|
|
94
|
+
const sortFields = opts.sort && opts.sort.length ?
|
|
95
|
+
opts.sort.map(x => x.toLowerCase()) : undefined;
|
|
96
|
+
const prefix = opts.prefix || '';
|
|
97
|
+
const suffix = opts.suffix || '';
|
|
98
|
+
for (const key of Object.keys(entity.fields)) {
|
|
99
|
+
const col = EntityMetadata.getField(entity, key);
|
|
100
|
+
if (!col || col.hidden)
|
|
101
|
+
continue;
|
|
102
|
+
const colNameLower = col.name.toLowerCase();
|
|
103
|
+
// Ignore field if in excluded list
|
|
104
|
+
if (_omit && _omit.includes(colNameLower))
|
|
105
|
+
continue;
|
|
106
|
+
// Check if field request list
|
|
107
|
+
if (!requestedFields.find((x) => x === colNameLower || x.startsWith(colNameLower + '.')))
|
|
108
|
+
continue;
|
|
109
|
+
// Add field to select list if field is a column
|
|
110
|
+
if (isColumnField(col)) {
|
|
111
|
+
const fieldAlias = this._selectColumn(tableAlias, col, prefix, suffix);
|
|
112
|
+
// Add column to converter
|
|
113
|
+
converter.addValueProperty({
|
|
114
|
+
name: col.name,
|
|
115
|
+
fieldAlias,
|
|
116
|
+
dataType: col.dataType,
|
|
117
|
+
parse: col.parse
|
|
118
|
+
});
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
if (isEmbeddedField(col)) {
|
|
122
|
+
const typ = await EmbeddedFieldMetadata.resolveType(col);
|
|
123
|
+
const subConverter = converter.addObjectProperty({
|
|
124
|
+
name: col.name,
|
|
125
|
+
type: typ.ctor
|
|
126
|
+
}).converter;
|
|
127
|
+
await this.addFields({
|
|
128
|
+
tableAlias,
|
|
129
|
+
converter: subConverter,
|
|
130
|
+
entity: typ,
|
|
131
|
+
prefix: col.fieldNamePrefix,
|
|
132
|
+
suffix: col.fieldNameSuffix,
|
|
133
|
+
pick: _pick?.includes(colNameLower) ? undefined :
|
|
134
|
+
extractSubFields(colNameLower, _pick),
|
|
135
|
+
include: extractSubFields(colNameLower, _include),
|
|
136
|
+
omit: extractSubFields(colNameLower, _omit),
|
|
137
|
+
sort: extractSubFields(colNameLower, sortFields),
|
|
138
|
+
});
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
if (isAssociationField(col)) {
|
|
142
|
+
// OtO relation
|
|
143
|
+
if (!col.association.returnsMany()) {
|
|
144
|
+
const joinInfo = await joinAssociationGetLast(this._joins, col.association, tableAlias);
|
|
145
|
+
const subConverter = converter.addObjectProperty({
|
|
146
|
+
name: col.name,
|
|
147
|
+
type: joinInfo.targetEntity.ctor
|
|
148
|
+
}).converter;
|
|
149
|
+
// Add join fields to select columns list
|
|
150
|
+
await this.addFields({
|
|
151
|
+
tableAlias: joinInfo.joinAlias,
|
|
152
|
+
converter: subConverter,
|
|
153
|
+
entity: joinInfo.targetEntity,
|
|
154
|
+
pick: _pick?.includes(colNameLower) ? undefined :
|
|
155
|
+
extractSubFields(colNameLower, _pick),
|
|
156
|
+
include: extractSubFields(colNameLower, _include),
|
|
157
|
+
omit: extractSubFields(colNameLower, _omit),
|
|
158
|
+
sort: extractSubFields(colNameLower, sortFields),
|
|
159
|
+
});
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
// One-2-Many Eager relation
|
|
163
|
+
if (this.maxSubQueries > 0) {
|
|
164
|
+
const targetCol = await col.association.resolveTargetProperty();
|
|
165
|
+
const sourceCol = await col.association.resolveSourceProperty();
|
|
166
|
+
// We need to know key value to filter sub query.
|
|
167
|
+
// So add key field into select columns
|
|
168
|
+
const parentField = this._selectColumn(tableAlias, sourceCol);
|
|
169
|
+
const findCommand = await FindCommand.create(col.association, {
|
|
170
|
+
maxSubQueries: this.maxSubQueries - 1,
|
|
171
|
+
maxEagerFetch: this.maxEagerFetch
|
|
172
|
+
});
|
|
173
|
+
findCommand.converter.parent = this.converter;
|
|
174
|
+
await findCommand.filter(In(targetCol.name, Param(parentField)));
|
|
175
|
+
const sort = sortFields && extractSubFields(colNameLower, sortFields);
|
|
176
|
+
await findCommand.addFields({
|
|
177
|
+
pick: _pick?.includes(colNameLower) ? undefined :
|
|
178
|
+
extractSubFields(colNameLower, _pick),
|
|
179
|
+
include: extractSubFields(colNameLower, _include),
|
|
180
|
+
omit: extractSubFields(colNameLower, _omit),
|
|
181
|
+
sort,
|
|
182
|
+
});
|
|
183
|
+
if (sort)
|
|
184
|
+
await findCommand.sort(sort);
|
|
185
|
+
const keyField = findCommand._selectColumn(findCommand.mainAlias, targetCol);
|
|
186
|
+
const resultType = await col.association.getLast().resolveTarget();
|
|
187
|
+
converter.addNestedProperty({
|
|
188
|
+
name: col.name,
|
|
189
|
+
type: resultType.ctor,
|
|
190
|
+
findCommand,
|
|
191
|
+
parentField,
|
|
192
|
+
keyField,
|
|
193
|
+
sort: extractSubFields(colNameLower, sortFields)
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
_selectColumn(tableAlias, el, prefix, suffix) {
|
|
200
|
+
const fieldName = (prefix || '').toLowerCase() +
|
|
201
|
+
el.fieldName.toUpperCase() +
|
|
202
|
+
(suffix || '').toLowerCase();
|
|
203
|
+
const fieldAlias = tableAlias + '_' + fieldName;
|
|
204
|
+
this._selectColumns[fieldAlias] = {
|
|
205
|
+
field: el,
|
|
206
|
+
statement: tableAlias + '.' + fieldName + ' as ' + fieldAlias
|
|
207
|
+
};
|
|
208
|
+
return fieldAlias;
|
|
209
|
+
}
|
|
210
|
+
async filter(filter) {
|
|
211
|
+
await prepareFilter(this.mainEntity, filter, this._filter);
|
|
212
|
+
}
|
|
213
|
+
async sort(sortFields) {
|
|
214
|
+
const out = [];
|
|
215
|
+
for (const item of sortFields) {
|
|
216
|
+
const m = item.match(SORT_ORDER_PATTERN);
|
|
217
|
+
if (!m)
|
|
218
|
+
throw new Error(`"${item}" is not a valid order expression`);
|
|
219
|
+
let elName = m[2];
|
|
220
|
+
let prefix = '';
|
|
221
|
+
let suffix = '';
|
|
222
|
+
let _entityDef = this.resultEntity;
|
|
223
|
+
let tableAlias = this.resultAlias;
|
|
224
|
+
if (elName.includes('.')) {
|
|
225
|
+
const a = elName.split('.');
|
|
226
|
+
while (a.length > 1) {
|
|
227
|
+
const col = EntityMetadata.getField(_entityDef, a.shift() || '');
|
|
228
|
+
if (isEmbeddedField(col)) {
|
|
229
|
+
_entityDef = await EmbeddedFieldMetadata.resolveType(col);
|
|
230
|
+
if (col.fieldNamePrefix)
|
|
231
|
+
prefix += col.fieldNamePrefix;
|
|
232
|
+
if (col.fieldNameSuffix)
|
|
233
|
+
suffix = col.fieldNameSuffix + suffix;
|
|
234
|
+
}
|
|
235
|
+
else if (isAssociationField(col)) {
|
|
236
|
+
if (col.association.returnsMany()) {
|
|
237
|
+
elName = '';
|
|
238
|
+
break;
|
|
239
|
+
}
|
|
240
|
+
const joinInfo = await joinAssociationGetLast(this._joins, col.association, tableAlias);
|
|
241
|
+
tableAlias = joinInfo.joinAlias;
|
|
242
|
+
_entityDef = joinInfo.targetEntity;
|
|
243
|
+
}
|
|
244
|
+
else
|
|
245
|
+
throw new Error(`Invalid column (${elName}) declared in sort property`);
|
|
246
|
+
}
|
|
247
|
+
if (!elName)
|
|
248
|
+
continue;
|
|
249
|
+
elName = a.shift() || '';
|
|
250
|
+
}
|
|
251
|
+
const col = EntityMetadata.getField(_entityDef, elName);
|
|
252
|
+
if (!col)
|
|
253
|
+
throw new Error(`Unknown field (${elName}) declared in sort property`);
|
|
254
|
+
if (!isColumnField(col))
|
|
255
|
+
throw new Error(`Can not sort by "${elName}", because it is not a data column`);
|
|
256
|
+
const dir = m[1] || '+';
|
|
257
|
+
out.push((dir || '') + tableAlias + '.' + prefix + col.fieldName + suffix);
|
|
258
|
+
}
|
|
259
|
+
this._sort = out;
|
|
260
|
+
}
|
|
261
|
+
async execute(args) {
|
|
262
|
+
// Generate select query
|
|
263
|
+
const columnSqls = Object.keys(this._selectColumns)
|
|
264
|
+
.map(x => this._selectColumns[x].statement);
|
|
265
|
+
if (!columnSqls.length)
|
|
266
|
+
columnSqls.push('1');
|
|
267
|
+
const query = Select(...columnSqls)
|
|
268
|
+
.from(this.mainEntity.tableName + ' as ' + this.mainAlias);
|
|
269
|
+
if (args.distinct)
|
|
270
|
+
query.distinct();
|
|
271
|
+
query.where(...this._filter._items);
|
|
272
|
+
if (this._sort) {
|
|
273
|
+
query.orderBy(...this._sort);
|
|
274
|
+
}
|
|
275
|
+
if (args.offset)
|
|
276
|
+
query.offset(args.offset);
|
|
277
|
+
// joins must be added last
|
|
278
|
+
if (this._joins)
|
|
279
|
+
for (const j of this._joins) {
|
|
280
|
+
query.join(j.join);
|
|
281
|
+
}
|
|
282
|
+
// Execute query
|
|
283
|
+
const resp = await args.connection.execute(query, {
|
|
284
|
+
params: args?.params,
|
|
285
|
+
fetchRows: args?.limit,
|
|
286
|
+
objectRows: false,
|
|
287
|
+
cursor: false
|
|
288
|
+
});
|
|
289
|
+
// Create objects
|
|
290
|
+
if (resp.rows && resp.fields) {
|
|
291
|
+
return this.converter.transform(args.connection, resp.fields, resp.rows, args.onTransformRow);
|
|
292
|
+
}
|
|
293
|
+
return [];
|
|
294
|
+
}
|
|
295
|
+
_getEntityFromAlias(tableAlias) {
|
|
296
|
+
if (tableAlias === this.mainAlias)
|
|
297
|
+
return this.mainEntity;
|
|
298
|
+
if (tableAlias === this.resultAlias)
|
|
299
|
+
return this.resultEntity;
|
|
300
|
+
if (this._joins) {
|
|
301
|
+
const join = this._joins.find(j => j.joinAlias === tableAlias);
|
|
302
|
+
if (join)
|
|
303
|
+
return join.targetEntity;
|
|
304
|
+
}
|
|
305
|
+
throw new Error(`Unknown table alias "${tableAlias}"`);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
function extractSubFields(colNameLower, fields) {
|
|
309
|
+
if (!(fields && fields.length))
|
|
310
|
+
return;
|
|
311
|
+
if (fields) {
|
|
312
|
+
return fields.reduce((trg, v) => {
|
|
313
|
+
if (v.startsWith(colNameLower + '.'))
|
|
314
|
+
trg.push(v.substring(colNameLower.length + 1).toLowerCase());
|
|
315
|
+
return trg;
|
|
316
|
+
}, []);
|
|
317
|
+
}
|
|
318
|
+
}
|