Skip to content

Commit d3242cb

Browse files
committed
fix: upgrade knex and remove undefined value bindings
1 parent 1365b11 commit d3242cb

6 files changed

Lines changed: 44 additions & 67 deletions

File tree

packages/entity-database-adapter-knex/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
},
2828
"dependencies": {
2929
"@expo/entity": "workspace:^",
30-
"knex": "^3.1.0"
30+
"knex": "^3.2.9"
3131
},
3232
"devDependencies": {
3333
"@expo/entity-testing-utils": "workspace:^",

packages/entity-database-adapter-knex/src/PostgresEntityDatabaseAdapter.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ export class PostgresEntityDatabaseAdapter<
9191
.select()
9292
.from(tableName)
9393
.whereRaw(`(??) = ANY(?)`, [
94-
tableColumns[0],
94+
tableColumns[0]!,
9595
tableTuples.map((tableTuple) => tableTuple[0]),
9696
]),
9797
);

packages/entity-database-adapter-knex/src/SQLOperator.ts

Lines changed: 23 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import assert from 'assert';
2+
import type { Knex } from 'knex';
23

34
/**
45
* Supported SQL value types that can be safely parameterized.
@@ -12,7 +13,6 @@ export type SupportedSQLValue =
1213
| Date
1314
| Buffer
1415
| bigint
15-
| undefined // Will be treated as NULL
1616
| readonly SupportedSQLValue[] // For IN clauses and array types
1717
| Readonly<{ [key: string]: unknown }>; // For JSON/JSONB columns
1818

@@ -41,15 +41,18 @@ export class SQLFragment<TFields extends Record<string, any>> {
4141
*/
4242
getKnexBindings(
4343
getColumnForField: (fieldName: keyof TFields) => string,
44-
): readonly SupportedSQLValue[] {
44+
): readonly Knex.RawBinding[] {
4545
return this.bindings.map((b) => {
4646
switch (b.type) {
4747
case 'entityField':
4848
return getColumnForField(b.fieldName);
4949
case 'identifier':
5050
return b.name;
5151
case 'value':
52-
return b.value;
52+
// Needs a cast since bigint is supported by knex postgres dialect but not all dialects, and thus isn't included
53+
// in the type. Because we only use the postgres dialect in this adapter, it's safe to allow it here.
54+
// https://github.com/knex/knex/issues/5013#issuecomment-3368744254
55+
return b.value as Knex.RawBinding;
5356
}
5457
});
5558
}
@@ -133,8 +136,8 @@ export class SQLFragment<TFields extends Record<string, any>> {
133136
* Handles all SupportedSQLValue types.
134137
*/
135138
private static formatDebugValue(value: SupportedSQLValue): string {
136-
// Handle null and undefined
137-
if (value === null || value === undefined) {
139+
// Handle null
140+
if (value === null) {
138141
return 'NULL';
139142
}
140143

@@ -303,7 +306,7 @@ export function sql<TFields extends Record<string, any>>(
303306
strings.forEach((string, i) => {
304307
sqlString += string;
305308
if (i < values.length) {
306-
const value = values[i];
309+
const value = values[i]!;
307310

308311
if (value instanceof SQLFragment) {
309312
// Handle nested SQL fragments
@@ -344,7 +347,7 @@ type PickSupportedSQLValueKeys<T> = {
344347
}[keyof T];
345348

346349
type PickStringValueKeys<T> = {
347-
[K in keyof T]: T[K] extends string | null | undefined ? K : never;
350+
[K in keyof T]: T[K] extends string | null ? K : never;
348351
}[keyof T];
349352

350353
type JsonSerializable =
@@ -368,27 +371,27 @@ export class SQLChainableFragment<
368371
> extends SQLFragment<TFields> {
369372
/**
370373
* Generates an equality condition (`= value`).
371-
* Automatically converts `null`/`undefined` to `IS NULL`.
374+
* Automatically converts `null` to `IS NULL`.
372375
*
373376
* @param value - The value to compare against
374377
* @returns A {@link SQLFragment} representing the equality condition
375378
*/
376-
eq(value: TValue | null | undefined): SQLFragment<TFields> {
377-
if (value === null || value === undefined) {
379+
eq(value: TValue | null): SQLFragment<TFields> {
380+
if (value === null) {
378381
return this.isNull();
379382
}
380383
return sql`${this} = ${value}`;
381384
}
382385

383386
/**
384387
* Generates an inequality condition (`!= value`).
385-
* Automatically converts `null`/`undefined` to `IS NOT NULL`.
388+
* Automatically converts `null` to `IS NOT NULL`.
386389
*
387390
* @param value - The value to compare against
388391
* @returns A {@link SQLFragment} representing the inequality condition
389392
*/
390-
neq(value: TValue | null | undefined): SQLFragment<TFields> {
391-
if (value === null || value === undefined) {
393+
neq(value: TValue | null): SQLFragment<TFields> {
394+
if (value === null) {
392395
return this.isNotNull();
393396
}
394397
return sql`${this} != ${value}`;
@@ -635,9 +638,7 @@ type ExtractFragmentFields<T> = T extends SQLFragment<infer F> ? F : never;
635638
// Conditional value types for expression overloads.
636639
// Uses SQLChainableFragment<any, ...> so that TExpr alone drives inference (single type param).
637640
type FragmentValueNullable<TFragment> =
638-
TFragment extends SQLChainableFragment<any, infer TValue>
639-
? TValue | null | undefined
640-
: SupportedSQLValue;
641+
TFragment extends SQLChainableFragment<any, infer TValue> ? TValue | null : SupportedSQLValue;
641642

642643
type FragmentValue<TFragment> =
643644
TFragment extends SQLChainableFragment<any, infer TValue> ? TValue : SupportedSQLValue;
@@ -950,7 +951,7 @@ function isNotNullHelper<TFields extends Record<string, any>>(
950951

951952
/**
952953
* Generates an equality condition (`= value`) from a fragment.
953-
* Automatically converts `null`/`undefined` to `IS NULL`.
954+
* Automatically converts `null` to `IS NULL`.
954955
*
955956
* @param fragment - A SQLFragment or SQLChainableFragment to compare
956957
* @param value - The value to compare against
@@ -961,7 +962,7 @@ function eqHelper<TFragment extends SQLFragment<any>>(
961962
): SQLFragment<ExtractFragmentFields<TFragment>>;
962963
/**
963964
* Generates an equality condition (`= value`) from a field name.
964-
* Automatically converts `null`/`undefined` to `IS NULL`.
965+
* Automatically converts `null` to `IS NULL`.
965966
*
966967
* @param fieldName - The entity field name to compare
967968
* @param value - The value to compare against
@@ -979,7 +980,7 @@ function eqHelper<TFields extends Record<string, any>>(
979980

980981
/**
981982
* Generates an inequality condition (`!= value`) from a fragment.
982-
* Automatically converts `null`/`undefined` to `IS NOT NULL`.
983+
* Automatically converts `null` to `IS NOT NULL`.
983984
*
984985
* @param fragment - A SQLFragment or SQLChainableFragment to compare
985986
* @param value - The value to compare against
@@ -990,7 +991,7 @@ function neqHelper<TFragment extends SQLFragment<any>>(
990991
): SQLFragment<ExtractFragmentFields<TFragment>>;
991992
/**
992993
* Generates an inequality condition (`!= value`) from a field name.
993-
* Automatically converts `null`/`undefined` to `IS NOT NULL`.
994+
* Automatically converts `null` to `IS NOT NULL`.
994995
*
995996
* @param fieldName - The entity field name to compare
996997
* @param value - The value to compare against
@@ -1521,12 +1522,12 @@ export const SQLExpression = {
15211522
isNotNull: isNotNullHelper,
15221523

15231524
/**
1524-
* Equality operator. Automatically converts null/undefined to IS NULL.
1525+
* Equality operator. Automatically converts null to IS NULL.
15251526
*/
15261527
eq: eqHelper,
15271528

15281529
/**
1529-
* Inequality operator. Automatically converts null/undefined to IS NOT NULL.
1530+
* Inequality operator. Automatically converts null to IS NOT NULL.
15301531
*/
15311532
neq: neqHelper,
15321533

packages/entity-database-adapter-knex/src/__tests__/SQLOperator-test.ts

Lines changed: 2 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -258,12 +258,11 @@ describe('SQLOperator', () => {
258258
});
259259

260260
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 (?, ?, ?, ?, ?, ?, ?, ?)', [
262262
{ type: 'value', value: 'string' },
263263
{ type: 'value', value: 123 },
264264
{ type: 'value', value: true },
265265
{ type: 'value', value: null },
266-
{ type: 'value', value: undefined },
267266
{ type: 'value', value: new Date('2024-01-01T00:00:00.000Z') },
268267
{ type: 'value', value: Buffer.from('hello') },
269268
{ type: 'value', value: BigInt(999) },
@@ -272,7 +271,7 @@ describe('SQLOperator', () => {
272271

273272
const text = fragment.getDebugString();
274273
expect(text).toBe(
275-
"INSERT INTO test VALUES ('string', 123, TRUE, NULL, NULL, '2024-01-01T00:00:00.000Z', '\\x68656c6c6f', 999, ARRAY[1, 2, 3])",
274+
"INSERT INTO test VALUES ('string', 123, TRUE, NULL, '2024-01-01T00:00:00.000Z', '\\x68656c6c6f', 999, ARRAY[1, 2, 3])",
276275
);
277276
});
278277

@@ -763,13 +762,6 @@ describe('SQLOperator', () => {
763762
expect(fragment.getKnexBindings(getColumnForField)).toEqual(['nullable_field']);
764763
});
765764

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-
773765
it('accepts a SQLFragment expression', () => {
774766
const fragment = SQLExpression.eq(sql<TestFields>`${entityField('stringField')}`, 'active');
775767
expect(fragment.sql).toBe('?? = ?');
@@ -801,13 +793,6 @@ describe('SQLOperator', () => {
801793
expect(fragment.getKnexBindings(getColumnForField)).toEqual(['nullable_field']);
802794
});
803795

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-
811796
it('accepts a SQLFragment expression', () => {
812797
const fragment = SQLExpression.neq(
813798
sql<TestFields>`${entityField('stringField')}`,
@@ -1131,12 +1116,6 @@ describe('SQLOperator', () => {
11311116
expect(fragment.getKnexBindings(getColumnForField)).toEqual(['string_field']);
11321117
});
11331118

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-
11401119
it('neq(value)', () => {
11411120
const fragment = makeExpr<string>(stringFieldFragment()).neq('deleted');
11421121
expect(fragment.sql).toBe('?? != ?');
@@ -1149,12 +1128,6 @@ describe('SQLOperator', () => {
11491128
expect(fragment.getKnexBindings(getColumnForField)).toEqual(['string_field']);
11501129
});
11511130

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-
11581131
it('gt(value)', () => {
11591132
const fragment = makeExpr<number>(intFieldFragment()).gt(10);
11601133
expect(fragment.sql).toBe('?? > ?');

packages/entity-database-adapter-knex/src/internal/EntityKnexDataManager.ts

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -480,17 +480,16 @@ export class EntityKnexDataManager<
480480
baseWhere: SQLFragment<TFields> | undefined,
481481
cursorCondition: SQLFragment<TFields> | null,
482482
): SQLFragment<TFields> {
483-
const conditions = [baseWhere, cursorCondition].filter((it) => !!it);
484-
if (conditions.length === 0) {
485-
return sql`TRUE`;
483+
if (!baseWhere) {
484+
return cursorCondition ?? sql`TRUE`;
486485
}
487-
if (conditions.length === 1) {
488-
return conditions[0]!;
486+
487+
if (!cursorCondition) {
488+
return baseWhere;
489489
}
490-
// Wrap baseWhere in parens if combining with cursor condition
491-
// We know we have exactly 2 conditions at this point
492-
const [first, second] = conditions;
493-
return sql`(${first}) AND ${second}`;
490+
491+
// Wrap baseWhere in parens when combining with cursor condition
492+
return sql`(${baseWhere}) AND ${cursorCondition}`;
494493
}
495494

496495
private augmentOrderByIfNecessary(

yarn.lock

Lines changed: 9 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)