@expo/entity-database-adapter-knex 0.59.0 → 0.60.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.
@@ -8,9 +8,11 @@ import {
8
8
  unsafeRaw,
9
9
  sql,
10
10
  SQLEntityField,
11
+ SQLExpression,
11
12
  SQLFragment,
12
13
  SQLFragmentHelpers,
13
14
  SQLIdentifier,
15
+ expression,
14
16
  } from '../SQLOperator';
15
17
  import { TestFields, testEntityConfiguration } from './fixtures/TestEntity';
16
18
 
@@ -497,7 +499,7 @@ describe('SQLOperator', () => {
497
499
  it('handles empty array', () => {
498
500
  const fragment = SQLFragmentHelpers.inArray('stringField', []);
499
501
 
500
- expect(fragment.sql).toBe('1 = 0'); // Always false
502
+ expect(fragment.sql).toBe('FALSE'); // Always false
501
503
  expect(fragment.getKnexBindings(getColumnForField)).toEqual([]);
502
504
  });
503
505
  });
@@ -517,7 +519,7 @@ describe('SQLOperator', () => {
517
519
  it('handles empty array', () => {
518
520
  const fragment = SQLFragmentHelpers.notInArray('stringField', []);
519
521
 
520
- expect(fragment.sql).toBe('1 = 1'); // Always true
522
+ expect(fragment.sql).toBe('TRUE'); // Always true
521
523
  expect(fragment.getKnexBindings(getColumnForField)).toEqual([]);
522
524
  });
523
525
  });
@@ -536,7 +538,7 @@ describe('SQLOperator', () => {
536
538
  it('handles empty array', () => {
537
539
  const fragment = SQLFragmentHelpers.anyArray('stringField', []);
538
540
 
539
- expect(fragment.sql).toBe('1 = 0'); // Always false
541
+ expect(fragment.sql).toBe('FALSE'); // Always false
540
542
  expect(fragment.getKnexBindings(getColumnForField)).toEqual([]);
541
543
  });
542
544
  });
@@ -829,7 +831,7 @@ describe('SQLOperator', () => {
829
831
  it('handles empty conditions in AND', () => {
830
832
  const fragment = SQLFragmentHelpers.and();
831
833
 
832
- expect(fragment.sql).toBe('1 = 1');
834
+ expect(fragment.sql).toBe('TRUE');
833
835
  expect(fragment.getKnexBindings(getColumnForField)).toEqual([]);
834
836
  });
835
837
  });
@@ -855,7 +857,7 @@ describe('SQLOperator', () => {
855
857
  it('handles empty conditions in OR', () => {
856
858
  const fragment = SQLFragmentHelpers.or();
857
859
 
858
- expect(fragment.sql).toBe('1 = 0');
860
+ expect(fragment.sql).toBe('FALSE');
859
861
  expect(fragment.getKnexBindings(getColumnForField)).toEqual([]);
860
862
  });
861
863
  });
@@ -908,5 +910,386 @@ describe('SQLOperator', () => {
908
910
  ]);
909
911
  });
910
912
  });
913
+
914
+ describe(expression, () => {
915
+ it('wraps a field name into an SQLExpression', () => {
916
+ const fragment = expression<TestFields>('stringField').eq('active');
917
+
918
+ expect(fragment.sql).toBe('?? = ?');
919
+ expect(fragment.getKnexBindings(getColumnForField)).toEqual(['string_field', 'active']);
920
+ });
921
+
922
+ it('wraps a SQLFragment into an SQLExpression', () => {
923
+ const raw = sql<TestFields>`LOWER(${entityField<TestFields>('stringField')})`;
924
+ const fragment = expression(raw).eq('test');
925
+
926
+ expect(fragment.sql).toBe('LOWER(??) = ?');
927
+ expect(fragment.getKnexBindings(getColumnForField)).toEqual(['string_field', 'test']);
928
+ });
929
+ });
930
+ });
931
+
932
+ describe(SQLExpression, () => {
933
+ // Use a simple expression for testing all fluent methods.
934
+ // Since fluent methods live on SQLExpression and behave identically regardless of
935
+ // how the expression was constructed, we only need to test them once.
936
+ const makeStringFieldExpr = (): SQLExpression<TestFields> =>
937
+ expression<TestFields>('stringField');
938
+
939
+ describe('comparison methods', () => {
940
+ it('eq(value)', () => {
941
+ const fragment = makeStringFieldExpr().eq('active');
942
+ expect(fragment.sql).toBe('?? = ?');
943
+ expect(fragment.getKnexBindings(getColumnForField)).toEqual(['string_field', 'active']);
944
+ });
945
+
946
+ it('eq(null) uses IS NULL', () => {
947
+ const fragment = makeStringFieldExpr().eq(null);
948
+ expect(fragment.sql).toBe('?? IS NULL');
949
+ expect(fragment.getKnexBindings(getColumnForField)).toEqual(['string_field']);
950
+ });
951
+
952
+ it('eq(undefined) uses IS NULL', () => {
953
+ const fragment = makeStringFieldExpr().eq(undefined);
954
+ expect(fragment.sql).toBe('?? IS NULL');
955
+ expect(fragment.getKnexBindings(getColumnForField)).toEqual(['string_field']);
956
+ });
957
+
958
+ it('neq(value)', () => {
959
+ const fragment = makeStringFieldExpr().neq('deleted');
960
+ expect(fragment.sql).toBe('?? != ?');
961
+ expect(fragment.getKnexBindings(getColumnForField)).toEqual(['string_field', 'deleted']);
962
+ });
963
+
964
+ it('neq(null) uses IS NOT NULL', () => {
965
+ const fragment = makeStringFieldExpr().neq(null);
966
+ expect(fragment.sql).toBe('?? IS NOT NULL');
967
+ expect(fragment.getKnexBindings(getColumnForField)).toEqual(['string_field']);
968
+ });
969
+
970
+ it('neq(undefined) uses IS NOT NULL', () => {
971
+ const fragment = makeStringFieldExpr().neq(undefined);
972
+ expect(fragment.sql).toBe('?? IS NOT NULL');
973
+ expect(fragment.getKnexBindings(getColumnForField)).toEqual(['string_field']);
974
+ });
975
+
976
+ it('gt(value)', () => {
977
+ const fragment = makeStringFieldExpr().gt(10);
978
+ expect(fragment.sql).toBe('?? > ?');
979
+ expect(fragment.getKnexBindings(getColumnForField)).toEqual(['string_field', 10]);
980
+ });
981
+
982
+ it('gte(value)', () => {
983
+ const fragment = makeStringFieldExpr().gte(10);
984
+ expect(fragment.sql).toBe('?? >= ?');
985
+ expect(fragment.getKnexBindings(getColumnForField)).toEqual(['string_field', 10]);
986
+ });
987
+
988
+ it('lt(value)', () => {
989
+ const fragment = makeStringFieldExpr().lt(100);
990
+ expect(fragment.sql).toBe('?? < ?');
991
+ expect(fragment.getKnexBindings(getColumnForField)).toEqual(['string_field', 100]);
992
+ });
993
+
994
+ it('lte(value)', () => {
995
+ const fragment = makeStringFieldExpr().lte(100);
996
+ expect(fragment.sql).toBe('?? <= ?');
997
+ expect(fragment.getKnexBindings(getColumnForField)).toEqual(['string_field', 100]);
998
+ });
999
+
1000
+ it('isNull()', () => {
1001
+ const fragment = makeStringFieldExpr().isNull();
1002
+ expect(fragment.sql).toBe('?? IS NULL');
1003
+ expect(fragment.getKnexBindings(getColumnForField)).toEqual(['string_field']);
1004
+ });
1005
+
1006
+ it('isNotNull()', () => {
1007
+ const fragment = makeStringFieldExpr().isNotNull();
1008
+ expect(fragment.sql).toBe('?? IS NOT NULL');
1009
+ expect(fragment.getKnexBindings(getColumnForField)).toEqual(['string_field']);
1010
+ });
1011
+ });
1012
+
1013
+ describe('pattern matching methods', () => {
1014
+ it('like(pattern)', () => {
1015
+ const fragment = makeStringFieldExpr().like('%test%');
1016
+ expect(fragment.sql).toBe('?? LIKE ?');
1017
+ expect(fragment.getKnexBindings(getColumnForField)).toEqual(['string_field', '%test%']);
1018
+ });
1019
+
1020
+ it('notLike(pattern)', () => {
1021
+ const fragment = makeStringFieldExpr().notLike('%test%');
1022
+ expect(fragment.sql).toBe('?? NOT LIKE ?');
1023
+ expect(fragment.getKnexBindings(getColumnForField)).toEqual(['string_field', '%test%']);
1024
+ });
1025
+
1026
+ it('ilike(pattern)', () => {
1027
+ const fragment = makeStringFieldExpr().ilike('%test%');
1028
+ expect(fragment.sql).toBe('?? ILIKE ?');
1029
+ expect(fragment.getKnexBindings(getColumnForField)).toEqual(['string_field', '%test%']);
1030
+ });
1031
+
1032
+ it('notIlike(pattern)', () => {
1033
+ const fragment = makeStringFieldExpr().notIlike('%test%');
1034
+ expect(fragment.sql).toBe('?? NOT ILIKE ?');
1035
+ expect(fragment.getKnexBindings(getColumnForField)).toEqual(['string_field', '%test%']);
1036
+ });
1037
+ });
1038
+
1039
+ describe('collection methods', () => {
1040
+ it('inArray(values)', () => {
1041
+ const fragment = makeStringFieldExpr().inArray(['a', 'b']);
1042
+ expect(fragment.sql).toBe('?? IN (?, ?)');
1043
+ expect(fragment.getKnexBindings(getColumnForField)).toEqual(['string_field', 'a', 'b']);
1044
+ });
1045
+
1046
+ it('inArray([]) returns always-false', () => {
1047
+ const fragment = makeStringFieldExpr().inArray([]);
1048
+ expect(fragment.sql).toBe('FALSE');
1049
+ });
1050
+
1051
+ it('notInArray(values)', () => {
1052
+ const fragment = makeStringFieldExpr().notInArray(['x']);
1053
+ expect(fragment.sql).toBe('?? NOT IN (?)');
1054
+ expect(fragment.getKnexBindings(getColumnForField)).toEqual(['string_field', 'x']);
1055
+ });
1056
+
1057
+ it('notInArray([]) returns always-true', () => {
1058
+ const fragment = makeStringFieldExpr().notInArray([]);
1059
+ expect(fragment.sql).toBe('TRUE');
1060
+ });
1061
+
1062
+ it('anyArray(values)', () => {
1063
+ const fragment = makeStringFieldExpr().anyArray(['a', 'b']);
1064
+ expect(fragment.sql).toBe('?? = ANY(?)');
1065
+ expect(fragment.getKnexBindings(getColumnForField)).toEqual(['string_field', ['a', 'b']]);
1066
+ });
1067
+
1068
+ it('anyArray([]) returns always-false', () => {
1069
+ const fragment = makeStringFieldExpr().anyArray([]);
1070
+ expect(fragment.sql).toBe('FALSE');
1071
+ });
1072
+ });
1073
+
1074
+ describe('range methods', () => {
1075
+ it('between(min, max)', () => {
1076
+ const fragment = makeStringFieldExpr().between(1, 100);
1077
+ expect(fragment.sql).toBe('?? BETWEEN ? AND ?');
1078
+ expect(fragment.getKnexBindings(getColumnForField)).toEqual(['string_field', 1, 100]);
1079
+ });
1080
+
1081
+ it('notBetween(min, max)', () => {
1082
+ const fragment = makeStringFieldExpr().notBetween(1, 100);
1083
+ expect(fragment.sql).toBe('?? NOT BETWEEN ? AND ?');
1084
+ expect(fragment.getKnexBindings(getColumnForField)).toEqual(['string_field', 1, 100]);
1085
+ });
1086
+ });
1087
+
1088
+ describe('helpers that return SQLExpression', () => {
1089
+ it('jsonPath returns an SQLExpression with correct base SQL', () => {
1090
+ const expr = SQLFragmentHelpers.jsonPath<TestFields>('stringField', 'key');
1091
+ expect(expr).toBeInstanceOf(SQLExpression);
1092
+ expect(expr.sql).toBe('??->?');
1093
+ expect(expr.getKnexBindings(getColumnForField)).toEqual(['string_field', 'key']);
1094
+ });
1095
+
1096
+ it('jsonPathText returns an SQLExpression with correct base SQL', () => {
1097
+ const expr = SQLFragmentHelpers.jsonPathText<TestFields>('stringField', 'email');
1098
+ expect(expr).toBeInstanceOf(SQLExpression);
1099
+ expect(expr.sql).toBe('??->>?');
1100
+ expect(expr.getKnexBindings(getColumnForField)).toEqual(['string_field', 'email']);
1101
+ });
1102
+
1103
+ it('jsonDeepPath returns an SQLExpression with correct base SQL', () => {
1104
+ const expr = SQLFragmentHelpers.jsonDeepPath<TestFields>('stringField', [
1105
+ 'user',
1106
+ 'address',
1107
+ 'city',
1108
+ ]);
1109
+ expect(expr).toBeInstanceOf(SQLExpression);
1110
+ expect(expr.sql).toBe('?? #> ?');
1111
+ expect(expr.getKnexBindings(getColumnForField)).toEqual([
1112
+ 'string_field',
1113
+ '{user,address,city}',
1114
+ ]);
1115
+ });
1116
+
1117
+ it('jsonDeepPath properly quotes path elements with special characters', () => {
1118
+ const fragment = SQLFragmentHelpers.jsonDeepPath<TestFields>('stringField', [
1119
+ 'user',
1120
+ 'first,last',
1121
+ 'na}me',
1122
+ ]);
1123
+
1124
+ expect(fragment.getKnexBindings(getColumnForField)).toEqual([
1125
+ 'string_field',
1126
+ '{user,"first,last","na}me"}',
1127
+ ]);
1128
+ });
1129
+
1130
+ it('jsonDeepPath properly quotes empty path elements', () => {
1131
+ const fragment = SQLFragmentHelpers.jsonDeepPath<TestFields>('stringField', ['user', '']);
1132
+
1133
+ expect(fragment.getKnexBindings(getColumnForField)).toEqual(['string_field', '{user,""}']);
1134
+ });
1135
+
1136
+ it('jsonDeepPath properly escapes quotes and backslashes in path elements', () => {
1137
+ const fragment = SQLFragmentHelpers.jsonDeepPath<TestFields>('stringField', [
1138
+ 'key"with"quotes',
1139
+ 'back\\slash',
1140
+ ]);
1141
+
1142
+ expect(fragment.getKnexBindings(getColumnForField)).toEqual([
1143
+ 'string_field',
1144
+ '{"key\\"with\\"quotes","back\\\\slash"}',
1145
+ ]);
1146
+ });
1147
+
1148
+ it('jsonDeepPathText returns an SQLExpression with correct base SQL', () => {
1149
+ const expr = SQLFragmentHelpers.jsonDeepPathText<TestFields>('stringField', [
1150
+ 'user',
1151
+ 'address',
1152
+ 'city',
1153
+ ]);
1154
+ expect(expr).toBeInstanceOf(SQLExpression);
1155
+ expect(expr.sql).toBe('?? #>> ?');
1156
+ expect(expr.getKnexBindings(getColumnForField)).toEqual([
1157
+ 'string_field',
1158
+ '{user,address,city}',
1159
+ ]);
1160
+ });
1161
+
1162
+ it('cast returns an SQLExpression with correct base SQL', () => {
1163
+ const jsonExpr = SQLFragmentHelpers.jsonPath<TestFields>('stringField', 'count');
1164
+ const expr = SQLFragmentHelpers.cast(jsonExpr, 'int');
1165
+ expect(expr).toBeInstanceOf(SQLExpression);
1166
+ expect(expr.sql).toBe('(??->?)::int');
1167
+ expect(expr.getKnexBindings(getColumnForField)).toEqual(['string_field', 'count']);
1168
+ });
1169
+
1170
+ it('cast rejects unsupported type names', () => {
1171
+ const expr = SQLFragmentHelpers.jsonPath<TestFields>('stringField', 'count');
1172
+ expect(() => SQLFragmentHelpers.cast(expr, 'int; DROP TABLE users' as any)).toThrow(
1173
+ 'cast: unsupported type name',
1174
+ );
1175
+ });
1176
+
1177
+ it('coalesce returns an SQLExpression with correct base SQL', () => {
1178
+ const expr = SQLFragmentHelpers.coalesce<TestFields>(
1179
+ sql`${entityField<TestFields>('nullableField')}`,
1180
+ 'default',
1181
+ );
1182
+ expect(expr).toBeInstanceOf(SQLExpression);
1183
+ expect(expr.sql).toBe('COALESCE(??, ?)');
1184
+ expect(expr.getKnexBindings(getColumnForField)).toEqual(['nullable_field', 'default']);
1185
+ });
1186
+
1187
+ it('coalesce with multiple expressions', () => {
1188
+ const fragment = SQLFragmentHelpers.coalesce<TestFields>(
1189
+ sql`${entityField<TestFields>('nullableField')}`,
1190
+ sql`${entityField<TestFields>('stringField')}`,
1191
+ 'fallback',
1192
+ );
1193
+ expect(fragment.sql).toBe('COALESCE(??, ??, ?)');
1194
+ expect(fragment.getKnexBindings(getColumnForField)).toEqual([
1195
+ 'nullable_field',
1196
+ 'string_field',
1197
+ 'fallback',
1198
+ ]);
1199
+ });
1200
+
1201
+ it('lower returns an SQLExpression with correct base SQL', () => {
1202
+ const expr = SQLFragmentHelpers.lower<TestFields>('stringField');
1203
+ expect(expr).toBeInstanceOf(SQLExpression);
1204
+ expect(expr.sql).toBe('LOWER(??)');
1205
+ expect(expr.getKnexBindings(getColumnForField)).toEqual(['string_field']);
1206
+ });
1207
+
1208
+ it('lower accepts a SQLFragment', () => {
1209
+ const fragment = SQLFragmentHelpers.lower(
1210
+ SQLFragmentHelpers.jsonPathText<TestFields>('stringField', 'email'),
1211
+ );
1212
+ expect(fragment.sql).toBe('LOWER(??->>?)');
1213
+ expect(fragment.getKnexBindings(getColumnForField)).toEqual(['string_field', 'email']);
1214
+ });
1215
+
1216
+ it('upper returns an SQLExpression with correct base SQL', () => {
1217
+ const expr = SQLFragmentHelpers.upper<TestFields>('stringField');
1218
+ expect(expr).toBeInstanceOf(SQLExpression);
1219
+ expect(expr.sql).toBe('UPPER(??)');
1220
+ expect(expr.getKnexBindings(getColumnForField)).toEqual(['string_field']);
1221
+ });
1222
+
1223
+ it('upper accepts a SQLFragment', () => {
1224
+ const fragment = SQLFragmentHelpers.upper(
1225
+ SQLFragmentHelpers.jsonPathText<TestFields>('stringField', 'email'),
1226
+ );
1227
+ expect(fragment.sql).toBe('UPPER(??->>?)');
1228
+ expect(fragment.getKnexBindings(getColumnForField)).toEqual(['string_field', 'email']);
1229
+ });
1230
+
1231
+ it('trim returns an SQLExpression with correct base SQL', () => {
1232
+ const expr = SQLFragmentHelpers.trim<TestFields>('stringField');
1233
+ expect(expr).toBeInstanceOf(SQLExpression);
1234
+ expect(expr.sql).toBe('TRIM(??)');
1235
+ expect(expr.getKnexBindings(getColumnForField)).toEqual(['string_field']);
1236
+ });
1237
+
1238
+ it('trim accepts a SQLFragment', () => {
1239
+ const fragment = SQLFragmentHelpers.trim(
1240
+ SQLFragmentHelpers.jsonPathText<TestFields>('stringField', 'name'),
1241
+ );
1242
+ expect(fragment.sql).toBe('TRIM(??->>?)');
1243
+ expect(fragment.getKnexBindings(getColumnForField)).toEqual(['string_field', 'name']);
1244
+ });
1245
+
1246
+ it('SQLExpression still works as a SQLFragment in sql template', () => {
1247
+ const path = SQLFragmentHelpers.jsonPath<TestFields>('stringField', 'key');
1248
+ const fragment = sql`${path} IS NOT NULL`;
1249
+
1250
+ expect(fragment.sql).toBe('??->? IS NOT NULL');
1251
+ expect(fragment.getKnexBindings(getColumnForField)).toEqual(['string_field', 'key']);
1252
+ });
1253
+ });
1254
+
1255
+ describe('composing multiple expression helpers', () => {
1256
+ it('lower(trim(field)).eq(value)', () => {
1257
+ const fragment = SQLFragmentHelpers.lower(
1258
+ SQLFragmentHelpers.trim<TestFields>('stringField'),
1259
+ ).eq('hello');
1260
+
1261
+ expect(fragment.sql).toBe('LOWER(TRIM(??)) = ?');
1262
+ expect(fragment.getKnexBindings(getColumnForField)).toEqual(['string_field', 'hello']);
1263
+ });
1264
+
1265
+ it('cast(jsonDeepPath(...), type).gt(value)', () => {
1266
+ const fragment = SQLFragmentHelpers.cast(
1267
+ SQLFragmentHelpers.jsonDeepPath<TestFields>('stringField', ['stats', 'count']),
1268
+ 'int',
1269
+ ).gt(10);
1270
+
1271
+ expect(fragment.sql).toBe('(?? #> ?)::int > ?');
1272
+ expect(fragment.getKnexBindings(getColumnForField)).toEqual([
1273
+ 'string_field',
1274
+ '{stats,count}',
1275
+ 10,
1276
+ ]);
1277
+ });
1278
+
1279
+ it('coalesce(jsonPathText(...), default).ilike(pattern)', () => {
1280
+ const fragment = SQLFragmentHelpers.coalesce(
1281
+ SQLFragmentHelpers.jsonPathText<TestFields>('stringField', 'name'),
1282
+ '',
1283
+ ).ilike('%test%');
1284
+
1285
+ expect(fragment.sql).toBe('COALESCE(??->>?, ?) ILIKE ?');
1286
+ expect(fragment.getKnexBindings(getColumnForField)).toEqual([
1287
+ 'string_field',
1288
+ 'name',
1289
+ '',
1290
+ '%test%',
1291
+ ]);
1292
+ });
1293
+ });
911
1294
  });
912
1295
  });
@@ -487,7 +487,7 @@ export class EntityKnexDataManager<
487
487
  ): SQLFragment<TFields> {
488
488
  const conditions = [baseWhere, cursorCondition].filter((it) => !!it);
489
489
  if (conditions.length === 0) {
490
- return sql`1 = 1`;
490
+ return sql`TRUE`;
491
491
  }
492
492
  if (conditions.length === 1) {
493
493
  return conditions[0]!;
@@ -775,7 +775,7 @@ export class EntityKnexDataManager<
775
775
  ];
776
776
 
777
777
  return {
778
- searchWhere: conditions.length > 0 ? SQLFragmentHelpers.or(...conditions) : sql`1 = 0`,
778
+ searchWhere: conditions.length > 0 ? SQLFragmentHelpers.or(...conditions) : sql`FALSE`,
779
779
  searchOrderByClauses,
780
780
  };
781
781
  }