@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.
Files changed (30) hide show
  1. package/README.md +1 -1
  2. package/build/src/AuthorizationResultBasedKnexEntityLoader.d.ts +15 -5
  3. package/build/src/AuthorizationResultBasedKnexEntityLoader.js +24 -7
  4. package/build/src/BasePostgresEntityDatabaseAdapter.d.ts +19 -11
  5. package/build/src/BasePostgresEntityDatabaseAdapter.js +37 -13
  6. package/build/src/BaseSQLQueryBuilder.d.ts +2 -2
  7. package/build/src/BaseSQLQueryBuilder.js +2 -2
  8. package/build/src/EnforcingKnexEntityLoader.d.ts +14 -28
  9. package/build/src/EnforcingKnexEntityLoader.js +17 -30
  10. package/build/src/PostgresEntityDatabaseAdapter.d.ts +7 -2
  11. package/build/src/PostgresEntityDatabaseAdapter.js +25 -15
  12. package/build/src/SQLOperator.d.ts +15 -14
  13. package/build/src/SQLOperator.js +11 -8
  14. package/build/src/internal/EntityKnexDataManager.d.ts +2 -10
  15. package/build/src/internal/EntityKnexDataManager.js +12 -22
  16. package/package.json +16 -16
  17. package/src/AuthorizationResultBasedKnexEntityLoader.ts +29 -14
  18. package/src/BasePostgresEntityDatabaseAdapter.ts +60 -29
  19. package/src/BaseSQLQueryBuilder.ts +2 -2
  20. package/src/EnforcingKnexEntityLoader.ts +20 -38
  21. package/src/PostgresEntityDatabaseAdapter.ts +66 -29
  22. package/src/SQLOperator.ts +23 -22
  23. package/src/__integration-tests__/PostgresEntityIntegration-test.ts +140 -116
  24. package/src/__tests__/AuthorizationResultBasedKnexEntityLoader-test.ts +0 -75
  25. package/src/__tests__/BasePostgresEntityDatabaseAdapter-test.ts +21 -28
  26. package/src/__tests__/EnforcingKnexEntityLoader-test.ts +0 -52
  27. package/src/__tests__/SQLOperator-test.ts +2 -29
  28. package/src/__tests__/fixtures/StubPostgresDatabaseAdapter.ts +26 -12
  29. package/src/internal/EntityKnexDataManager.ts +28 -31
  30. package/src/internal/__tests__/EntityKnexDataManager-test.ts +1 -57
package/README.md CHANGED
@@ -39,4 +39,4 @@ export const createDefaultEntityCompanionProvider = (
39
39
  }
40
40
  );
41
41
  };
42
- ```
42
+ ```
@@ -236,13 +236,23 @@ export declare class AuthorizationResultBasedKnexEntityLoader<TFields extends Re
236
236
  */
237
237
  loadManyByFieldEqualityConjunctionAsync<N extends keyof Pick<TFields, TSelectedFields>>(fieldEqualityOperands: readonly FieldEqualityCondition<TFields, N>[], querySelectionModifiers?: EntityLoaderQuerySelectionModifiers<TFields, TSelectedFields>): Promise<readonly Result<TEntity>[]>;
238
238
  /**
239
- * Authorization-result-based version of the EnforcingKnexEntityLoader method by the same name.
240
- * @returns array of entity results that match the query, where result error can be UnauthorizedError
241
- * @throws Error when rawWhereClause or bindings are invalid
239
+ * Count entities matching the conjunction of field equality operands.
240
+ * This does not perform authorization since count does not load full entities.
241
+ * Note that this should be used with the same caution as loadManyByFieldEqualityConjunctionAsync
242
+ * regarding indexing since counts can be expensive on large datasets without appropriate indexes.
243
+ *
244
+ * @returns count of entities matching the filters
245
+ */
246
+ countByFieldEqualityConjunctionAsync<N extends keyof Pick<TFields, TSelectedFields>>(fieldEqualityOperands: readonly FieldEqualityCondition<TFields, N>[]): Promise<number>;
247
+ /**
248
+ * Count entities matching a SQL fragment.
249
+ * This does not perform authorization since count does not load full entities.
250
+ * Note that this should be used with the same caution as loadManyBySQL regarding indexing
251
+ * since counts can be expensive on large datasets without appropriate indexes.
242
252
  *
243
- * @deprecated Use loadManyBySQL instead for safer value bindings and more flexible query building.
253
+ * @returns count of entities matching the query
244
254
  */
245
- loadManyByRawWhereClauseAsync(rawWhereClause: string, bindings: any[] | object, querySelectionModifiers?: EntityLoaderQuerySelectionModifiers<TFields, TSelectedFields>): Promise<readonly Result<TEntity>[]>;
255
+ countBySQLAsync(fragment: SQLFragment<Pick<TFields, TSelectedFields>>): Promise<number>;
246
256
  /**
247
257
  * Authorization-result-based version of the EnforcingKnexEntityLoader method by the same name.
248
258
  * @returns SQL query builder for building and executing SQL queries that when executed returns entity results where result error can be UnauthorizedError.
@@ -44,15 +44,32 @@ export class AuthorizationResultBasedKnexEntityLoader {
44
44
  return await this.constructionUtils.constructAndAuthorizeEntitiesArrayAsync(fieldObjects);
45
45
  }
46
46
  /**
47
- * Authorization-result-based version of the EnforcingKnexEntityLoader method by the same name.
48
- * @returns array of entity results that match the query, where result error can be UnauthorizedError
49
- * @throws Error when rawWhereClause or bindings are invalid
47
+ * Count entities matching the conjunction of field equality operands.
48
+ * This does not perform authorization since count does not load full entities.
49
+ * Note that this should be used with the same caution as loadManyByFieldEqualityConjunctionAsync
50
+ * regarding indexing since counts can be expensive on large datasets without appropriate indexes.
50
51
  *
51
- * @deprecated Use loadManyBySQL instead for safer value bindings and more flexible query building.
52
+ * @returns count of entities matching the filters
52
53
  */
53
- async loadManyByRawWhereClauseAsync(rawWhereClause, bindings, querySelectionModifiers = {}) {
54
- const fieldObjects = await this.knexDataManager.loadManyByRawWhereClauseAsync(this.queryContext, rawWhereClause, bindings, querySelectionModifiers);
55
- return await this.constructionUtils.constructAndAuthorizeEntitiesArrayAsync(fieldObjects);
54
+ async countByFieldEqualityConjunctionAsync(fieldEqualityOperands) {
55
+ for (const fieldEqualityOperand of fieldEqualityOperands) {
56
+ const fieldValues = isSingleValueFieldEqualityCondition(fieldEqualityOperand)
57
+ ? [fieldEqualityOperand.fieldValue]
58
+ : fieldEqualityOperand.fieldValues;
59
+ this.constructionUtils.validateFieldAndValues(fieldEqualityOperand.fieldName, fieldValues);
60
+ }
61
+ return await this.knexDataManager.countByFieldEqualityConjunctionAsync(this.queryContext, fieldEqualityOperands);
62
+ }
63
+ /**
64
+ * Count entities matching a SQL fragment.
65
+ * This does not perform authorization since count does not load full entities.
66
+ * Note that this should be used with the same caution as loadManyBySQL regarding indexing
67
+ * since counts can be expensive on large datasets without appropriate indexes.
68
+ *
69
+ * @returns count of entities matching the query
70
+ */
71
+ async countBySQLAsync(fragment) {
72
+ return await this.knexDataManager.countBySQLFragmentAsync(this.queryContext, fragment);
56
73
  }
57
74
  /**
58
75
  * Authorization-result-based version of the EnforcingKnexEntityLoader method by the same name.
@@ -126,17 +126,6 @@ export declare abstract class BasePostgresEntityDatabaseAdapter<TFields extends
126
126
  */
127
127
  fetchManyByFieldEqualityConjunctionAsync<N extends keyof TFields>(queryContext: EntityQueryContext, fieldEqualityOperands: readonly FieldEqualityCondition<TFields, N>[], querySelectionModifiers: PostgresQuerySelectionModifiers<TFields>): Promise<readonly Readonly<TFields>[]>;
128
128
  protected abstract fetchManyByFieldEqualityConjunctionInternalAsync(queryInterface: Knex, tableName: string, tableFieldSingleValueEqualityOperands: TableFieldSingleValueEqualityCondition[], tableFieldMultiValueEqualityOperands: TableFieldMultiValueEqualityCondition[], querySelectionModifiers: TableQuerySelectionModifiers<TFields>): Promise<object[]>;
129
- /**
130
- * Fetch many objects matching the raw WHERE clause.
131
- *
132
- * @param queryContext - query context with which to perform the fetch
133
- * @param rawWhereClause - parameterized SQL WHERE clause with positional binding placeholders or named binding placeholders
134
- * @param bindings - array of positional bindings or object of named bindings
135
- * @param querySelectionModifiers - limit, offset, and orderBy for the query
136
- * @returns array of objects matching the query
137
- */
138
- fetchManyByRawWhereClauseAsync(queryContext: EntityQueryContext, rawWhereClause: string, bindings: any[] | object, querySelectionModifiers: PostgresQuerySelectionModifiers<TFields>): Promise<readonly Readonly<TFields>[]>;
139
- protected abstract fetchManyByRawWhereClauseInternalAsync(queryInterface: Knex, tableName: string, rawWhereClause: string, bindings: object | any[], querySelectionModifiers: TableQuerySelectionModifiers<TFields>): Promise<object[]>;
140
129
  /**
141
130
  * Fetch many objects matching the SQL fragment.
142
131
  *
@@ -147,5 +136,24 @@ export declare abstract class BasePostgresEntityDatabaseAdapter<TFields extends
147
136
  */
148
137
  fetchManyBySQLFragmentAsync(queryContext: EntityQueryContext, sqlFragment: SQLFragment<TFields>, querySelectionModifiers: PostgresQuerySelectionModifiers<TFields>): Promise<readonly Readonly<TFields>[]>;
149
138
  protected abstract fetchManyBySQLFragmentInternalAsync(queryInterface: Knex, tableName: string, sqlFragment: SQLFragment<TFields>, querySelectionModifiers: TableQuerySelectionModifiers<TFields>): Promise<object[]>;
139
+ /**
140
+ * Count objects matching the conjunction of where clauses constructed from
141
+ * specified field equality operands.
142
+ *
143
+ * @param queryContext - query context with which to perform the count
144
+ * @param fieldEqualityOperands - list of field equality where clause operand specifications
145
+ * @returns count of objects matching the query
146
+ */
147
+ countByFieldEqualityConjunctionAsync<N extends keyof TFields>(queryContext: EntityQueryContext, fieldEqualityOperands: readonly FieldEqualityCondition<TFields, N>[]): Promise<number>;
148
+ protected abstract countByFieldEqualityConjunctionInternalAsync(queryInterface: Knex, tableName: string, tableFieldSingleValueEqualityOperands: TableFieldSingleValueEqualityCondition[], tableFieldMultiValueEqualityOperands: TableFieldMultiValueEqualityCondition[]): Promise<number>;
149
+ /**
150
+ * Count objects matching the SQL fragment.
151
+ *
152
+ * @param queryContext - query context with which to perform the count
153
+ * @param sqlFragment - SQLFragment for the WHERE clause of the query
154
+ * @returns count of objects matching the query
155
+ */
156
+ countBySQLFragmentAsync(queryContext: EntityQueryContext, sqlFragment: SQLFragment<TFields>): Promise<number>;
157
+ protected abstract countBySQLFragmentInternalAsync(queryInterface: Knex, tableName: string, sqlFragment: SQLFragment<TFields>): Promise<number>;
150
158
  private convertToTableQueryModifiers;
151
159
  }
@@ -60,19 +60,6 @@ export class BasePostgresEntityDatabaseAdapter extends EntityDatabaseAdapter {
60
60
  const results = await this.fetchManyByFieldEqualityConjunctionInternalAsync(queryContext.getQueryInterface(), this.entityConfiguration.tableName, tableFieldSingleValueOperands, tableFieldMultipleValueOperands, this.convertToTableQueryModifiers(querySelectionModifiers));
61
61
  return results.map((result) => transformDatabaseObjectToFields(this.entityConfiguration, this.fieldTransformerMap, result));
62
62
  }
63
- /**
64
- * Fetch many objects matching the raw WHERE clause.
65
- *
66
- * @param queryContext - query context with which to perform the fetch
67
- * @param rawWhereClause - parameterized SQL WHERE clause with positional binding placeholders or named binding placeholders
68
- * @param bindings - array of positional bindings or object of named bindings
69
- * @param querySelectionModifiers - limit, offset, and orderBy for the query
70
- * @returns array of objects matching the query
71
- */
72
- async fetchManyByRawWhereClauseAsync(queryContext, rawWhereClause, bindings, querySelectionModifiers) {
73
- const results = await this.fetchManyByRawWhereClauseInternalAsync(queryContext.getQueryInterface(), this.entityConfiguration.tableName, rawWhereClause, bindings, this.convertToTableQueryModifiers(querySelectionModifiers));
74
- return results.map((result) => transformDatabaseObjectToFields(this.entityConfiguration, this.fieldTransformerMap, result));
75
- }
76
63
  /**
77
64
  * Fetch many objects matching the SQL fragment.
78
65
  *
@@ -85,6 +72,43 @@ export class BasePostgresEntityDatabaseAdapter extends EntityDatabaseAdapter {
85
72
  const results = await this.fetchManyBySQLFragmentInternalAsync(queryContext.getQueryInterface(), this.entityConfiguration.tableName, sqlFragment, this.convertToTableQueryModifiers(querySelectionModifiers));
86
73
  return results.map((result) => transformDatabaseObjectToFields(this.entityConfiguration, this.fieldTransformerMap, result));
87
74
  }
75
+ /**
76
+ * Count objects matching the conjunction of where clauses constructed from
77
+ * specified field equality operands.
78
+ *
79
+ * @param queryContext - query context with which to perform the count
80
+ * @param fieldEqualityOperands - list of field equality where clause operand specifications
81
+ * @returns count of objects matching the query
82
+ */
83
+ async countByFieldEqualityConjunctionAsync(queryContext, fieldEqualityOperands) {
84
+ const tableFieldSingleValueOperands = [];
85
+ const tableFieldMultipleValueOperands = [];
86
+ for (const operand of fieldEqualityOperands) {
87
+ if (isSingleValueFieldEqualityCondition(operand)) {
88
+ tableFieldSingleValueOperands.push({
89
+ tableField: getDatabaseFieldForEntityField(this.entityConfiguration, operand.fieldName),
90
+ tableValue: operand.fieldValue,
91
+ });
92
+ }
93
+ else {
94
+ tableFieldMultipleValueOperands.push({
95
+ tableField: getDatabaseFieldForEntityField(this.entityConfiguration, operand.fieldName),
96
+ tableValues: operand.fieldValues,
97
+ });
98
+ }
99
+ }
100
+ return await this.countByFieldEqualityConjunctionInternalAsync(queryContext.getQueryInterface(), this.entityConfiguration.tableName, tableFieldSingleValueOperands, tableFieldMultipleValueOperands);
101
+ }
102
+ /**
103
+ * Count objects matching the SQL fragment.
104
+ *
105
+ * @param queryContext - query context with which to perform the count
106
+ * @param sqlFragment - SQLFragment for the WHERE clause of the query
107
+ * @returns count of objects matching the query
108
+ */
109
+ async countBySQLFragmentAsync(queryContext, sqlFragment) {
110
+ return await this.countBySQLFragmentInternalAsync(queryContext.getQueryInterface(), this.entityConfiguration.tableName, sqlFragment);
111
+ }
88
112
  convertToTableQueryModifiers(querySelectionModifiers) {
89
113
  const orderBy = querySelectionModifiers.orderBy;
90
114
  return {
@@ -25,7 +25,7 @@ export declare abstract class BaseSQLQueryBuilder<TFields extends Record<string,
25
25
  /**
26
26
  * Order by a field. Can be called multiple times to add multiple order bys.
27
27
  */
28
- orderBy(fieldName: TSelectedFields, order?: OrderByOrdering, nulls?: NullsOrdering | undefined): this;
28
+ orderBy(fieldName: TSelectedFields, order?: OrderByOrdering, nulls?: NullsOrdering): this;
29
29
  /**
30
30
  * Order by a SQL fragment expression.
31
31
  * Provides type-safe, parameterized ORDER BY clauses
@@ -44,7 +44,7 @@ export declare abstract class BaseSQLQueryBuilder<TFields extends Record<string,
44
44
  * @param fragment - The SQL fragment to order by. Must not include the ASC/DESC keyword, as ordering direction is determined by the `order` parameter.
45
45
  * @param order - The ordering direction (ascending or descending). Defaults to ascending.
46
46
  */
47
- orderBySQL(fragment: SQLFragment<Pick<TFields, TSelectedFields>>, order?: OrderByOrdering, nulls?: NullsOrdering | undefined): this;
47
+ orderBySQL(fragment: SQLFragment<Pick<TFields, TSelectedFields>>, order?: OrderByOrdering, nulls?: NullsOrdering): this;
48
48
  /**
49
49
  * Get the current modifiers as QuerySelectionModifiersWithOrderByFragment<TFields>
50
50
  */
@@ -27,7 +27,7 @@ export class BaseSQLQueryBuilder {
27
27
  /**
28
28
  * Order by a field. Can be called multiple times to add multiple order bys.
29
29
  */
30
- orderBy(fieldName, order = OrderByOrdering.ASCENDING, nulls = undefined) {
30
+ orderBy(fieldName, order = OrderByOrdering.ASCENDING, nulls) {
31
31
  this.modifiers.orderBy = [...(this.modifiers.orderBy ?? []), { fieldName, order, nulls }];
32
32
  return this;
33
33
  }
@@ -49,7 +49,7 @@ export class BaseSQLQueryBuilder {
49
49
  * @param fragment - The SQL fragment to order by. Must not include the ASC/DESC keyword, as ordering direction is determined by the `order` parameter.
50
50
  * @param order - The ordering direction (ascending or descending). Defaults to ascending.
51
51
  */
52
- orderBySQL(fragment, order = OrderByOrdering.ASCENDING, nulls = undefined) {
52
+ orderBySQL(fragment, order = OrderByOrdering.ASCENDING, nulls) {
53
53
  this.modifiers.orderBy = [
54
54
  ...(this.modifiers.orderBy ?? []),
55
55
  { fieldFragment: fragment, order, nulls },
@@ -39,37 +39,23 @@ export declare class EnforcingKnexEntityLoader<TFields extends Record<string, an
39
39
  */
40
40
  loadManyByFieldEqualityConjunctionAsync<N extends keyof Pick<TFields, TSelectedFields>>(fieldEqualityOperands: FieldEqualityCondition<TFields, N>[], querySelectionModifiers?: EntityLoaderQuerySelectionModifiers<TFields, TSelectedFields>): Promise<readonly TEntity[]>;
41
41
  /**
42
- * Load entities with a raw SQL WHERE clause.
42
+ * Count entities matching the conjunction of field equality operands.
43
+ * This does not perform authorization since count does not load full entities.
44
+ * Note that this should be used with the same caution as loadManyByFieldEqualityConjunctionAsync
45
+ * regarding indexing since counts can be expensive on large datasets without appropriate indexes.
43
46
  *
44
- * @example
45
- * Load entities with SQL function
46
- * ```typescript
47
- * const entitiesWithJsonKey = await ExampleEntity.loader(vc)
48
- * .loadManyByRawWhereClauseAsync(
49
- * "json_column->>'key_name' = ?",
50
- * ['value'],
51
- * );
52
- * ```
53
- *
54
- * @example
55
- * Load entities with tuple matching
56
- * ```typescript
57
- * const entities = await ExampleEntity.loader(vc)
58
- * .loadManyByRawWhereClauseAsync(
59
- * '(column_1, column_2) IN ((?, ?), (?, ?))',
60
- * [value1, value2, value3, value4],
61
- * );
62
- * ```
63
- * @param rawWhereClause - SQL WHERE clause. Interpolated values should be specified as ?-placeholders or :key_name
64
- * @param bindings - values to bind to the placeholders in the WHERE clause
65
- * @param querySelectionModifiers - limit, offset, and orderBy for the query.
66
- * @returns entities matching the WHERE clause
67
- * @throws EntityNotAuthorizedError when viewer is not authorized to view one or more of the returned entities
68
- * @throws Error when rawWhereClause or bindings are invalid
47
+ * @returns count of entities matching the filters
48
+ */
49
+ countByFieldEqualityConjunctionAsync<N extends keyof Pick<TFields, TSelectedFields>>(fieldEqualityOperands: FieldEqualityCondition<TFields, N>[]): Promise<number>;
50
+ /**
51
+ * Count entities matching a SQL fragment.
52
+ * This does not perform authorization since count does not load full entity rows.
53
+ * Note that this should be used with the same caution as loadManyBySQL regarding indexing
54
+ * since counts can be expensive on large datasets without appropriate indexes.
69
55
  *
70
- * @deprecated Use loadManyBySQL instead for safer value bindings and more flexible query building.
56
+ * @returns count of entities matching the query
71
57
  */
72
- loadManyByRawWhereClauseAsync(rawWhereClause: string, bindings: any[] | object, querySelectionModifiers?: EntityLoaderQuerySelectionModifiers<TFields, TSelectedFields>): Promise<readonly TEntity[]>;
58
+ countBySQLAsync(fragment: SQLFragment<Pick<TFields, TSelectedFields>>): Promise<number>;
73
59
  /**
74
60
  * Load entities using a SQL query builder. When executed, all queries will enforce authorization and throw if not authorized.
75
61
  *
@@ -46,39 +46,26 @@ export class EnforcingKnexEntityLoader {
46
46
  return entityResults.map((result) => result.enforceValue());
47
47
  }
48
48
  /**
49
- * Load entities with a raw SQL WHERE clause.
49
+ * Count entities matching the conjunction of field equality operands.
50
+ * This does not perform authorization since count does not load full entities.
51
+ * Note that this should be used with the same caution as loadManyByFieldEqualityConjunctionAsync
52
+ * regarding indexing since counts can be expensive on large datasets without appropriate indexes.
50
53
  *
51
- * @example
52
- * Load entities with SQL function
53
- * ```typescript
54
- * const entitiesWithJsonKey = await ExampleEntity.loader(vc)
55
- * .loadManyByRawWhereClauseAsync(
56
- * "json_column->>'key_name' = ?",
57
- * ['value'],
58
- * );
59
- * ```
60
- *
61
- * @example
62
- * Load entities with tuple matching
63
- * ```typescript
64
- * const entities = await ExampleEntity.loader(vc)
65
- * .loadManyByRawWhereClauseAsync(
66
- * '(column_1, column_2) IN ((?, ?), (?, ?))',
67
- * [value1, value2, value3, value4],
68
- * );
69
- * ```
70
- * @param rawWhereClause - SQL WHERE clause. Interpolated values should be specified as ?-placeholders or :key_name
71
- * @param bindings - values to bind to the placeholders in the WHERE clause
72
- * @param querySelectionModifiers - limit, offset, and orderBy for the query.
73
- * @returns entities matching the WHERE clause
74
- * @throws EntityNotAuthorizedError when viewer is not authorized to view one or more of the returned entities
75
- * @throws Error when rawWhereClause or bindings are invalid
54
+ * @returns count of entities matching the filters
55
+ */
56
+ async countByFieldEqualityConjunctionAsync(fieldEqualityOperands) {
57
+ return await this.knexEntityLoader.countByFieldEqualityConjunctionAsync(fieldEqualityOperands);
58
+ }
59
+ /**
60
+ * Count entities matching a SQL fragment.
61
+ * This does not perform authorization since count does not load full entity rows.
62
+ * Note that this should be used with the same caution as loadManyBySQL regarding indexing
63
+ * since counts can be expensive on large datasets without appropriate indexes.
76
64
  *
77
- * @deprecated Use loadManyBySQL instead for safer value bindings and more flexible query building.
65
+ * @returns count of entities matching the query
78
66
  */
79
- async loadManyByRawWhereClauseAsync(rawWhereClause, bindings, querySelectionModifiers = {}) {
80
- const entityResults = await this.knexEntityLoader.loadManyByRawWhereClauseAsync(rawWhereClause, bindings, querySelectionModifiers);
81
- return entityResults.map((result) => result.enforceValue());
67
+ async countBySQLAsync(fragment) {
68
+ return await this.knexEntityLoader.countBySQLAsync(fragment);
82
69
  }
83
70
  /**
84
71
  * Load entities using a SQL query builder. When executed, all queries will enforce authorization and throw if not authorized.
@@ -12,10 +12,15 @@ export declare class PostgresEntityDatabaseAdapter<TFields extends Record<string
12
12
  protected fetchManyWhereInternalAsync(queryInterface: Knex, tableName: string, tableColumns: readonly string[], tableTuples: any[][]): Promise<object[]>;
13
13
  protected fetchOneWhereInternalAsync(queryInterface: Knex, tableName: string, tableColumns: readonly string[], tableTuple: readonly any[]): Promise<object | null>;
14
14
  private applyQueryModifiersToQuery;
15
+ private applyFieldEqualityConjunctionWhereClause;
15
16
  protected fetchManyByFieldEqualityConjunctionInternalAsync(queryInterface: Knex, tableName: string, tableFieldSingleValueEqualityOperands: TableFieldSingleValueEqualityCondition[], tableFieldMultiValueEqualityOperands: TableFieldMultiValueEqualityCondition[], querySelectionModifiers: TableQuerySelectionModifiers<TFields>): Promise<object[]>;
16
- protected fetchManyByRawWhereClauseInternalAsync(queryInterface: Knex, tableName: string, rawWhereClause: string, bindings: object | any[], querySelectionModifiers: TableQuerySelectionModifiers<TFields>): Promise<object[]>;
17
+ private applySQLFragmentWhereClause;
17
18
  protected fetchManyBySQLFragmentInternalAsync(queryInterface: Knex, tableName: string, sqlFragment: SQLFragment<TFields>, querySelectionModifiers: TableQuerySelectionModifiers<TFields>): Promise<object[]>;
19
+ protected countByFieldEqualityConjunctionInternalAsync(queryInterface: Knex, tableName: string, tableFieldSingleValueEqualityOperands: TableFieldSingleValueEqualityCondition[], tableFieldMultiValueEqualityOperands: TableFieldMultiValueEqualityCondition[]): Promise<number>;
20
+ protected countBySQLFragmentInternalAsync(queryInterface: Knex, tableName: string, sqlFragment: SQLFragment<TFields>): Promise<number>;
18
21
  protected insertInternalAsync(queryInterface: Knex, tableName: string, object: object): Promise<object[]>;
19
- protected updateInternalAsync(queryInterface: Knex, tableName: string, tableIdField: string, id: any, object: object): Promise<object[]>;
22
+ protected updateInternalAsync(queryInterface: Knex, tableName: string, tableIdField: string, id: any, object: object): Promise<{
23
+ updatedRowCount: number;
24
+ }>;
20
25
  protected deleteInternalAsync(queryInterface: Knex, tableName: string, tableIdField: string, id: any): Promise<number>;
21
26
  }
@@ -1,4 +1,4 @@
1
- import { getDatabaseFieldForEntityField } from '@expo/entity';
1
+ import { getDatabaseFieldForEntityField, RESERVED_ENTITY_COUNT_QUERY_ALIAS } from '@expo/entity';
2
2
  import { BasePostgresEntityDatabaseAdapter, NullsOrdering, OrderByOrdering, } from "./BasePostgresEntityDatabaseAdapter.js";
3
3
  import { JSONArrayField, MaybeJSONArrayField } from "./EntityFields.js";
4
4
  import { wrapNativePostgresCallAsync } from "./errors/wrapNativePostgresCallAsync.js";
@@ -100,8 +100,8 @@ export class PostgresEntityDatabaseAdapter extends BasePostgresEntityDatabaseAda
100
100
  }
101
101
  return ret;
102
102
  }
103
- async fetchManyByFieldEqualityConjunctionInternalAsync(queryInterface, tableName, tableFieldSingleValueEqualityOperands, tableFieldMultiValueEqualityOperands, querySelectionModifiers) {
104
- let query = queryInterface.select().from(tableName);
103
+ applyFieldEqualityConjunctionWhereClause(query, tableFieldSingleValueEqualityOperands, tableFieldMultiValueEqualityOperands) {
104
+ let result = query;
105
105
  if (tableFieldSingleValueEqualityOperands.length > 0) {
106
106
  const whereObject = {};
107
107
  const nonNullTableFieldSingleValueEqualityOperands = tableFieldSingleValueEqualityOperands.filter(({ tableValue }) => tableValue !== null);
@@ -110,18 +110,18 @@ export class PostgresEntityDatabaseAdapter extends BasePostgresEntityDatabaseAda
110
110
  for (const { tableField, tableValue } of nonNullTableFieldSingleValueEqualityOperands) {
111
111
  whereObject[tableField] = tableValue;
112
112
  }
113
- query = query.where(whereObject);
113
+ result = result.where(whereObject);
114
114
  }
115
115
  if (nullTableFieldSingleValueEqualityOperands.length > 0) {
116
116
  for (const { tableField } of nullTableFieldSingleValueEqualityOperands) {
117
- query = query.whereNull(tableField);
117
+ result = result.whereNull(tableField);
118
118
  }
119
119
  }
120
120
  }
121
121
  if (tableFieldMultiValueEqualityOperands.length > 0) {
122
122
  for (const { tableField, tableValues } of tableFieldMultiValueEqualityOperands) {
123
123
  const nonNullTableValues = tableValues.filter((tableValue) => tableValue !== null);
124
- query = query.where((builder) => {
124
+ result = result.where((builder) => {
125
125
  builder.whereRaw('?? = ANY(?)', [tableField, [...nonNullTableValues]]);
126
126
  // there was at least one null, allow null in this equality clause
127
127
  if (nonNullTableValues.length !== tableValues.length) {
@@ -130,27 +130,37 @@ export class PostgresEntityDatabaseAdapter extends BasePostgresEntityDatabaseAda
130
130
  });
131
131
  }
132
132
  }
133
- query = this.applyQueryModifiersToQuery(query, querySelectionModifiers);
134
- return await wrapNativePostgresCallAsync(() => query);
133
+ return result;
135
134
  }
136
- async fetchManyByRawWhereClauseInternalAsync(queryInterface, tableName, rawWhereClause, bindings, querySelectionModifiers) {
137
- let query = queryInterface.select().from(tableName).whereRaw(rawWhereClause, bindings);
135
+ async fetchManyByFieldEqualityConjunctionInternalAsync(queryInterface, tableName, tableFieldSingleValueEqualityOperands, tableFieldMultiValueEqualityOperands, querySelectionModifiers) {
136
+ let query = this.applyFieldEqualityConjunctionWhereClause(queryInterface.select().from(tableName), tableFieldSingleValueEqualityOperands, tableFieldMultiValueEqualityOperands);
138
137
  query = this.applyQueryModifiersToQuery(query, querySelectionModifiers);
139
138
  return await wrapNativePostgresCallAsync(() => query);
140
139
  }
140
+ applySQLFragmentWhereClause(query, sqlFragment) {
141
+ return query.whereRaw(sqlFragment.sql, sqlFragment.getKnexBindings((fieldName) => getDatabaseFieldForEntityField(this.entityConfiguration, fieldName)));
142
+ }
141
143
  async fetchManyBySQLFragmentInternalAsync(queryInterface, tableName, sqlFragment, querySelectionModifiers) {
142
- let query = queryInterface
143
- .select()
144
- .from(tableName)
145
- .whereRaw(sqlFragment.sql, sqlFragment.getKnexBindings((fieldName) => getDatabaseFieldForEntityField(this.entityConfiguration, fieldName)));
144
+ let query = this.applySQLFragmentWhereClause(queryInterface.select().from(tableName), sqlFragment);
146
145
  query = this.applyQueryModifiersToQuery(query, querySelectionModifiers);
147
146
  return await wrapNativePostgresCallAsync(() => query);
148
147
  }
148
+ async countByFieldEqualityConjunctionInternalAsync(queryInterface, tableName, tableFieldSingleValueEqualityOperands, tableFieldMultiValueEqualityOperands) {
149
+ const query = this.applyFieldEqualityConjunctionWhereClause(queryInterface.count('*', { as: RESERVED_ENTITY_COUNT_QUERY_ALIAS }).from(tableName), tableFieldSingleValueEqualityOperands, tableFieldMultiValueEqualityOperands);
150
+ const result = await wrapNativePostgresCallAsync(() => query);
151
+ return parseInt(String(result[0][RESERVED_ENTITY_COUNT_QUERY_ALIAS]), 10);
152
+ }
153
+ async countBySQLFragmentInternalAsync(queryInterface, tableName, sqlFragment) {
154
+ const query = this.applySQLFragmentWhereClause(queryInterface.count('*', { as: RESERVED_ENTITY_COUNT_QUERY_ALIAS }).from(tableName), sqlFragment);
155
+ const result = await wrapNativePostgresCallAsync(() => query);
156
+ return parseInt(String(result[0][RESERVED_ENTITY_COUNT_QUERY_ALIAS]), 10);
157
+ }
149
158
  async insertInternalAsync(queryInterface, tableName, object) {
150
159
  return await wrapNativePostgresCallAsync(() => queryInterface.insert(object).into(tableName).returning('*'));
151
160
  }
152
161
  async updateInternalAsync(queryInterface, tableName, tableIdField, id, object) {
153
- return await wrapNativePostgresCallAsync(() => queryInterface.update(object).into(tableName).where(tableIdField, id).returning('*'));
162
+ const updatedRowCount = await wrapNativePostgresCallAsync(() => queryInterface.update(object).into(tableName).where(tableIdField, id));
163
+ return { updatedRowCount };
154
164
  }
155
165
  async deleteInternalAsync(queryInterface, tableName, tableIdField, id) {
156
166
  return await wrapNativePostgresCallAsync(() => queryInterface.into(tableName).where(tableIdField, id).del());
@@ -1,8 +1,9 @@
1
+ import type { Knex } from 'knex';
1
2
  /**
2
3
  * Supported SQL value types that can be safely parameterized.
3
4
  * This ensures type safety and prevents passing unsupported types to SQL queries.
4
5
  */
5
- export type SupportedSQLValue = string | number | boolean | null | Date | Buffer | bigint | undefined | readonly SupportedSQLValue[] | Readonly<{
6
+ export type SupportedSQLValue = string | number | boolean | null | Date | Buffer | bigint | readonly SupportedSQLValue[] | Readonly<{
6
7
  [key: string]: unknown;
7
8
  }>;
8
9
  /**
@@ -31,7 +32,7 @@ export declare class SQLFragment<TFields extends Record<string, any>> {
31
32
  *
32
33
  * @param getColumnForField - function that resolves an entity field name to its database column name
33
34
  */
34
- getKnexBindings(getColumnForField: (fieldName: keyof TFields) => string): readonly SupportedSQLValue[];
35
+ getKnexBindings(getColumnForField: (fieldName: keyof TFields) => string): readonly Knex.RawBinding[];
35
36
  /**
36
37
  * Combine SQL fragments
37
38
  */
@@ -161,7 +162,7 @@ type PickSupportedSQLValueKeys<T> = {
161
162
  [K in keyof T]: T[K] extends SupportedSQLValue ? K : never;
162
163
  }[keyof T];
163
164
  type PickStringValueKeys<T> = {
164
- [K in keyof T]: T[K] extends string | null | undefined ? K : never;
165
+ [K in keyof T]: T[K] extends string | null ? K : never;
165
166
  }[keyof T];
166
167
  type JsonSerializable = string | number | boolean | null | undefined | readonly JsonSerializable[] | {
167
168
  readonly [key: string]: JsonSerializable;
@@ -175,20 +176,20 @@ type JsonSerializable = string | number | boolean | null | undefined | readonly
175
176
  export declare class SQLChainableFragment<TFields extends Record<string, any>, TValue extends SupportedSQLValue> extends SQLFragment<TFields> {
176
177
  /**
177
178
  * Generates an equality condition (`= value`).
178
- * Automatically converts `null`/`undefined` to `IS NULL`.
179
+ * Automatically converts `null` to `IS NULL`.
179
180
  *
180
181
  * @param value - The value to compare against
181
182
  * @returns A {@link SQLFragment} representing the equality condition
182
183
  */
183
- eq(value: TValue | null | undefined): SQLFragment<TFields>;
184
+ eq(value: TValue | null): SQLFragment<TFields>;
184
185
  /**
185
186
  * Generates an inequality condition (`!= value`).
186
- * Automatically converts `null`/`undefined` to `IS NOT NULL`.
187
+ * Automatically converts `null` to `IS NOT NULL`.
187
188
  *
188
189
  * @param value - The value to compare against
189
190
  * @returns A {@link SQLFragment} representing the inequality condition
190
191
  */
191
- neq(value: TValue | null | undefined): SQLFragment<TFields>;
192
+ neq(value: TValue | null): SQLFragment<TFields>;
192
193
  /**
193
194
  * Generates a greater-than condition (`> value`).
194
195
  *
@@ -312,7 +313,7 @@ declare const ALLOWED_CAST_TYPES: readonly ["int", "integer", "int2", "int4", "i
312
313
  */
313
314
  export type PostgresCastType = (typeof ALLOWED_CAST_TYPES)[number];
314
315
  type ExtractFragmentFields<T> = T extends SQLFragment<infer F> ? F : never;
315
- type FragmentValueNullable<TFragment> = TFragment extends SQLChainableFragment<any, infer TValue> ? TValue | null | undefined : SupportedSQLValue;
316
+ type FragmentValueNullable<TFragment> = TFragment extends SQLChainableFragment<any, infer TValue> ? TValue | null : SupportedSQLValue;
316
317
  type FragmentValue<TFragment> = TFragment extends SQLChainableFragment<any, infer TValue> ? TValue : SupportedSQLValue;
317
318
  type FragmentValueArray<TFragment> = TFragment extends SQLChainableFragment<any, infer TValue> ? readonly TValue[] : readonly SupportedSQLValue[];
318
319
  /**
@@ -477,7 +478,7 @@ declare function isNotNullHelper<TFragment extends SQLFragment<any>>(fragment: T
477
478
  declare function isNotNullHelper<TFields extends Record<string, any>, N extends keyof TFields>(fieldName: N): SQLFragment<TFields>;
478
479
  /**
479
480
  * Generates an equality condition (`= value`) from a fragment.
480
- * Automatically converts `null`/`undefined` to `IS NULL`.
481
+ * Automatically converts `null` to `IS NULL`.
481
482
  *
482
483
  * @param fragment - A SQLFragment or SQLChainableFragment to compare
483
484
  * @param value - The value to compare against
@@ -485,7 +486,7 @@ declare function isNotNullHelper<TFields extends Record<string, any>, N extends
485
486
  declare function eqHelper<TFragment extends SQLFragment<any>>(fragment: TFragment, value: FragmentValueNullable<TFragment>): SQLFragment<ExtractFragmentFields<TFragment>>;
486
487
  /**
487
488
  * Generates an equality condition (`= value`) from a field name.
488
- * Automatically converts `null`/`undefined` to `IS NULL`.
489
+ * Automatically converts `null` to `IS NULL`.
489
490
  *
490
491
  * @param fieldName - The entity field name to compare
491
492
  * @param value - The value to compare against
@@ -493,7 +494,7 @@ declare function eqHelper<TFragment extends SQLFragment<any>>(fragment: TFragmen
493
494
  declare function eqHelper<TFields extends Record<string, any>, N extends PickSupportedSQLValueKeys<TFields>>(fieldName: N, value: TFields[N]): SQLFragment<TFields>;
494
495
  /**
495
496
  * Generates an inequality condition (`!= value`) from a fragment.
496
- * Automatically converts `null`/`undefined` to `IS NOT NULL`.
497
+ * Automatically converts `null` to `IS NOT NULL`.
497
498
  *
498
499
  * @param fragment - A SQLFragment or SQLChainableFragment to compare
499
500
  * @param value - The value to compare against
@@ -501,7 +502,7 @@ declare function eqHelper<TFields extends Record<string, any>, N extends PickSup
501
502
  declare function neqHelper<TFragment extends SQLFragment<any>>(fragment: TFragment, value: FragmentValueNullable<TFragment>): SQLFragment<ExtractFragmentFields<TFragment>>;
502
503
  /**
503
504
  * Generates an inequality condition (`!= value`) from a field name.
504
- * Automatically converts `null`/`undefined` to `IS NOT NULL`.
505
+ * Automatically converts `null` to `IS NOT NULL`.
505
506
  *
506
507
  * @param fieldName - The entity field name to compare
507
508
  * @param value - The value to compare against
@@ -804,11 +805,11 @@ export declare const SQLExpression: {
804
805
  */
805
806
  isNotNull: typeof isNotNullHelper;
806
807
  /**
807
- * Equality operator. Automatically converts null/undefined to IS NULL.
808
+ * Equality operator. Automatically converts null to IS NULL.
808
809
  */
809
810
  eq: typeof eqHelper;
810
811
  /**
811
- * Inequality operator. Automatically converts null/undefined to IS NOT NULL.
812
+ * Inequality operator. Automatically converts null to IS NOT NULL.
812
813
  */
813
814
  neq: typeof neqHelper;
814
815
  /**
@@ -23,6 +23,9 @@ export class SQLFragment {
23
23
  case 'identifier':
24
24
  return b.name;
25
25
  case 'value':
26
+ // Needs a cast since bigint is supported by knex postgres dialect but not all dialects, and thus isn't included
27
+ // in the type. Because we only use the postgres dialect in this adapter, it's safe to allow it here.
28
+ // https://github.com/knex/knex/issues/5013#issuecomment-3368744254
26
29
  return b.value;
27
30
  }
28
31
  });
@@ -98,8 +101,8 @@ export class SQLFragment {
98
101
  * Handles all SupportedSQLValue types.
99
102
  */
100
103
  static formatDebugValue(value) {
101
- // Handle null and undefined
102
- if (value === null || value === undefined) {
104
+ // Handle null
105
+ if (value === null) {
103
106
  return 'NULL';
104
107
  }
105
108
  // Handle primitives
@@ -299,26 +302,26 @@ export function sql(strings, ...values) {
299
302
  export class SQLChainableFragment extends SQLFragment {
300
303
  /**
301
304
  * Generates an equality condition (`= value`).
302
- * Automatically converts `null`/`undefined` to `IS NULL`.
305
+ * Automatically converts `null` to `IS NULL`.
303
306
  *
304
307
  * @param value - The value to compare against
305
308
  * @returns A {@link SQLFragment} representing the equality condition
306
309
  */
307
310
  eq(value) {
308
- if (value === null || value === undefined) {
311
+ if (value === null) {
309
312
  return this.isNull();
310
313
  }
311
314
  return sql `${this} = ${value}`;
312
315
  }
313
316
  /**
314
317
  * Generates an inequality condition (`!= value`).
315
- * Automatically converts `null`/`undefined` to `IS NOT NULL`.
318
+ * Automatically converts `null` to `IS NOT NULL`.
316
319
  *
317
320
  * @param value - The value to compare against
318
321
  * @returns A {@link SQLFragment} representing the inequality condition
319
322
  */
320
323
  neq(value) {
321
- if (value === null || value === undefined) {
324
+ if (value === null) {
322
325
  return this.isNotNull();
323
326
  }
324
327
  return sql `${this} != ${value}`;
@@ -717,11 +720,11 @@ export const SQLExpression = {
717
720
  */
718
721
  isNotNull: isNotNullHelper,
719
722
  /**
720
- * Equality operator. Automatically converts null/undefined to IS NULL.
723
+ * Equality operator. Automatically converts null to IS NULL.
721
724
  */
722
725
  eq: eqHelper,
723
726
  /**
724
- * Inequality operator. Automatically converts null/undefined to IS NOT NULL.
727
+ * Inequality operator. Automatically converts null to IS NOT NULL.
725
728
  */
726
729
  neq: neqHelper,
727
730
  /**