@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.
Files changed (154) hide show
  1. package/README.md +67 -0
  2. package/{dist/client/Adapter.js → cjs/client/adapter.js} +0 -0
  3. package/{dist/client/CursorStream.js → cjs/client/cursor-stream.js} +5 -2
  4. package/{dist/client/Cursor.js → cjs/client/cursor.js} +18 -11
  5. package/{dist → cjs}/client/extensions.js +0 -0
  6. package/{dist/client/FieldInfoMap.js → cjs/client/field-info-map.js} +2 -0
  7. package/{dist → cjs}/client/helpers.js +2 -2
  8. package/{dist/client/SqbClient.js → cjs/client/sqb-client.js} +16 -10
  9. package/{dist/client/SqbConnection.js → cjs/client/sqb-connection.js} +17 -14
  10. package/{dist → cjs}/client/types.js +0 -0
  11. package/cjs/index.js +42 -0
  12. package/{dist → cjs}/orm/backward.js +8 -8
  13. package/{dist → cjs}/orm/base-entity.js +8 -7
  14. package/{dist → cjs}/orm/commands/command.helper.js +9 -9
  15. package/{dist → cjs}/orm/commands/count.command.js +2 -2
  16. package/{dist → cjs}/orm/commands/create.command.js +12 -12
  17. package/{dist → cjs}/orm/commands/destroy.command.js +2 -2
  18. package/{dist → cjs}/orm/commands/find.command.js +100 -88
  19. package/{dist → cjs}/orm/commands/row-converter.js +10 -7
  20. package/{dist → cjs}/orm/commands/update.command.js +10 -10
  21. package/{dist → cjs}/orm/decorators/column.decorator.js +3 -3
  22. package/{dist → cjs}/orm/decorators/embedded.decorator.js +3 -3
  23. package/cjs/orm/decorators/entity.decorator.js +179 -0
  24. package/{dist → cjs}/orm/decorators/events.decorator.js +13 -13
  25. package/{dist → cjs}/orm/decorators/foreignkey.decorator.js +3 -3
  26. package/{dist → cjs}/orm/decorators/index.decorator.js +5 -5
  27. package/cjs/orm/decorators/link.decorator.js +70 -0
  28. package/{dist → cjs}/orm/decorators/primarykey.decorator.js +7 -7
  29. package/{dist → cjs}/orm/decorators/transform.decorator.js +5 -5
  30. package/cjs/orm/model/association-field-metadata.js +24 -0
  31. package/{dist → cjs}/orm/model/association-node.js +5 -2
  32. package/{dist → cjs}/orm/model/association.js +31 -39
  33. package/{dist/orm/model/column-element-metadata.js → cjs/orm/model/column-field-metadata.js} +8 -8
  34. package/{dist/orm/model/embedded-element-metadata.js → cjs/orm/model/embedded-field-metadata.js} +8 -8
  35. package/{dist → cjs}/orm/model/entity-metadata.js +90 -91
  36. package/{dist/orm/model/element-metadata.js → cjs/orm/model/field-metadata.js} +0 -0
  37. package/{dist → cjs}/orm/model/index-metadata.js +0 -0
  38. package/{dist → cjs}/orm/model/link-chain.js +12 -15
  39. package/{dist → cjs}/orm/orm.const.js +0 -0
  40. package/{dist → cjs}/orm/orm.type.js +0 -0
  41. package/{dist → cjs}/orm/repository.class.js +25 -22
  42. package/{dist → cjs}/orm/util/apply-mixins.js +0 -0
  43. package/{dist → cjs}/orm/util/extract-keyvalues.js +5 -5
  44. package/{dist → cjs}/orm/util/orm.helper.js +10 -10
  45. package/{dist/orm/util/serialize-element.js → cjs/orm/util/serialize-field.js} +2 -2
  46. package/cjs/package.json +3 -0
  47. package/{dist → cjs}/types.js +0 -0
  48. package/{dist/client/Adapter.d.ts → esm/client/adapter.d.ts} +1 -1
  49. package/esm/client/adapter.js +1 -0
  50. package/{dist/client/CursorStream.d.ts → esm/client/cursor-stream.d.ts} +1 -1
  51. package/esm/client/cursor-stream.js +96 -0
  52. package/{dist/client/Cursor.d.ts → esm/client/cursor.d.ts} +5 -5
  53. package/esm/client/cursor.js +252 -0
  54. package/{dist → esm}/client/extensions.d.ts +1 -1
  55. package/esm/client/extensions.js +9 -0
  56. package/{dist/client/FieldInfoMap.d.ts → esm/client/field-info-map.d.ts} +1 -1
  57. package/esm/client/field-info-map.js +50 -0
  58. package/{dist → esm}/client/helpers.d.ts +3 -3
  59. package/esm/client/helpers.js +125 -0
  60. package/{dist/client/SqbClient.d.ts → esm/client/sqb-client.d.ts} +4 -10
  61. package/esm/client/sqb-client.js +166 -0
  62. package/{dist/client/SqbConnection.d.ts → esm/client/sqb-connection.d.ts} +4 -4
  63. package/esm/client/sqb-connection.js +270 -0
  64. package/{dist → esm}/client/types.d.ts +5 -13
  65. package/esm/client/types.js +1 -0
  66. package/esm/index.d.ts +33 -0
  67. package/esm/index.js +32 -0
  68. package/{dist → esm}/orm/backward.d.ts +1 -1
  69. package/esm/orm/backward.js +17 -0
  70. package/{dist → esm}/orm/base-entity.d.ts +1 -1
  71. package/esm/orm/base-entity.js +25 -0
  72. package/{dist → esm}/orm/commands/command.helper.d.ts +2 -2
  73. package/esm/orm/commands/command.helper.js +135 -0
  74. package/{dist → esm}/orm/commands/count.command.d.ts +3 -3
  75. package/esm/orm/commands/count.command.js +26 -0
  76. package/{dist → esm}/orm/commands/create.command.d.ts +2 -2
  77. package/esm/orm/commands/create.command.js +85 -0
  78. package/{dist → esm}/orm/commands/destroy.command.d.ts +3 -3
  79. package/esm/orm/commands/destroy.command.js +26 -0
  80. package/{dist → esm}/orm/commands/find.command.d.ts +9 -9
  81. package/esm/orm/commands/find.command.js +318 -0
  82. package/{dist → esm}/orm/commands/row-converter.d.ts +5 -5
  83. package/esm/orm/commands/row-converter.js +186 -0
  84. package/{dist → esm}/orm/commands/update.command.d.ts +3 -3
  85. package/esm/orm/commands/update.command.js +80 -0
  86. package/esm/orm/decorators/column.decorator.d.ts +4 -0
  87. package/esm/orm/decorators/column.decorator.js +19 -0
  88. package/esm/orm/decorators/embedded.decorator.d.ts +3 -0
  89. package/esm/orm/decorators/embedded.decorator.js +12 -0
  90. package/{dist → esm}/orm/decorators/entity.decorator.d.ts +17 -17
  91. package/esm/orm/decorators/entity.decorator.js +175 -0
  92. package/{dist → esm}/orm/decorators/events.decorator.d.ts +0 -0
  93. package/esm/orm/decorators/events.decorator.js +55 -0
  94. package/{dist → esm}/orm/decorators/foreignkey.decorator.d.ts +1 -1
  95. package/esm/orm/decorators/foreignkey.decorator.js +9 -0
  96. package/{dist → esm}/orm/decorators/index.decorator.d.ts +1 -1
  97. package/esm/orm/decorators/index.decorator.js +17 -0
  98. package/esm/orm/decorators/link.decorator.d.ts +13 -0
  99. package/esm/orm/decorators/link.decorator.js +66 -0
  100. package/{dist → esm}/orm/decorators/primarykey.decorator.d.ts +1 -1
  101. package/esm/orm/decorators/primarykey.decorator.js +20 -0
  102. package/{dist → esm}/orm/decorators/transform.decorator.d.ts +1 -1
  103. package/esm/orm/decorators/transform.decorator.js +19 -0
  104. package/esm/orm/model/association-field-metadata.d.ts +12 -0
  105. package/esm/orm/model/association-field-metadata.js +20 -0
  106. package/{dist → esm}/orm/model/association-node.d.ts +1 -1
  107. package/esm/orm/model/association-node.js +24 -0
  108. package/{dist → esm}/orm/model/association.d.ts +6 -7
  109. package/esm/orm/model/association.js +118 -0
  110. package/{dist/orm/model/column-element-metadata.d.ts → esm/orm/model/column-field-metadata.d.ts} +10 -14
  111. package/esm/orm/model/column-field-metadata.js +28 -0
  112. package/esm/orm/model/embedded-field-metadata.d.ts +14 -0
  113. package/esm/orm/model/embedded-field-metadata.js +25 -0
  114. package/esm/orm/model/entity-metadata.d.ts +50 -0
  115. package/esm/orm/model/entity-metadata.js +337 -0
  116. package/esm/orm/model/field-metadata.d.ts +16 -0
  117. package/esm/orm/model/field-metadata.js +1 -0
  118. package/{dist → esm}/orm/model/index-metadata.d.ts +0 -0
  119. package/esm/orm/model/index-metadata.js +1 -0
  120. package/esm/orm/model/link-chain.d.ts +13 -0
  121. package/esm/orm/model/link-chain.js +43 -0
  122. package/{dist → esm}/orm/orm.const.d.ts +0 -0
  123. package/esm/orm/orm.const.js +2 -0
  124. package/{dist → esm}/orm/orm.type.d.ts +3 -4
  125. package/esm/orm/orm.type.js +1 -0
  126. package/{dist → esm}/orm/repository.class.d.ts +23 -23
  127. package/esm/orm/repository.class.js +238 -0
  128. package/{dist → esm}/orm/util/apply-mixins.d.ts +0 -0
  129. package/esm/orm/util/apply-mixins.js +9 -0
  130. package/{dist → esm}/orm/util/extract-keyvalues.d.ts +1 -1
  131. package/esm/orm/util/extract-keyvalues.js +57 -0
  132. package/esm/orm/util/orm.helper.d.ts +13 -0
  133. package/esm/orm/util/orm.helper.js +28 -0
  134. package/esm/orm/util/serialize-field.d.ts +2 -0
  135. package/esm/orm/util/serialize-field.js +39 -0
  136. package/esm/types.d.ts +7 -0
  137. package/esm/types.js +1 -0
  138. package/package.json +54 -41
  139. package/dist/index.d.ts +0 -33
  140. package/dist/index.js +0 -42
  141. package/dist/orm/decorators/column.decorator.d.ts +0 -4
  142. package/dist/orm/decorators/embedded.decorator.d.ts +0 -3
  143. package/dist/orm/decorators/entity.decorator.js +0 -179
  144. package/dist/orm/decorators/link.decorator.d.ts +0 -23
  145. package/dist/orm/decorators/link.decorator.js +0 -88
  146. package/dist/orm/model/association-element-metadata.d.ts +0 -10
  147. package/dist/orm/model/association-element-metadata.js +0 -15
  148. package/dist/orm/model/element-metadata.d.ts +0 -7
  149. package/dist/orm/model/embedded-element-metadata.d.ts +0 -14
  150. package/dist/orm/model/entity-metadata.d.ts +0 -50
  151. package/dist/orm/model/link-chain.d.ts +0 -15
  152. package/dist/orm/util/orm.helper.d.ts +0 -13
  153. package/dist/orm/util/serialize-element.d.ts +0 -2
  154. 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/SqbConnection';
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/SqbConnection';
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/SqbConnection';
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/SqbConnection';
2
- import { AssociationNode } from '../model/association-node';
3
- import { EntityMetadata } from '../model/entity-metadata';
4
- import type { Repository } from '../repository.class';
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
- addElements(opts?: {
28
+ addFields(opts?: {
29
29
  tableAlias?: string;
30
30
  converter?: RowConverter;
31
31
  entity?: EntityMetadata;
32
- elements?: string[];
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(sortElements: string[]): Promise<void>;
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
+ }