@expo/entity-database-adapter-knex 0.62.0 → 0.64.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 +1 -1
- package/build/src/AuthorizationResultBasedKnexEntityLoader.d.ts +15 -5
- package/build/src/AuthorizationResultBasedKnexEntityLoader.js +24 -7
- package/build/src/BasePostgresEntityDatabaseAdapter.d.ts +19 -11
- package/build/src/BasePostgresEntityDatabaseAdapter.js +37 -13
- package/build/src/BaseSQLQueryBuilder.d.ts +2 -2
- package/build/src/BaseSQLQueryBuilder.js +2 -2
- package/build/src/EnforcingKnexEntityLoader.d.ts +14 -28
- package/build/src/EnforcingKnexEntityLoader.js +17 -30
- package/build/src/PostgresEntityDatabaseAdapter.d.ts +7 -2
- package/build/src/PostgresEntityDatabaseAdapter.js +25 -15
- package/build/src/SQLOperator.d.ts +15 -14
- package/build/src/SQLOperator.js +11 -8
- package/build/src/internal/EntityKnexDataManager.d.ts +2 -10
- package/build/src/internal/EntityKnexDataManager.js +12 -22
- package/package.json +16 -16
- package/src/AuthorizationResultBasedKnexEntityLoader.ts +29 -14
- package/src/BasePostgresEntityDatabaseAdapter.ts +60 -29
- package/src/BaseSQLQueryBuilder.ts +2 -2
- package/src/EnforcingKnexEntityLoader.ts +20 -38
- package/src/PostgresEntityDatabaseAdapter.ts +66 -29
- package/src/SQLOperator.ts +23 -22
- package/src/__integration-tests__/PostgresEntityIntegration-test.ts +140 -116
- package/src/__tests__/AuthorizationResultBasedKnexEntityLoader-test.ts +0 -75
- package/src/__tests__/BasePostgresEntityDatabaseAdapter-test.ts +21 -28
- package/src/__tests__/EnforcingKnexEntityLoader-test.ts +0 -52
- package/src/__tests__/SQLOperator-test.ts +2 -29
- package/src/__tests__/fixtures/StubPostgresDatabaseAdapter.ts +26 -12
- package/src/internal/EntityKnexDataManager.ts +28 -31
- package/src/internal/__tests__/EntityKnexDataManager-test.ts +1 -57
|
@@ -258,12 +258,11 @@ describe('SQLOperator', () => {
|
|
|
258
258
|
});
|
|
259
259
|
|
|
260
260
|
it('handles all SupportedSQLValue types in getDebugString', () => {
|
|
261
|
-
const fragment = new SQLFragment('INSERT INTO test VALUES (?, ?, ?, ?, ?, ?, ?,
|
|
261
|
+
const fragment = new SQLFragment('INSERT INTO test VALUES (?, ?, ?, ?, ?, ?, ?, ?)', [
|
|
262
262
|
{ type: 'value', value: 'string' },
|
|
263
263
|
{ type: 'value', value: 123 },
|
|
264
264
|
{ type: 'value', value: true },
|
|
265
265
|
{ type: 'value', value: null },
|
|
266
|
-
{ type: 'value', value: undefined },
|
|
267
266
|
{ type: 'value', value: new Date('2024-01-01T00:00:00.000Z') },
|
|
268
267
|
{ type: 'value', value: Buffer.from('hello') },
|
|
269
268
|
{ type: 'value', value: BigInt(999) },
|
|
@@ -272,7 +271,7 @@ describe('SQLOperator', () => {
|
|
|
272
271
|
|
|
273
272
|
const text = fragment.getDebugString();
|
|
274
273
|
expect(text).toBe(
|
|
275
|
-
"INSERT INTO test VALUES ('string', 123, TRUE, NULL,
|
|
274
|
+
"INSERT INTO test VALUES ('string', 123, TRUE, NULL, '2024-01-01T00:00:00.000Z', '\\x68656c6c6f', 999, ARRAY[1, 2, 3])",
|
|
276
275
|
);
|
|
277
276
|
});
|
|
278
277
|
|
|
@@ -763,13 +762,6 @@ describe('SQLOperator', () => {
|
|
|
763
762
|
expect(fragment.getKnexBindings(getColumnForField)).toEqual(['nullable_field']);
|
|
764
763
|
});
|
|
765
764
|
|
|
766
|
-
it('handles undefined in equality check', () => {
|
|
767
|
-
const fragment = SQLExpression.eq('nullableField', undefined);
|
|
768
|
-
|
|
769
|
-
expect(fragment.sql).toBe('?? IS NULL');
|
|
770
|
-
expect(fragment.getKnexBindings(getColumnForField)).toEqual(['nullable_field']);
|
|
771
|
-
});
|
|
772
|
-
|
|
773
765
|
it('accepts a SQLFragment expression', () => {
|
|
774
766
|
const fragment = SQLExpression.eq(sql<TestFields>`${entityField('stringField')}`, 'active');
|
|
775
767
|
expect(fragment.sql).toBe('?? = ?');
|
|
@@ -801,13 +793,6 @@ describe('SQLOperator', () => {
|
|
|
801
793
|
expect(fragment.getKnexBindings(getColumnForField)).toEqual(['nullable_field']);
|
|
802
794
|
});
|
|
803
795
|
|
|
804
|
-
it('handles undefined in inequality check', () => {
|
|
805
|
-
const fragment = SQLExpression.neq('nullableField', undefined);
|
|
806
|
-
|
|
807
|
-
expect(fragment.sql).toBe('?? IS NOT NULL');
|
|
808
|
-
expect(fragment.getKnexBindings(getColumnForField)).toEqual(['nullable_field']);
|
|
809
|
-
});
|
|
810
|
-
|
|
811
796
|
it('accepts a SQLFragment expression', () => {
|
|
812
797
|
const fragment = SQLExpression.neq(
|
|
813
798
|
sql<TestFields>`${entityField('stringField')}`,
|
|
@@ -1131,12 +1116,6 @@ describe('SQLOperator', () => {
|
|
|
1131
1116
|
expect(fragment.getKnexBindings(getColumnForField)).toEqual(['string_field']);
|
|
1132
1117
|
});
|
|
1133
1118
|
|
|
1134
|
-
it('eq(undefined) uses IS NULL', () => {
|
|
1135
|
-
const fragment = makeExpr<string>(stringFieldFragment()).eq(undefined);
|
|
1136
|
-
expect(fragment.sql).toBe('?? IS NULL');
|
|
1137
|
-
expect(fragment.getKnexBindings(getColumnForField)).toEqual(['string_field']);
|
|
1138
|
-
});
|
|
1139
|
-
|
|
1140
1119
|
it('neq(value)', () => {
|
|
1141
1120
|
const fragment = makeExpr<string>(stringFieldFragment()).neq('deleted');
|
|
1142
1121
|
expect(fragment.sql).toBe('?? != ?');
|
|
@@ -1149,12 +1128,6 @@ describe('SQLOperator', () => {
|
|
|
1149
1128
|
expect(fragment.getKnexBindings(getColumnForField)).toEqual(['string_field']);
|
|
1150
1129
|
});
|
|
1151
1130
|
|
|
1152
|
-
it('neq(undefined) uses IS NOT NULL', () => {
|
|
1153
|
-
const fragment = makeExpr<string>(stringFieldFragment()).neq(undefined);
|
|
1154
|
-
expect(fragment.sql).toBe('?? IS NOT NULL');
|
|
1155
|
-
expect(fragment.getKnexBindings(getColumnForField)).toEqual(['string_field']);
|
|
1156
|
-
});
|
|
1157
|
-
|
|
1158
1131
|
it('gt(value)', () => {
|
|
1159
1132
|
const fragment = makeExpr<number>(intFieldFragment()).gt(10);
|
|
1160
1133
|
expect(fragment.sql).toBe('?? > ?');
|
|
@@ -76,7 +76,7 @@ export class StubPostgresDatabaseAdapter<
|
|
|
76
76
|
): Promise<object[]> {
|
|
77
77
|
const objectCollection = this.getObjectCollectionForTable(tableName);
|
|
78
78
|
const results = StubPostgresDatabaseAdapter.uniqBy(tableTuples, (tuple) =>
|
|
79
|
-
|
|
79
|
+
JSON.stringify(tuple),
|
|
80
80
|
).reduce(
|
|
81
81
|
(acc, tableTuple) => {
|
|
82
82
|
return acc.concat(
|
|
@@ -193,23 +193,37 @@ export class StubPostgresDatabaseAdapter<
|
|
|
193
193
|
return filteredObjects;
|
|
194
194
|
}
|
|
195
195
|
|
|
196
|
-
protected
|
|
196
|
+
protected fetchManyBySQLFragmentInternalAsync(
|
|
197
197
|
_queryInterface: any,
|
|
198
198
|
_tableName: string,
|
|
199
|
-
|
|
200
|
-
_bindings: object | any[],
|
|
199
|
+
_sqlFragment: SQLFragment<TFields>,
|
|
201
200
|
_querySelectionModifiers: TableQuerySelectionModifiers<TFields>,
|
|
202
201
|
): Promise<object[]> {
|
|
203
|
-
throw new Error('
|
|
202
|
+
throw new Error('SQL fragments not supported for StubDatabaseAdapter');
|
|
204
203
|
}
|
|
205
204
|
|
|
206
|
-
protected
|
|
205
|
+
protected async countByFieldEqualityConjunctionInternalAsync(
|
|
206
|
+
queryInterface: any,
|
|
207
|
+
tableName: string,
|
|
208
|
+
tableFieldSingleValueEqualityOperands: TableFieldSingleValueEqualityCondition[],
|
|
209
|
+
tableFieldMultiValueEqualityOperands: TableFieldMultiValueEqualityCondition[],
|
|
210
|
+
): Promise<number> {
|
|
211
|
+
const results = await this.fetchManyByFieldEqualityConjunctionInternalAsync(
|
|
212
|
+
queryInterface,
|
|
213
|
+
tableName,
|
|
214
|
+
tableFieldSingleValueEqualityOperands,
|
|
215
|
+
tableFieldMultiValueEqualityOperands,
|
|
216
|
+
{ orderBy: undefined, offset: undefined, limit: undefined },
|
|
217
|
+
);
|
|
218
|
+
return results.length;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
protected countBySQLFragmentInternalAsync(
|
|
207
222
|
_queryInterface: any,
|
|
208
223
|
_tableName: string,
|
|
209
224
|
_sqlFragment: SQLFragment<TFields>,
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
throw new Error('SQL fragments not supported for StubDatabaseAdapter');
|
|
225
|
+
): Promise<number> {
|
|
226
|
+
throw new Error('SQL fragment count not supported for StubDatabaseAdapter');
|
|
213
227
|
}
|
|
214
228
|
|
|
215
229
|
private generateRandomID(): any {
|
|
@@ -254,7 +268,7 @@ export class StubPostgresDatabaseAdapter<
|
|
|
254
268
|
tableIdField: string,
|
|
255
269
|
id: any,
|
|
256
270
|
object: object,
|
|
257
|
-
): Promise<
|
|
271
|
+
): Promise<{ updatedRowCount: number }> {
|
|
258
272
|
// SQL does not support empty updates, mirror behavior here for better test simulation
|
|
259
273
|
if (Object.keys(object).length === 0) {
|
|
260
274
|
throw new Error(`Empty update (${tableIdField} = ${id})`);
|
|
@@ -269,14 +283,14 @@ export class StubPostgresDatabaseAdapter<
|
|
|
269
283
|
// SQL updates to a nonexistent row succeed but affect 0 rows,
|
|
270
284
|
// mirror that behavior here for better test simulation
|
|
271
285
|
if (objectIndex < 0) {
|
|
272
|
-
return
|
|
286
|
+
return { updatedRowCount: 0 };
|
|
273
287
|
}
|
|
274
288
|
|
|
275
289
|
objectCollection[objectIndex] = {
|
|
276
290
|
...objectCollection[objectIndex],
|
|
277
291
|
...object,
|
|
278
292
|
};
|
|
279
|
-
return
|
|
293
|
+
return { updatedRowCount: 1 };
|
|
280
294
|
}
|
|
281
295
|
|
|
282
296
|
protected async deleteInternalAsync(
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
EntityDatabaseAdapterPaginationCursorInvalidError,
|
|
4
4
|
EntityMetricsLoadType,
|
|
5
5
|
getDatabaseFieldForEntityField,
|
|
6
|
+
timeAndLogCountEventAsync,
|
|
6
7
|
timeAndLogLoadEventAsync,
|
|
7
8
|
} from '@expo/entity';
|
|
8
9
|
import assert from 'assert';
|
|
@@ -190,34 +191,19 @@ export class EntityKnexDataManager<
|
|
|
190
191
|
);
|
|
191
192
|
}
|
|
192
193
|
|
|
193
|
-
|
|
194
|
-
* Loads many objects matching the raw WHERE clause.
|
|
195
|
-
*
|
|
196
|
-
* @param queryContext - query context in which to perform the load
|
|
197
|
-
* @param rawWhereClause - parameterized SQL WHERE clause with positional binding placeholders or named binding placeholders
|
|
198
|
-
* @param bindings - array of positional bindings or object of named bindings
|
|
199
|
-
* @param querySelectionModifiers - limit, offset, orderBy, and orderByRaw for the query
|
|
200
|
-
* @returns array of objects matching the query
|
|
201
|
-
*/
|
|
202
|
-
async loadManyByRawWhereClauseAsync(
|
|
194
|
+
async countByFieldEqualityConjunctionAsync<N extends keyof TFields>(
|
|
203
195
|
queryContext: EntityQueryContext,
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
): Promise<readonly Readonly<TFields>[]> {
|
|
208
|
-
EntityKnexDataManager.validateOrderByClauses(querySelectionModifiers.orderBy);
|
|
209
|
-
|
|
210
|
-
return await timeAndLogLoadEventAsync(
|
|
196
|
+
fieldEqualityOperands: readonly FieldEqualityCondition<TFields, N>[],
|
|
197
|
+
): Promise<number> {
|
|
198
|
+
return await timeAndLogCountEventAsync(
|
|
211
199
|
this.metricsAdapter,
|
|
212
|
-
EntityMetricsLoadType.
|
|
200
|
+
EntityMetricsLoadType.COUNT_EQUALITY_CONJUNCTION,
|
|
213
201
|
this.entityClassName,
|
|
214
202
|
queryContext,
|
|
215
203
|
)(
|
|
216
|
-
this.databaseAdapter.
|
|
204
|
+
this.databaseAdapter.countByFieldEqualityConjunctionAsync(
|
|
217
205
|
queryContext,
|
|
218
|
-
|
|
219
|
-
bindings,
|
|
220
|
-
querySelectionModifiers,
|
|
206
|
+
fieldEqualityOperands,
|
|
221
207
|
),
|
|
222
208
|
);
|
|
223
209
|
}
|
|
@@ -243,6 +229,18 @@ export class EntityKnexDataManager<
|
|
|
243
229
|
);
|
|
244
230
|
}
|
|
245
231
|
|
|
232
|
+
async countBySQLFragmentAsync(
|
|
233
|
+
queryContext: EntityQueryContext,
|
|
234
|
+
sqlFragment: SQLFragment<TFields>,
|
|
235
|
+
): Promise<number> {
|
|
236
|
+
return await timeAndLogCountEventAsync(
|
|
237
|
+
this.metricsAdapter,
|
|
238
|
+
EntityMetricsLoadType.COUNT_SQL,
|
|
239
|
+
this.entityClassName,
|
|
240
|
+
queryContext,
|
|
241
|
+
)(this.databaseAdapter.countBySQLFragmentAsync(queryContext, sqlFragment));
|
|
242
|
+
}
|
|
243
|
+
|
|
246
244
|
/**
|
|
247
245
|
* Load a page of objects using cursor-based pagination with unified pagination specification.
|
|
248
246
|
*
|
|
@@ -482,17 +480,16 @@ export class EntityKnexDataManager<
|
|
|
482
480
|
baseWhere: SQLFragment<TFields> | undefined,
|
|
483
481
|
cursorCondition: SQLFragment<TFields> | null,
|
|
484
482
|
): SQLFragment<TFields> {
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
return sql`TRUE`;
|
|
483
|
+
if (!baseWhere) {
|
|
484
|
+
return cursorCondition ?? sql`TRUE`;
|
|
488
485
|
}
|
|
489
|
-
|
|
490
|
-
|
|
486
|
+
|
|
487
|
+
if (!cursorCondition) {
|
|
488
|
+
return baseWhere;
|
|
491
489
|
}
|
|
492
|
-
|
|
493
|
-
//
|
|
494
|
-
|
|
495
|
-
return sql`(${first}) AND ${second}`;
|
|
490
|
+
|
|
491
|
+
// Wrap baseWhere in parens when combining with cursor condition
|
|
492
|
+
return sql`(${baseWhere}) AND ${cursorCondition}`;
|
|
496
493
|
}
|
|
497
494
|
|
|
498
495
|
private augmentOrderByIfNecessary(
|
|
@@ -2,17 +2,7 @@ import type { EntityQueryContext, IEntityMetricsAdapter } from '@expo/entity';
|
|
|
2
2
|
import { EntityMetricsLoadType, NoOpEntityMetricsAdapter } from '@expo/entity';
|
|
3
3
|
import { StubQueryContextProvider } from '@expo/entity-testing-utils';
|
|
4
4
|
import { describe, expect, it } from '@jest/globals';
|
|
5
|
-
import {
|
|
6
|
-
anyNumber,
|
|
7
|
-
anyString,
|
|
8
|
-
anything,
|
|
9
|
-
deepEqual,
|
|
10
|
-
instance,
|
|
11
|
-
mock,
|
|
12
|
-
resetCalls,
|
|
13
|
-
verify,
|
|
14
|
-
when,
|
|
15
|
-
} from 'ts-mockito';
|
|
5
|
+
import { anyNumber, anything, deepEqual, instance, mock, verify, when } from 'ts-mockito';
|
|
16
6
|
|
|
17
7
|
import { OrderByOrdering } from '../../BasePostgresEntityDatabaseAdapter.ts';
|
|
18
8
|
import { PaginationStrategy } from '../../PaginationStrategy.ts';
|
|
@@ -110,14 +100,6 @@ describe(EntityKnexDataManager, () => {
|
|
|
110
100
|
nullableField: null,
|
|
111
101
|
},
|
|
112
102
|
]);
|
|
113
|
-
when(
|
|
114
|
-
databaseAdapterMock.fetchManyByRawWhereClauseAsync(
|
|
115
|
-
anything(),
|
|
116
|
-
anyString(),
|
|
117
|
-
anything(),
|
|
118
|
-
anything(),
|
|
119
|
-
),
|
|
120
|
-
).thenResolve([]);
|
|
121
103
|
|
|
122
104
|
const entityDataManager = new EntityKnexDataManager(
|
|
123
105
|
testEntityConfiguration,
|
|
@@ -148,21 +130,6 @@ describe(EntityKnexDataManager, () => {
|
|
|
148
130
|
),
|
|
149
131
|
).once();
|
|
150
132
|
|
|
151
|
-
resetCalls(metricsAdapterMock);
|
|
152
|
-
|
|
153
|
-
await entityDataManager.loadManyByRawWhereClauseAsync(queryContext, '', [], {});
|
|
154
|
-
verify(
|
|
155
|
-
metricsAdapterMock.logDataManagerLoadEvent(
|
|
156
|
-
deepEqual({
|
|
157
|
-
type: EntityMetricsLoadType.LOAD_MANY_RAW,
|
|
158
|
-
isInTransaction: false,
|
|
159
|
-
entityClassName: TestEntity.name,
|
|
160
|
-
duration: anyNumber(),
|
|
161
|
-
count: 0,
|
|
162
|
-
}),
|
|
163
|
-
),
|
|
164
|
-
).once();
|
|
165
|
-
|
|
166
133
|
verify(metricsAdapterMock.incrementDataManagerLoadCount(anything())).never();
|
|
167
134
|
});
|
|
168
135
|
|
|
@@ -190,14 +157,6 @@ describe(EntityKnexDataManager, () => {
|
|
|
190
157
|
nullableField: null,
|
|
191
158
|
},
|
|
192
159
|
]);
|
|
193
|
-
when(
|
|
194
|
-
databaseAdapterMock.fetchManyByRawWhereClauseAsync(
|
|
195
|
-
anything(),
|
|
196
|
-
anyString(),
|
|
197
|
-
anything(),
|
|
198
|
-
anything(),
|
|
199
|
-
),
|
|
200
|
-
).thenResolve([]);
|
|
201
160
|
|
|
202
161
|
const entityDataManager = new EntityKnexDataManager(
|
|
203
162
|
testEntityConfiguration,
|
|
@@ -229,21 +188,6 @@ describe(EntityKnexDataManager, () => {
|
|
|
229
188
|
),
|
|
230
189
|
).once();
|
|
231
190
|
|
|
232
|
-
resetCalls(metricsAdapterMock);
|
|
233
|
-
|
|
234
|
-
await entityDataManager.loadManyByRawWhereClauseAsync(queryContext, '', [], {});
|
|
235
|
-
verify(
|
|
236
|
-
metricsAdapterMock.logDataManagerLoadEvent(
|
|
237
|
-
deepEqual({
|
|
238
|
-
type: EntityMetricsLoadType.LOAD_MANY_RAW,
|
|
239
|
-
isInTransaction: true,
|
|
240
|
-
entityClassName: TestEntity.name,
|
|
241
|
-
duration: anyNumber(),
|
|
242
|
-
count: 0,
|
|
243
|
-
}),
|
|
244
|
-
),
|
|
245
|
-
).once();
|
|
246
|
-
|
|
247
191
|
verify(metricsAdapterMock.incrementDataManagerLoadCount(anything())).never();
|
|
248
192
|
});
|
|
249
193
|
});
|