pqb 0.27.1 → 0.27.2

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/dist/index.js CHANGED
@@ -102,25 +102,25 @@ function sqlQueryArgsToExpression(args) {
102
102
  return Array.isArray(args[0]) ? new RawSQL(args) : args[0];
103
103
  }
104
104
 
105
- var __defProp$g = Object.defineProperty;
106
- var __defProps$a = Object.defineProperties;
107
- var __getOwnPropDescs$a = Object.getOwnPropertyDescriptors;
108
- var __getOwnPropSymbols$h = Object.getOwnPropertySymbols;
109
- var __hasOwnProp$h = Object.prototype.hasOwnProperty;
110
- var __propIsEnum$h = Object.prototype.propertyIsEnumerable;
111
- var __defNormalProp$g = (obj, key, value) => key in obj ? __defProp$g(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
112
- var __spreadValues$g = (a, b) => {
105
+ var __defProp$h = Object.defineProperty;
106
+ var __defProps$b = Object.defineProperties;
107
+ var __getOwnPropDescs$b = Object.getOwnPropertyDescriptors;
108
+ var __getOwnPropSymbols$i = Object.getOwnPropertySymbols;
109
+ var __hasOwnProp$i = Object.prototype.hasOwnProperty;
110
+ var __propIsEnum$i = Object.prototype.propertyIsEnumerable;
111
+ var __defNormalProp$h = (obj, key, value) => key in obj ? __defProp$h(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
112
+ var __spreadValues$h = (a, b) => {
113
113
  for (var prop in b || (b = {}))
114
- if (__hasOwnProp$h.call(b, prop))
115
- __defNormalProp$g(a, prop, b[prop]);
116
- if (__getOwnPropSymbols$h)
117
- for (var prop of __getOwnPropSymbols$h(b)) {
118
- if (__propIsEnum$h.call(b, prop))
119
- __defNormalProp$g(a, prop, b[prop]);
114
+ if (__hasOwnProp$i.call(b, prop))
115
+ __defNormalProp$h(a, prop, b[prop]);
116
+ if (__getOwnPropSymbols$i)
117
+ for (var prop of __getOwnPropSymbols$i(b)) {
118
+ if (__propIsEnum$i.call(b, prop))
119
+ __defNormalProp$h(a, prop, b[prop]);
120
120
  }
121
121
  return a;
122
122
  };
123
- var __spreadProps$a = (a, b) => __defProps$a(a, __getOwnPropDescs$a(b));
123
+ var __spreadProps$b = (a, b) => __defProps$b(a, __getOwnPropDescs$b(b));
124
124
  class ColumnType extends orchidCore.ColumnTypeBase {
125
125
  /**
126
126
  * Mark the column as a primary key.
@@ -146,7 +146,7 @@ class ColumnType extends orchidCore.ColumnTypeBase {
146
146
  return orchidCore.setColumnData(this, "isPrimaryKey", true);
147
147
  }
148
148
  foreignKey(fnOrTable, column, options = orchidCore.emptyObject) {
149
- const item = typeof fnOrTable === "string" ? __spreadValues$g({ table: fnOrTable, columns: [column] }, options) : __spreadValues$g({ fn: fnOrTable, columns: [column] }, options);
149
+ const item = typeof fnOrTable === "string" ? __spreadValues$h({ table: fnOrTable, columns: [column] }, options) : __spreadValues$h({ fn: fnOrTable, columns: [column] }, options);
150
150
  return orchidCore.pushColumnData(this, "foreignKeys", item);
151
151
  }
152
152
  toSQL() {
@@ -203,10 +203,10 @@ class ColumnType extends orchidCore.ColumnTypeBase {
203
203
  * @param options - index options
204
204
  */
205
205
  searchIndex(options) {
206
- return orchidCore.pushColumnData(this, "indexes", __spreadValues$g(__spreadValues$g({}, options), this.dataType === "tsvector" ? { using: "GIN" } : { tsVector: true }));
206
+ return orchidCore.pushColumnData(this, "indexes", __spreadValues$h(__spreadValues$h({}, options), this.dataType === "tsvector" ? { using: "GIN" } : { tsVector: true }));
207
207
  }
208
208
  unique(options = {}) {
209
- return orchidCore.pushColumnData(this, "indexes", __spreadProps$a(__spreadValues$g({}, options), { unique: true }));
209
+ return orchidCore.pushColumnData(this, "indexes", __spreadProps$b(__spreadValues$h({}, options), { unique: true }));
210
210
  }
211
211
  comment(comment) {
212
212
  return orchidCore.setColumnData(this, "comment", comment);
@@ -244,25 +244,25 @@ class ColumnType extends orchidCore.ColumnTypeBase {
244
244
  }
245
245
  }
246
246
 
247
- var __defProp$f = Object.defineProperty;
248
- var __defProps$9 = Object.defineProperties;
249
- var __getOwnPropDescs$9 = Object.getOwnPropertyDescriptors;
250
- var __getOwnPropSymbols$g = Object.getOwnPropertySymbols;
251
- var __hasOwnProp$g = Object.prototype.hasOwnProperty;
252
- var __propIsEnum$g = Object.prototype.propertyIsEnumerable;
253
- var __defNormalProp$f = (obj, key, value) => key in obj ? __defProp$f(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
254
- var __spreadValues$f = (a, b) => {
247
+ var __defProp$g = Object.defineProperty;
248
+ var __defProps$a = Object.defineProperties;
249
+ var __getOwnPropDescs$a = Object.getOwnPropertyDescriptors;
250
+ var __getOwnPropSymbols$h = Object.getOwnPropertySymbols;
251
+ var __hasOwnProp$h = Object.prototype.hasOwnProperty;
252
+ var __propIsEnum$h = Object.prototype.propertyIsEnumerable;
253
+ var __defNormalProp$g = (obj, key, value) => key in obj ? __defProp$g(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
254
+ var __spreadValues$g = (a, b) => {
255
255
  for (var prop in b || (b = {}))
256
- if (__hasOwnProp$g.call(b, prop))
257
- __defNormalProp$f(a, prop, b[prop]);
258
- if (__getOwnPropSymbols$g)
259
- for (var prop of __getOwnPropSymbols$g(b)) {
260
- if (__propIsEnum$g.call(b, prop))
261
- __defNormalProp$f(a, prop, b[prop]);
256
+ if (__hasOwnProp$h.call(b, prop))
257
+ __defNormalProp$g(a, prop, b[prop]);
258
+ if (__getOwnPropSymbols$h)
259
+ for (var prop of __getOwnPropSymbols$h(b)) {
260
+ if (__propIsEnum$h.call(b, prop))
261
+ __defNormalProp$g(a, prop, b[prop]);
262
262
  }
263
263
  return a;
264
264
  };
265
- var __spreadProps$9 = (a, b) => __defProps$9(a, __getOwnPropDescs$9(b));
265
+ var __spreadProps$a = (a, b) => __defProps$a(a, __getOwnPropDescs$a(b));
266
266
  const knownDefaults = {
267
267
  current_timestamp: "now()",
268
268
  "transaction_timestamp()": "now()"
@@ -276,7 +276,7 @@ const simplifyColumnDefault = (value) => {
276
276
  };
277
277
  const instantiateColumn = (typeFn, params) => {
278
278
  const column = typeFn();
279
- Object.assign(column.data, __spreadProps$9(__spreadValues$f({}, params), {
279
+ Object.assign(column.data, __spreadProps$a(__spreadValues$g({}, params), {
280
280
  default: simplifyColumnDefault(params.default)
281
281
  }));
282
282
  return column;
@@ -993,14 +993,14 @@ const processWhere = (ands, ctx, table, query, data, quotedAs) => {
993
993
  const joinItems = Array.isArray(value[0]) ? value : [value];
994
994
  const joinSet = joinItems.length > 1 ? /* @__PURE__ */ new Set() : null;
995
995
  for (const args of joinItems) {
996
- const { target, conditions } = processJoinItem(
996
+ const { target, on } = processJoinItem(
997
997
  ctx,
998
998
  table,
999
999
  query,
1000
1000
  args,
1001
1001
  quotedAs
1002
1002
  );
1003
- const sql = `EXISTS (SELECT 1 FROM ${target} WHERE ${conditions})`;
1003
+ const sql = `EXISTS (SELECT 1 FROM ${target}${on ? ` WHERE ${on}` : ""})`;
1004
1004
  if (joinSet) {
1005
1005
  if (joinSet.has(sql))
1006
1006
  continue;
@@ -1091,190 +1091,144 @@ const pushIn = (ctx, query, ands, quotedAs, arg) => {
1091
1091
  ands.push(`${multiple ? `(${columnsSql})` : columnsSql} IN ${value}`);
1092
1092
  };
1093
1093
 
1094
- var __defProp$e = Object.defineProperty;
1095
- var __defProps$8 = Object.defineProperties;
1096
- var __getOwnPropDescs$8 = Object.getOwnPropertyDescriptors;
1097
- var __getOwnPropSymbols$f = Object.getOwnPropertySymbols;
1098
- var __hasOwnProp$f = Object.prototype.hasOwnProperty;
1099
- var __propIsEnum$f = Object.prototype.propertyIsEnumerable;
1100
- var __defNormalProp$e = (obj, key, value) => key in obj ? __defProp$e(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
1101
- var __spreadValues$e = (a, b) => {
1094
+ var __defProp$f = Object.defineProperty;
1095
+ var __defProps$9 = Object.defineProperties;
1096
+ var __getOwnPropDescs$9 = Object.getOwnPropertyDescriptors;
1097
+ var __getOwnPropSymbols$g = Object.getOwnPropertySymbols;
1098
+ var __hasOwnProp$g = Object.prototype.hasOwnProperty;
1099
+ var __propIsEnum$g = Object.prototype.propertyIsEnumerable;
1100
+ var __defNormalProp$f = (obj, key, value) => key in obj ? __defProp$f(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
1101
+ var __spreadValues$f = (a, b) => {
1102
1102
  for (var prop in b || (b = {}))
1103
- if (__hasOwnProp$f.call(b, prop))
1104
- __defNormalProp$e(a, prop, b[prop]);
1105
- if (__getOwnPropSymbols$f)
1106
- for (var prop of __getOwnPropSymbols$f(b)) {
1107
- if (__propIsEnum$f.call(b, prop))
1108
- __defNormalProp$e(a, prop, b[prop]);
1103
+ if (__hasOwnProp$g.call(b, prop))
1104
+ __defNormalProp$f(a, prop, b[prop]);
1105
+ if (__getOwnPropSymbols$g)
1106
+ for (var prop of __getOwnPropSymbols$g(b)) {
1107
+ if (__propIsEnum$g.call(b, prop))
1108
+ __defNormalProp$f(a, prop, b[prop]);
1109
1109
  }
1110
1110
  return a;
1111
1111
  };
1112
- var __spreadProps$8 = (a, b) => __defProps$8(a, __getOwnPropDescs$8(b));
1113
- const processJoinItem = (ctx, table, query, item, quotedAs) => {
1112
+ var __spreadProps$9 = (a, b) => __defProps$9(a, __getOwnPropDescs$9(b));
1113
+ const processJoinItem = (ctx, table, query, args, quotedAs) => {
1114
1114
  let target;
1115
- let conditions;
1116
- const { first, args } = item;
1117
- if (typeof first === "string") {
1118
- if (first in table.relations) {
1119
- const { query: toQuery, joinQuery } = table.relations[first].relationConfig;
1120
- const jq = joinQuery(toQuery, table);
1121
- const { q: j } = jq;
1122
- const tableName = typeof j.from === "string" ? j.from : jq.table;
1123
- target = quoteSchemaAndTable(j.schema, tableName);
1124
- const as = j.as || first;
1125
- const joinAs = `"${as}"`;
1126
- if (as !== tableName) {
1127
- target += ` AS ${joinAs}`;
1128
- }
1129
- const queryData = {
1130
- shape: j.shape,
1131
- joinedShapes: __spreadProps$8(__spreadValues$e(__spreadValues$e({}, query.joinedShapes), j.joinedShapes), {
1132
- [table.q.as || table.table]: table.shape
1133
- }),
1134
- and: j.and ? [...j.and] : [],
1135
- or: j.or ? [...j.or] : []
1136
- };
1137
- if (args[0]) {
1138
- const arg = args[0](
1139
- new ctx.queryBuilder.onQueryBuilder(jq, j, table)
1140
- ).q;
1141
- if (arg.and)
1142
- queryData.and.push(...arg.and);
1143
- if (arg.or)
1144
- queryData.or.push(...arg.or);
1115
+ let on;
1116
+ if ("j" in args) {
1117
+ const { j, s, r } = args;
1118
+ const tableName = typeof j.q.from === "string" ? j.q.from : j.table;
1119
+ const quotedTable = quoteSchemaAndTable(j.q.schema, tableName);
1120
+ target = quotedTable;
1121
+ const as = j.q.as;
1122
+ const joinAs = `"${as}"`;
1123
+ if (as !== tableName) {
1124
+ target += ` AS ${joinAs}`;
1125
+ }
1126
+ if (r && s) {
1127
+ target = `LATERAL ${subJoinToSql(ctx, j, quotedTable, joinAs, true)}`;
1128
+ } else {
1129
+ on = whereToSql(ctx, j, j.q, joinAs);
1130
+ }
1131
+ } else if ("w" in args) {
1132
+ const { w } = args;
1133
+ target = `"${w}"`;
1134
+ if ("r" in args) {
1135
+ const { s, r } = args;
1136
+ if (s) {
1137
+ target = `LATERAL ${subJoinToSql(ctx, r, target, target)}`;
1138
+ } else {
1139
+ on = whereToSql(ctx, r, r.q, target);
1145
1140
  }
1146
- conditions = whereToSql(ctx, jq, queryData, joinAs);
1147
1141
  } else {
1148
- target = `"${first}"`;
1149
- const joinShape = query.joinedShapes[first];
1150
- conditions = processArgs(
1151
- args,
1142
+ on = processArgs(
1143
+ args.a,
1152
1144
  ctx,
1153
- table,
1154
1145
  query,
1155
- first,
1156
1146
  target,
1157
- joinShape,
1147
+ query.joinedShapes[w],
1158
1148
  quotedAs
1159
1149
  );
1160
1150
  }
1161
1151
  } else {
1162
- const joinQuery = first.q;
1163
- const quotedFrom = typeof joinQuery.from === "string" ? `"${joinQuery.from}"` : void 0;
1164
- target = quotedFrom || quoteSchemaAndTable(joinQuery.schema, first.table);
1165
- let joinAs = quotedFrom || `"${first.table}"`;
1166
- const qAs = joinQuery.as ? `"${joinQuery.as}"` : void 0;
1167
- const addAs = qAs && qAs !== joinAs;
1168
- const joinedShape = first.shape;
1169
- if (item.isSubQuery) {
1170
- const subQuery = first.toSQL({
1171
- values: ctx.values
1172
- });
1173
- target = `(${subQuery.text}) ${qAs || joinAs}`;
1174
- if (addAs)
1175
- joinAs = qAs;
1152
+ const { q, s } = args;
1153
+ let joinAs;
1154
+ if ("r" in args) {
1155
+ const { r } = args;
1156
+ const res = getArgQueryTarget(ctx, q, s, s);
1157
+ target = s ? `LATERAL ${res.target}` : res.target;
1158
+ joinAs = res.joinAs;
1159
+ if (!s) {
1160
+ on = whereToSql(ctx, r, r.q, joinAs);
1161
+ }
1176
1162
  } else {
1177
- if (addAs) {
1178
- joinAs = qAs;
1179
- target += ` AS ${qAs}`;
1163
+ const res = getArgQueryTarget(ctx, q, s);
1164
+ target = res.target;
1165
+ joinAs = res.joinAs;
1166
+ if ("a" in args) {
1167
+ on = processArgs(args.a, ctx, query, joinAs, q.shape, quotedAs);
1180
1168
  }
1181
1169
  }
1182
- conditions = processArgs(
1183
- args,
1184
- ctx,
1185
- table,
1186
- query,
1187
- first,
1188
- joinAs,
1189
- joinedShape,
1190
- quotedAs
1191
- );
1192
- if (!item.isSubQuery) {
1170
+ if (!s) {
1193
1171
  const whereSql = whereToSql(
1194
1172
  ctx,
1195
- first,
1196
- __spreadProps$8(__spreadValues$e({}, joinQuery), {
1197
- joinedShapes: __spreadProps$8(__spreadValues$e(__spreadValues$e({}, query.joinedShapes), joinQuery.joinedShapes), {
1173
+ q,
1174
+ __spreadProps$9(__spreadValues$f({}, q.q), {
1175
+ joinedShapes: __spreadProps$9(__spreadValues$f(__spreadValues$f({}, query.joinedShapes), q.q.joinedShapes), {
1198
1176
  [table.q.as || table.table]: table.q.shape
1199
1177
  })
1200
1178
  }),
1201
1179
  joinAs
1202
1180
  );
1203
1181
  if (whereSql) {
1204
- if (conditions)
1205
- conditions += ` AND ${whereSql}`;
1182
+ if (on)
1183
+ on += ` AND ${whereSql}`;
1206
1184
  else
1207
- conditions = whereSql;
1185
+ on = whereSql;
1208
1186
  }
1209
1187
  }
1210
1188
  }
1211
- return { target, conditions };
1189
+ return { target, on };
1212
1190
  };
1213
- const processArgs = (args, ctx, table, query, first, joinAs, joinShape, quotedAs) => {
1214
- var _a;
1215
- if (args.length === 1) {
1216
- const arg = args[0];
1217
- if (typeof arg === "function") {
1218
- const joinedShapes = __spreadProps$8(__spreadValues$e({}, query.joinedShapes), {
1219
- [table.q.as || table.table]: table.shape
1220
- });
1221
- let q;
1222
- let data;
1223
- if (typeof first === "string") {
1224
- const name = first;
1225
- const query2 = table.q;
1226
- const shape = (_a = query2.withShapes) == null ? void 0 : _a[name];
1227
- if (!shape) {
1228
- throw new Error("Cannot get shape of `with` statement");
1229
- }
1230
- q = Object.create(table);
1231
- q.q = {
1232
- type: void 0,
1233
- shape,
1234
- adapter: query2.adapter,
1235
- handleResult: query2.handleResult,
1236
- returnType: "all",
1237
- logger: query2.logger
1238
- };
1239
- data = { shape, joinedShapes };
1240
- } else {
1241
- q = first;
1242
- if (first.joinQueryAfterCallback) {
1243
- let base = q.baseQuery;
1244
- if (q.q.as) {
1245
- base = base.as(q.q.as);
1246
- }
1247
- const { q: query2 } = first.joinQueryAfterCallback(
1248
- base,
1249
- table
1250
- );
1251
- if (query2.and) {
1252
- pushQueryArray(q, "and", query2.and);
1253
- }
1254
- if (query2.or) {
1255
- pushQueryArray(q, "or", query2.or);
1256
- }
1257
- }
1258
- data = __spreadProps$8(__spreadValues$e({}, first.q), {
1259
- joinedShapes: __spreadValues$e(__spreadValues$e({}, first.q.joinedShapes), joinedShapes)
1260
- });
1261
- }
1262
- const jq = arg(new ctx.queryBuilder.onQueryBuilder(q, data, table));
1263
- if (jq.q.joinedShapes !== joinedShapes) {
1264
- jq.q.joinedShapes = __spreadValues$e(__spreadValues$e({}, jq.q.joinedShapes), joinedShapes);
1265
- }
1266
- return whereToSql(ctx, jq, jq.q, joinAs);
1267
- } else {
1268
- return getObjectOrRawConditions(
1269
- ctx,
1270
- query,
1271
- arg,
1272
- quotedAs,
1273
- joinAs,
1274
- joinShape
1275
- );
1191
+ const getArgQueryTarget = (ctx, first, joinSubQuery, cloned) => {
1192
+ const joinQuery = first.q;
1193
+ const quotedFrom = typeof joinQuery.from === "string" ? `"${joinQuery.from}"` : void 0;
1194
+ let joinAs = quotedFrom || `"${first.table}"`;
1195
+ const qAs = joinQuery.as ? `"${joinQuery.as}"` : void 0;
1196
+ const addAs = qAs && qAs !== joinAs;
1197
+ if (joinSubQuery) {
1198
+ return {
1199
+ target: subJoinToSql(ctx, first, joinAs, qAs, cloned),
1200
+ joinAs: addAs ? qAs : joinAs
1201
+ };
1202
+ } else {
1203
+ let target = quotedFrom || quoteSchemaAndTable(joinQuery.schema, first.table);
1204
+ if (addAs) {
1205
+ joinAs = qAs;
1206
+ target += ` AS ${qAs}`;
1276
1207
  }
1277
- } else if (args.length >= 2) {
1208
+ return { target, joinAs };
1209
+ }
1210
+ };
1211
+ const subJoinToSql = (ctx, jq, innerAs, outerAs, cloned) => {
1212
+ if (!jq.q.select && jq.internal.columnsForSelectAll) {
1213
+ if (!cloned)
1214
+ jq = jq.clone();
1215
+ jq.q.select = [new RawSQL(`${innerAs}.*`)];
1216
+ }
1217
+ return `(${jq.toSQL({
1218
+ values: ctx.values
1219
+ }).text}) ${outerAs || innerAs}`;
1220
+ };
1221
+ const processArgs = (args, ctx, query, joinAs, joinShape, quotedAs) => {
1222
+ if (args.length === 1) {
1223
+ return getObjectOrRawConditions(
1224
+ ctx,
1225
+ query,
1226
+ args[0],
1227
+ quotedAs,
1228
+ joinAs,
1229
+ joinShape
1230
+ );
1231
+ } else {
1278
1232
  return getConditionsFor3Or4LengthItem(
1279
1233
  ctx,
1280
1234
  query,
@@ -1284,7 +1238,6 @@ const processArgs = (args, ctx, table, query, first, joinAs, joinShape, quotedAs
1284
1238
  joinShape
1285
1239
  );
1286
1240
  }
1287
- return void 0;
1288
1241
  };
1289
1242
  const getConditionsFor3Or4LengthItem = (ctx, query, target, quotedAs, args, joinShape) => {
1290
1243
  const [leftColumn, opOrRightColumn, maybeRightColumn] = args;
@@ -1334,14 +1287,14 @@ const pushJoinSql = (ctx, table, query, quotedAs) => {
1334
1287
  sql = `${item[0]} LATERAL (${q.toSQL(ctx).text}) "${((_a = query.joinOverrides) == null ? void 0 : _a[as]) || as}" ON true`;
1335
1288
  ctx.aliasValue = aliasValue;
1336
1289
  } else {
1337
- const { target, conditions } = processJoinItem(
1290
+ const { target, on = "true" } = processJoinItem(
1338
1291
  ctx,
1339
1292
  table,
1340
1293
  query,
1341
- item,
1294
+ item.args,
1342
1295
  quotedAs
1343
1296
  );
1344
- sql = conditions ? `${item.type} ${target} ON ${conditions}` : `${item.type} ${target} ON true`;
1297
+ sql = `${item.type} ${target} ON ${on}`;
1345
1298
  }
1346
1299
  if (joinSet) {
1347
1300
  if (joinSet.has(sql))
@@ -1362,15 +1315,123 @@ const skipQueryKeysForSubQuery = {
1362
1315
  joinedShapes: true,
1363
1316
  returnsOne: true
1364
1317
  };
1365
- const getIsJoinSubQuery = (query, baseQuery) => {
1366
- for (const key in query) {
1367
- if (!skipQueryKeysForSubQuery[key] && query[key] !== baseQuery[key]) {
1318
+ const getIsJoinSubQuery = (query) => {
1319
+ const {
1320
+ q,
1321
+ baseQuery: { q: baseQ }
1322
+ } = query;
1323
+ for (const key in q) {
1324
+ if (!skipQueryKeysForSubQuery[key] && q[key] !== baseQ[key]) {
1368
1325
  return true;
1369
1326
  }
1370
1327
  }
1371
1328
  return false;
1372
1329
  };
1373
1330
 
1331
+ var __defProp$e = Object.defineProperty;
1332
+ var __defProps$8 = Object.defineProperties;
1333
+ var __getOwnPropDescs$8 = Object.getOwnPropertyDescriptors;
1334
+ var __getOwnPropSymbols$f = Object.getOwnPropertySymbols;
1335
+ var __hasOwnProp$f = Object.prototype.hasOwnProperty;
1336
+ var __propIsEnum$f = Object.prototype.propertyIsEnumerable;
1337
+ var __defNormalProp$e = (obj, key, value) => key in obj ? __defProp$e(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
1338
+ var __spreadValues$e = (a, b) => {
1339
+ for (var prop in b || (b = {}))
1340
+ if (__hasOwnProp$f.call(b, prop))
1341
+ __defNormalProp$e(a, prop, b[prop]);
1342
+ if (__getOwnPropSymbols$f)
1343
+ for (var prop of __getOwnPropSymbols$f(b)) {
1344
+ if (__propIsEnum$f.call(b, prop))
1345
+ __defNormalProp$e(a, prop, b[prop]);
1346
+ }
1347
+ return a;
1348
+ };
1349
+ var __spreadProps$8 = (a, b) => __defProps$8(a, __getOwnPropDescs$8(b));
1350
+ const processJoinArgs = (joinTo, first, args, joinSubQuery) => {
1351
+ var _a;
1352
+ if (typeof first === "string") {
1353
+ if (first in joinTo.relations) {
1354
+ const { query: toQuery, joinQuery } = joinTo.relations[first].relationConfig;
1355
+ const j = joinQuery(toQuery, joinTo);
1356
+ if (typeof args[0] === "function") {
1357
+ const r = args[0](
1358
+ makeJoinQueryBuilder(j, j.q.joinedShapes, joinTo)
1359
+ );
1360
+ return { j: j.merge(r), s: joinSubQuery || getIsJoinSubQuery(r), r };
1361
+ }
1362
+ return { j, s: joinSubQuery };
1363
+ } else if (typeof args[0] !== "function") {
1364
+ return { w: first, a: args };
1365
+ } else {
1366
+ const joinToQ = joinTo.q;
1367
+ const shape = (_a = joinToQ.withShapes) == null ? void 0 : _a[first];
1368
+ if (!shape) {
1369
+ throw new Error("Cannot get shape of `with` statement");
1370
+ }
1371
+ const j = joinTo.queryBuilder.baseQuery.clone();
1372
+ j.table = first;
1373
+ j.q = {
1374
+ shape,
1375
+ adapter: joinToQ.adapter,
1376
+ handleResult: joinToQ.handleResult,
1377
+ returnType: "all",
1378
+ logger: joinToQ.logger
1379
+ };
1380
+ j.baseQuery = j;
1381
+ const joinedShapes = __spreadProps$8(__spreadValues$e({}, joinToQ.joinedShapes), {
1382
+ [joinToQ.as || joinTo.table]: joinTo.shape
1383
+ });
1384
+ const r = args[0](
1385
+ makeJoinQueryBuilder(
1386
+ j,
1387
+ j.q.joinedShapes ? __spreadValues$e(__spreadValues$e({}, j.q.joinedShapes), joinedShapes) : joinedShapes,
1388
+ joinTo
1389
+ )
1390
+ );
1391
+ return { w: first, r, s: joinSubQuery || getIsJoinSubQuery(r) };
1392
+ }
1393
+ } else if (typeof args[0] === "function") {
1394
+ const q = first;
1395
+ if (q.joinQueryAfterCallback) {
1396
+ let base = q.baseQuery;
1397
+ if (q.q.as) {
1398
+ base = base.as(q.q.as);
1399
+ }
1400
+ const { q: query } = q.joinQueryAfterCallback(base, joinTo);
1401
+ if (query.and) {
1402
+ pushQueryArray(q, "and", query.and);
1403
+ }
1404
+ if (query.or) {
1405
+ pushQueryArray(q, "or", query.or);
1406
+ }
1407
+ }
1408
+ const joinedShapes = __spreadProps$8(__spreadValues$e({}, joinTo.q.joinedShapes), {
1409
+ [joinTo.q.as || joinTo.table]: joinTo.shape
1410
+ });
1411
+ const r = args[0](
1412
+ makeJoinQueryBuilder(
1413
+ q,
1414
+ q.q.joinedShapes ? __spreadValues$e(__spreadValues$e({}, q.q.joinedShapes), joinedShapes) : joinedShapes,
1415
+ joinTo
1416
+ )
1417
+ );
1418
+ joinSubQuery || (joinSubQuery = getIsJoinSubQuery(r));
1419
+ return { q: joinSubQuery ? q.merge(r) : q, r, s: joinSubQuery };
1420
+ }
1421
+ return {
1422
+ q: first,
1423
+ a: args,
1424
+ s: joinSubQuery
1425
+ };
1426
+ };
1427
+ const makeJoinQueryBuilder = (joinedQuery, joinedShapes, joinTo) => {
1428
+ const q = joinedQuery.baseQuery.clone();
1429
+ q.q.joinedShapes = joinedShapes;
1430
+ q.baseQuery = q;
1431
+ q.q.joinTo = joinTo;
1432
+ return q;
1433
+ };
1434
+
1374
1435
  var __defProp$d = Object.defineProperty;
1375
1436
  var __defProps$7 = Object.defineProperties;
1376
1437
  var __getOwnPropDescs$7 = Object.getOwnPropertyDescriptors;
@@ -1391,23 +1452,24 @@ var __spreadValues$d = (a, b) => {
1391
1452
  };
1392
1453
  var __spreadProps$7 = (a, b) => __defProps$7(a, __getOwnPropDescs$7(b));
1393
1454
  const _join = (q, require2, type, first, args) => {
1394
- var _a;
1455
+ var _a, _b;
1395
1456
  let joinKey;
1396
1457
  let shape;
1397
1458
  let parsers;
1398
- let isSubQuery = false;
1459
+ let joinSubQuery = false;
1399
1460
  if (typeof first === "function") {
1400
1461
  first = first(q.relations);
1401
1462
  first.joinQueryAfterCallback = first.joinQuery;
1402
1463
  }
1403
1464
  if (typeof first === "object") {
1404
- isSubQuery = getIsJoinSubQuery(first.q, first.baseQuery.q);
1405
- joinKey = first.q.as || first.table;
1465
+ const q2 = first;
1466
+ joinSubQuery = getIsJoinSubQuery(q2);
1467
+ joinKey = q2.q.as || q2.table;
1406
1468
  if (joinKey) {
1407
- shape = getShapeFromSelect(first, isSubQuery);
1408
- parsers = first.q.parsers;
1409
- if (isSubQuery) {
1410
- first = first.clone();
1469
+ shape = getShapeFromSelect(q2, joinSubQuery);
1470
+ parsers = q2.q.parsers;
1471
+ if (joinSubQuery) {
1472
+ first = q2.clone();
1411
1473
  first.shape = shape;
1412
1474
  }
1413
1475
  }
@@ -1433,14 +1495,46 @@ const _join = (q, require2, type, first, args) => {
1433
1495
  }
1434
1496
  }
1435
1497
  if (joinKey) {
1436
- setQueryObjectValue(q, "joinedShapes", joinKey, shape);
1437
- setQueryObjectValue(q, "joinedParsers", joinKey, parsers);
1498
+ setQueryObjectValue(
1499
+ q,
1500
+ "joinedShapes",
1501
+ joinKey,
1502
+ shape
1503
+ );
1504
+ setQueryObjectValue(
1505
+ q,
1506
+ "joinedParsers",
1507
+ joinKey,
1508
+ parsers
1509
+ );
1438
1510
  }
1439
- return pushQueryValue(q, "join", {
1440
- type,
1511
+ const joinArgs = processJoinArgs(
1512
+ q,
1441
1513
  first,
1442
1514
  args,
1443
- isSubQuery
1515
+ joinSubQuery
1516
+ );
1517
+ if (joinKey && "s" in joinArgs && joinArgs.s) {
1518
+ const j = "j" in joinArgs ? (_b = joinArgs.r) != null ? _b : joinArgs.j : "r" in joinArgs ? joinArgs.r : joinArgs.q;
1519
+ if (j.q.select || !j.internal.columnsForSelectAll) {
1520
+ const shape2 = getShapeFromSelect(j, true);
1521
+ setQueryObjectValue(
1522
+ q,
1523
+ "joinedShapes",
1524
+ joinKey,
1525
+ shape2
1526
+ );
1527
+ setQueryObjectValue(
1528
+ q,
1529
+ "joinedParsers",
1530
+ joinKey,
1531
+ j.q.parsers
1532
+ );
1533
+ }
1534
+ }
1535
+ return pushQueryValue(q, "join", {
1536
+ type,
1537
+ args: joinArgs
1444
1538
  });
1445
1539
  };
1446
1540
  const _joinLateral = (self, type, arg, cb, as) => {
@@ -1450,7 +1544,7 @@ const _joinLateral = (self, type, arg, cb, as) => {
1450
1544
  if (typeof arg === "string") {
1451
1545
  relation = q.relations[arg];
1452
1546
  if (relation) {
1453
- arg = relation.relationConfig.query;
1547
+ arg = relation.relationConfig.query.clone();
1454
1548
  } else {
1455
1549
  const shape = (_a = q.q.withShapes) == null ? void 0 : _a[arg];
1456
1550
  if (shape) {
@@ -3286,7 +3380,7 @@ const processValue = (ctx, table, QueryClass, key, value, quotedAs) => {
3286
3380
  };
3287
3381
 
3288
3382
  const pushDeleteSql = (ctx, table, query, quotedAs) => {
3289
- var _a, _b, _c;
3383
+ var _a, _b, _c, _d;
3290
3384
  const from = `"${table.table}"`;
3291
3385
  ctx.sql.push(`DELETE FROM ${from}`);
3292
3386
  if (from !== quotedAs) {
@@ -3294,28 +3388,40 @@ const pushDeleteSql = (ctx, table, query, quotedAs) => {
3294
3388
  }
3295
3389
  let conditions;
3296
3390
  if ((_a = query.join) == null ? void 0 : _a.length) {
3297
- const items = [];
3391
+ const targets = [];
3392
+ const ons = [];
3298
3393
  const joinSet = query.join.length > 1 ? /* @__PURE__ */ new Set() : null;
3299
3394
  for (const item of query.join) {
3300
- if (!Array.isArray(item)) {
3301
- const join = processJoinItem(ctx, table, query, item, quotedAs);
3302
- const key = `${join.target}${join.conditions}`;
3395
+ if (Array.isArray(item)) {
3396
+ const q = item[1];
3397
+ const { aliasValue } = ctx;
3398
+ ctx.aliasValue = true;
3399
+ const as = item[2];
3400
+ targets.push(
3401
+ `LATERAL (${q.toSQL(ctx).text}) "${((_b = query.joinOverrides) == null ? void 0 : _b[as]) || as}"`
3402
+ );
3403
+ ctx.aliasValue = aliasValue;
3404
+ } else {
3405
+ const join = processJoinItem(ctx, table, query, item.args, quotedAs);
3406
+ const key = `${join.target}${join.on}`;
3303
3407
  if (joinSet) {
3304
3408
  if (joinSet.has(key))
3305
3409
  continue;
3306
3410
  joinSet.add(key);
3307
3411
  }
3308
- items.push(join);
3412
+ targets.push(join.target);
3413
+ if (join.on)
3414
+ ons.push(join.on);
3309
3415
  }
3310
3416
  }
3311
- if (items.length) {
3312
- ctx.sql.push(`USING ${items.map((item) => item.target).join(", ")}`);
3313
- conditions = items.map((item) => item.conditions).filter(Boolean).join(" AND ");
3417
+ if (targets.length) {
3418
+ ctx.sql.push(`USING ${targets.join(", ")}`);
3314
3419
  }
3420
+ conditions = ons.join(" AND ");
3315
3421
  }
3316
3422
  pushWhereStatementSql(ctx, table, query, quotedAs);
3317
- if (conditions == null ? void 0 : conditions.length) {
3318
- if (((_b = query.and) == null ? void 0 : _b.length) || ((_c = query.or) == null ? void 0 : _c.length)) {
3423
+ if (conditions) {
3424
+ if (((_c = query.and) == null ? void 0 : _c.length) || ((_d = query.or) == null ? void 0 : _d.length)) {
3319
3425
  ctx.sql.push("AND", conditions);
3320
3426
  } else {
3321
3427
  ctx.sql.push("WHERE", conditions);
@@ -6881,1944 +6987,1996 @@ class QueryHooks {
6881
6987
  }
6882
6988
  }
6883
6989
 
6884
- class QueryBase {
6885
- constructor() {
6886
- this.q = {};
6887
- }
6990
+ class Join {
6888
6991
  /**
6889
- * Clones the current query chain, useful for re-using partial query snippets in other queries without mutating the original.
6992
+ * ## Select relation
6890
6993
  *
6891
- * Used under the hood, and not really needed on the app side.
6892
- */
6893
- clone() {
6894
- const cloned = Object.create(this.baseQuery);
6895
- cloned.q = getClonedQueryData(this.q);
6896
- return cloned;
6897
- }
6898
- }
6899
-
6900
- const _queryWhere = (q, args) => {
6901
- return pushQueryArray(
6902
- q,
6903
- "and",
6904
- args
6905
- );
6906
- };
6907
- const _queryWhereSql = (q, args) => {
6908
- return pushQueryValue(
6909
- q,
6910
- "and",
6911
- sqlQueryArgsToExpression(args)
6912
- );
6913
- };
6914
- const _queryWhereNot = (q, args) => {
6915
- return pushQueryValue(q, "and", {
6916
- NOT: args
6917
- });
6918
- };
6919
- const _queryWhereNotSql = (q, args) => {
6920
- return pushQueryValue(q, "and", {
6921
- NOT: sqlQueryArgsToExpression(args)
6922
- });
6923
- };
6924
- const _queryOr = (q, args) => {
6925
- return pushQueryArray(
6926
- q,
6927
- "or",
6928
- args.map((item) => [item])
6929
- );
6930
- };
6931
- const _queryOrNot = (q, args) => {
6932
- return pushQueryArray(
6933
- q,
6934
- "or",
6935
- args.map((item) => [{ NOT: item }])
6936
- );
6937
- };
6938
- const _queryWhereIn = (q, and, arg, values, not) => {
6939
- let item;
6940
- if (values) {
6941
- if (Array.isArray(arg)) {
6942
- item = {
6943
- IN: {
6944
- columns: arg,
6945
- values
6946
- }
6947
- };
6948
- } else {
6949
- item = { [arg]: { in: values } };
6950
- }
6951
- } else {
6952
- item = {};
6953
- for (const key in arg) {
6954
- item[key] = { in: arg[key] };
6955
- }
6956
- }
6957
- if (not)
6958
- item = { NOT: item };
6959
- if (and) {
6960
- pushQueryValue(q, "and", item);
6961
- } else {
6962
- pushQueryValue(q, "or", [item]);
6963
- }
6964
- return q;
6965
- };
6966
- const existsArgs = (q, args) => {
6967
- let isSubQuery;
6968
- if (typeof q === "object") {
6969
- isSubQuery = getIsJoinSubQuery(q.q, q.baseQuery.q);
6970
- if (isSubQuery) {
6971
- q = q.clone();
6972
- q.shape = getShapeFromSelect(q, true);
6973
- }
6974
- } else {
6975
- isSubQuery = false;
6976
- }
6977
- return [
6978
- {
6979
- EXISTS: {
6980
- first: q,
6981
- args,
6982
- isSubQuery
6983
- }
6984
- }
6985
- ];
6986
- };
6987
- class Where {
6988
- /**
6989
- * Constructing `WHERE` conditions:
6994
+ * Before joining a table, consider if selecting a relation is enough for your case:
6990
6995
  *
6991
6996
  * ```ts
6992
- * db.table.where({
6993
- * // column of the current table
6994
- * name: 'John',
6995
- *
6996
- * // table name may be specified, it can be the name of a joined table
6997
- * 'table.lastName': 'Johnsonuk',
6997
+ * // select users with profiles
6998
+ * // result type is Array<{ name: string, profile: Profile }>
6999
+ * await db.user.select('name', {
7000
+ * profile: (q) => q.profile,
7001
+ * });
6998
7002
  *
6999
- * // object with operators, see the "column operators" section to see a full list of them:
7000
- * age: {
7001
- * gt: 30,
7002
- * lt: 70,
7003
- * },
7003
+ * // select posts with counts of comments, order by comments count
7004
+ * // result type is Array<Post & { commentsCount: number }>
7005
+ * await db.post
7006
+ * .select('*', {
7007
+ * commentsCount: (q) => q.comments.count(),
7008
+ * })
7009
+ * .order({
7010
+ * commentsCount: 'DESC',
7011
+ * });
7004
7012
  *
7005
- * // where column equals to raw SQL
7006
- * column: db.table.sql`sql expression`,
7013
+ * // select authors with array of their book titles
7014
+ * // result type is Array<Author & { books: string[] }>
7015
+ * await db.author.select('*', {
7016
+ * books: (q) => q.books.pluck('title'),
7007
7017
  * });
7008
7018
  * ```
7009
7019
  *
7010
- * Multiple `where`s are joined with `AND`:
7020
+ * Internally, such selects will use `LEFT JOIN LATERAL` to join a relation.
7021
+ * If you're loading users with profiles (one-to-one relation), and some users don't have a profile, `profile` property will have `NULL` for such users.
7022
+ * If you want to load only users that have profiles, and filter out the rest, add `.join()` method to the relation without arguments:
7011
7023
  *
7012
7024
  * ```ts
7013
- * db.table.where({ foo: 'foo' }).where({ bar: 'bar' });
7014
- * ```
7025
+ * // load only users who have a profile
7026
+ * await db.user.select('*', {
7027
+ * profile: (q) => q.profile.join(),
7028
+ * });
7015
7029
  *
7016
- * ```sql
7017
- * SELECT * FROM table WHERE foo = 'foo' AND bar = 'bar'
7030
+ * // load only users who have a specific profile
7031
+ * await db.user.select('*', {
7032
+ * profile: (q) => q.profile.join().where({ age: { gt: 20 } }),
7033
+ * });
7018
7034
  * ```
7019
7035
  *
7020
- * `undefined` values are ignored, so you can supply a partial object with conditions:
7036
+ * You can also use this `.join()` method on the one-to-many relations, and records with empty array will be filtered out:
7021
7037
  *
7022
7038
  * ```ts
7023
- * type Params = {
7024
- * // allow providing exact age, or lower or greater than
7025
- * age?: number | { lt?: number; gt?: number };
7026
- * };
7027
- *
7028
- * const loadRecords = async (params: Params) => {
7029
- * // this will load all records if params is an empty object
7030
- * const records = await db.table.where(params);
7031
- * };
7039
+ * // posts that have no tags won't be loaded
7040
+ * // result type is Array<Post & { tags: Tag[] }>
7041
+ * db.post.select('*', {
7042
+ * tags: (q) => q.tags.join(),
7043
+ * });
7032
7044
  * ```
7033
7045
  *
7034
- * It supports a sub-query that is selecting a single value to compare it with a column:
7046
+ * # Joins
7035
7047
  *
7036
- * ```ts
7037
- * db.table.where({
7038
- * // compare `someColumn` in one table with the `column` value returned from another query.
7039
- * someColumn: db.otherTable.where(...conditions).get('column'),
7040
- * });
7041
- * ```
7048
+ * `join` methods allows to join other tables, relations by name, [with](/guide/advanced-queries#with) statements, sub queries.
7042
7049
  *
7043
- * `where` can accept other queries and merge their conditions:
7050
+ * All the `join` methods accept the same arguments, but returning type is different because with `join` it's guaranteed to load joined table, and with `leftJoin` the joined table columns may be `NULL` when no matching record was found.
7051
+ *
7052
+ * For the following examples, imagine we have a `User` table with `id` and `name`, and `Message` table with `id`, `text`, messages belongs to user via `userId` column:
7044
7053
  *
7045
7054
  * ```ts
7046
- * const otherQuery = db.table.where({ name: 'John' });
7055
+ * export class UserTable extends BaseTable {
7056
+ * readonly table = 'user';
7057
+ * columns = this.setColumns((t) => ({
7058
+ * id: t.identity().primaryKey(),
7059
+ * name: t.text(),
7060
+ * }));
7047
7061
  *
7048
- * db.table.where({ id: 1 }, otherQuery);
7049
- * // this will produce WHERE "table"."id" = 1 AND "table"."name' = 'John'
7062
+ * relations = {
7063
+ * messages: this.hasMany(() => MessageTable, {
7064
+ * primaryKey: 'id',
7065
+ * foreignKey: 'userId',
7066
+ * }),
7067
+ * };
7068
+ * }
7069
+ *
7070
+ * export class MessageTable extends BaseTable {
7071
+ * readonly table = 'message';
7072
+ * columns = this.setColumns((t) => ({
7073
+ * id: t.identity().primaryKey(),
7074
+ * text: t.text(),
7075
+ * ...t.timestamps(),
7076
+ * }));
7077
+ *
7078
+ * relations = {
7079
+ * user: this.belongsTo(() => UserTable, {
7080
+ * primaryKey: 'id',
7081
+ * foreignKey: 'userId',
7082
+ * }),
7083
+ * };
7084
+ * }
7050
7085
  * ```
7051
7086
  *
7052
- * `where` supports raw SQL:
7087
+ * ## join
7053
7088
  *
7054
- * ```ts
7055
- * db.table.where(db.table.sql`a = b`);
7089
+ * `join` is a method for SQL `JOIN`, which is equivalent to `INNER JOIN`, `LEFT INNERT JOIN`.
7056
7090
  *
7057
- * // or
7058
- * import { raw } from 'orchid-orm';
7091
+ * When no matching record is found, it will skip records of the main table.
7059
7092
  *
7060
- * db.table.where(raw`a = b`);
7093
+ * When joining the same table with the same condition more than once, duplicated joins will be ignored:
7094
+ *
7095
+ * ```ts
7096
+ * // joining a relation
7097
+ * db.post.join('comments').join('comments');
7098
+ *
7099
+ * // joining a table with a condition
7100
+ * db.post
7101
+ * .join('comments', 'comments.postId', 'post.id')
7102
+ * .join('comments', 'comments.postId', 'post.id');
7061
7103
  * ```
7062
7104
  *
7063
- * `where` can accept a callback with a specific query builder containing all "where" methods such as `where`, `orWhere`, `whereNot`, `whereIn`, `whereExists`:
7105
+ * Both queries will produce SQL with only 1 join
7064
7106
  *
7065
- * ```ts
7066
- * db.table.where((q) =>
7067
- * q
7068
- * .where({ name: 'Name' })
7069
- * .orWhere({ id: 1 }, { id: 2 })
7070
- * .whereIn('letter', ['a', 'b', 'c'])
7071
- * .whereExists(Message, 'authorId', 'id'),
7072
- * );
7107
+ * ```sql
7108
+ * SELECT * FROM post JOIN comments ON comments.postId = post.id
7073
7109
  * ```
7074
7110
  *
7075
- * `where` can accept multiple arguments, conditions are joined with `AND`:
7111
+ * However, this is only possible if the join has no dynamic values:
7076
7112
  *
7077
7113
  * ```ts
7078
- * db.table.where(
7079
- * { id: 1 },
7080
- * db.table.where({ name: 'John' }),
7081
- * db.table.sql`a = b`,
7082
- * );
7114
+ * db.post
7115
+ * .join('comments', (q) => q.where({ rating: { gt: 5 } }))
7116
+ * .join('comments', (q) => q.where({ rating: { gt: 5 } }));
7083
7117
  * ```
7084
7118
  *
7085
- * ## where sub query
7119
+ * Both joins above have the same `{ gt: 5 }`, but still, the `5` is a dynamic value and in this case joins will be duplicated,
7120
+ * resulting in a database error.
7086
7121
  *
7087
- * `where` handles a special callback where you can query a relation to get some value and filter by that value.
7122
+ * ### join relation
7088
7123
  *
7089
- * It is useful for a faceted search. For instance, posts have tags, and we want to find all posts that have all the given tags.
7124
+ * When relations are defined between the tables, you can join them by a relation name.
7125
+ * Joined table can be references from `where` and `select` by a relation name.
7090
7126
  *
7091
7127
  * ```ts
7092
- * const givenTags = ['typescript', 'node.js'];
7128
+ * const result = await db.user
7129
+ * .join('messages')
7130
+ * // after joining a table, we can use it in `where` conditions:
7131
+ * .where({ 'messages.text': { startsWith: 'Hi' } })
7132
+ * .select(
7133
+ * 'name', // name is User column, table name may be omitted
7134
+ * 'messages.text', // text is the Message column, and the table name is required
7135
+ * );
7093
7136
  *
7094
- * const posts = await db.post.where(
7095
- * (post) =>
7096
- * post.tags // query tags of the post
7097
- * .whereIn('tagName', givenTags) // where name of the tag is inside array
7098
- * .count() // count how many such tags were found
7099
- * .equals(wantedTags.length), // the count must be exactly the length of array
7100
- * // if the post has ony `typescript` tag but not the `node.js` it will be omitted
7101
- * );
7137
+ * // result has the following type:
7138
+ * const ok: { name: string; text: string }[] = result;
7102
7139
  * ```
7103
7140
  *
7104
- * This will produce an efficient SQL query:
7141
+ * The first argument can also be a callback, where instead of relation name as a string we're picking it as a property of `q`.
7142
+ * In such a way, we can alias the relation with `as`, add `where` conditions, use other query methods.
7105
7143
  *
7106
- * ```sql
7107
- * SELECT * FROM "post"
7108
- * WHERE (
7109
- * SELECT count(*) = 3
7110
- * FROM "tag" AS "tags"
7111
- * WHERE "tag"."tagName" IN ('typescript', 'node.js')
7112
- * -- join tags to the post via "postTag" table
7113
- * AND EXISTS (
7114
- * SELECT 1 FROM "postTag"
7115
- * WHERE "postTag"."postId" = "post"."id"
7116
- * AND "postTag"."tagId" = "tag"."id"
7117
- * )
7118
- * )
7144
+ * ```ts
7145
+ * const result = await db.user.join((q) =>
7146
+ * q.messages.as('m').where({ text: 'some text' }),
7147
+ * );
7119
7148
  * ```
7120
7149
  *
7121
- * In the example above we use `count()`, you can also use any other aggregate method instead, such as `min`, `max`, `avg`.
7150
+ * Optionally, you can pass a second callback argument, it makes `on` and `orOn` methods available.
7122
7151
  *
7123
- * The `count()` is chained with `equals` to check for a strict equality, any other operation is also allowed, such as `not`, `lt`, `gt`.
7152
+ * But remember that when joining a relation, the relevant `ON` conditions are already handled automatically.
7124
7153
  *
7125
- * ## where special keys
7154
+ * ```ts
7155
+ * const result = await db.user.join(
7156
+ * (q) => q.messages.as('m'),
7157
+ * (q) =>
7158
+ * q
7159
+ * .on('text', 'name') // additionally, match message with user name
7160
+ * .where({ text: 'some text' }), // you can add `where` in a second callback as well.
7161
+ * );
7162
+ * ```
7126
7163
  *
7127
- * The object passed to `where` can contain special keys, each of the keys corresponds to its own method and takes the same value as the type of argument of the method.
7164
+ * ### Selecting full joined records
7128
7165
  *
7129
- * For example:
7166
+ * `select` supports selecting a full record of a previously joined table by passing a table name with `.*` at the end:
7130
7167
  *
7131
7168
  * ```ts
7132
- * db.table.where({
7133
- * NOT: { key: 'value' },
7134
- * OR: [{ name: 'a' }, { name: 'b' }],
7135
- * IN: {
7136
- * columns: ['id', 'name'],
7137
- * values: [
7138
- * [1, 'a'],
7139
- * [2, 'b'],
7140
- * ],
7141
- * },
7169
+ * const result = await db.book.join('author').select('title', {
7170
+ * author: 'author.*',
7142
7171
  * });
7172
+ *
7173
+ * // result has the following type:
7174
+ * const ok: {
7175
+ * // title of the book
7176
+ * title: string;
7177
+ * // a full author record is included:
7178
+ * author: { id: number; name: string; updatedAt: Date; createdAt: Date };
7179
+ * }[] = result;
7143
7180
  * ```
7144
7181
  *
7145
- * Using methods `whereNot`, `orWhere`, `whereIn` instead of this is a shorter and cleaner way, but in some cases, such object keys way may be more convenient.
7182
+ * It works fine for `1:1` (`belongsTo`, `hasOne`) relations, but it may have an unexpected result for `1:M` or `M:M` (`hasMany`, `hasAndBelongsToMany`) relations.
7183
+ * For any kind of relation, it results in one main table record with data of exactly one joined table record, i.e. when selecting in this way, the records **won't** be collected into arrays.
7146
7184
  *
7147
7185
  * ```ts
7148
- * db.table.where({
7149
- * // see .whereNot
7150
- * NOT: { id: 1 },
7151
- * // can be an array:
7152
- * NOT: [{ id: 1 }, { id: 2 }],
7153
- *
7154
- * // see .orWhere
7155
- * OR: [{ name: 'a' }, { name: 'b' }],
7156
- * // can be an array:
7157
- * // this will give id = 1 AND id = 2 OR id = 3 AND id = 4
7158
- * OR: [
7159
- * [{ id: 1 }, { id: 2 }],
7160
- * [{ id: 3 }, { id: 4 }],
7161
- * ],
7186
+ * const result = await db.user
7187
+ * .join('messages')
7188
+ * .where({ 'messages.text': { startsWith: 'Hi' } })
7189
+ * .select('name', { messages: 'messages.*' });
7162
7190
  *
7163
- * // see .in, the key syntax requires an object with columns and values
7164
- * IN: {
7165
- * columns: ['id', 'name'],
7166
- * values: [
7167
- * [1, 'a'],
7168
- * [2, 'b'],
7169
- * ],
7170
- * },
7171
- * // can be an array:
7172
- * IN: [
7173
- * {
7174
- * columns: ['id', 'name'],
7175
- * values: [
7176
- * [1, 'a'],
7177
- * [2, 'b'],
7178
- * ],
7179
- * },
7180
- * { columns: ['someColumn'], values: [['foo', 'bar']] },
7181
- * ],
7182
- * });
7191
+ * // result has the following type:
7192
+ * const ok: {
7193
+ * name: string;
7194
+ * // full message is included:
7195
+ * messages: { id: number; text: string; updatedAt: Date; createdAt: Date };
7196
+ * }[] = result;
7183
7197
  * ```
7184
7198
  *
7185
- * ## column operators
7199
+ * Because it's a one-to-many relation, one user has many messages, the user data will be duplicated for different messages data:
7186
7200
  *
7187
- * `where` argument can take an object where the key is the name of the operator and the value is its argument.
7201
+ * | name | msg |
7202
+ * | ------ | ------------------------------ |
7203
+ * | user 1 | `{ id: 1, text: 'message 1' }` |
7204
+ * | user 1 | `{ id: 2, text: 'message 2' }` |
7205
+ * | user 1 | `{ id: 3, text: 'message 3' }` |
7188
7206
  *
7189
- * Different types of columns support different sets of operators.
7207
+ * ### join table
7190
7208
  *
7191
- * All column operators can take a value of the same type as the column, a sub-query, or a raw SQL expression:
7209
+ * If relation wasn't defined, provide a `db.table` instance and specify columns for the join.
7210
+ * Joined table can be references from `where` and `select` by a table name.
7192
7211
  *
7193
7212
  * ```ts
7194
- * import { sql } from 'orchid-orm';
7195
- *
7196
- * db.table.where({
7197
- * numericColumn: {
7198
- * // lower than 5
7199
- * lt: 5,
7200
- *
7201
- * // lower than the value returned by sub-query
7202
- * lt: OtherTable.select('someNumber').take(),
7203
- *
7204
- * // raw SQL expression produces WHERE "numericColumn" < "otherColumn" + 10
7205
- * lt: sql`"otherColumn" + 10`,
7206
- * },
7207
- * });
7213
+ * // Join message where userId = id:
7214
+ * db.user
7215
+ * .join(db.message, 'userId', 'id')
7216
+ * .where({ 'message.text': { startsWith: 'Hi' } })
7217
+ * .select('name', 'message.text');
7208
7218
  * ```
7209
7219
  *
7210
- * ### Any type of column operators
7211
- *
7212
- * `equals` is a simple `=` operator, it may be useful for comparing column value with JSON object:
7220
+ * Columns in the join list may be prefixed with table names for clarity:
7213
7221
  *
7214
7222
  * ```ts
7215
- * db.table.where({
7216
- * // when searching for an exact same JSON value, this won't work:
7217
- * jsonColumn: someObject,
7218
- *
7219
- * // use `{ equals: ... }` instead:
7220
- * jsonColumn: { equals: someObject },
7221
- * });
7223
+ * db.user.join(db.message, 'message.userId', 'user.id');
7222
7224
  * ```
7223
7225
  *
7224
- * `not` is `!=` (aka `<>`) not equal operator:
7226
+ * Joined table can have an alias for referencing it further:
7225
7227
  *
7226
7228
  * ```ts
7227
- * db.table.where({
7228
- * anyColumn: { not: value },
7229
- * });
7229
+ * db.user
7230
+ * .join(db.message.as('m'), 'message.userId', 'user.id')
7231
+ * .where({ 'm.text': { startsWith: 'Hi' } })
7232
+ * .select('name', 'm.text');
7230
7233
  * ```
7231
7234
  *
7232
- * `in` is for the `IN` operator to check if the column value is included in a list of values.
7233
- *
7234
- * Takes an array of the same type as a column, a sub-query that returns a list of values, or a raw SQL expression that returns a list.
7235
+ * Joined table can be selected as an object as well as the relation join above:
7235
7236
  *
7236
7237
  * ```ts
7237
- * db.table.where({
7238
- * column: {
7239
- * in: ['a', 'b', 'c'],
7240
- *
7241
- * // WHERE "column" IN (SELECT "column" FROM "otherTable")
7242
- * in: OtherTable.select('column'),
7238
+ * const result = await db.user
7239
+ * .join(db.message.as('m'), 'message.userId', 'user.id')
7240
+ * .where({ 'm.text': { startsWith: 'Hi' } })
7241
+ * .select('name', { msg: 'm.*' });
7243
7242
  *
7244
- * in: db.table.sql`('a', 'b')`,
7245
- * },
7246
- * });
7243
+ * // result has the following type:
7244
+ * const ok: {
7245
+ * name: string;
7246
+ * // full message is included as msg:
7247
+ * msg: { id: number; text: string; updatedAt: Date; createdAt: Date };
7248
+ * }[] = result;
7247
7249
  * ```
7248
7250
  *
7249
- * `notIn` is for the `NOT IN` operator, and takes the same arguments as `in`
7250
- *
7251
- * ### Numeric, Date, and Time column operators
7252
- *
7253
- * To compare numbers, dates, and times.
7254
- *
7255
- * `lt` is for `<` (lower than)
7256
- *
7257
- * `lte` is for `<=` (lower than or equal)
7258
- *
7259
- * `gt` is for `>` (greater than)
7260
- *
7261
- * `gte` is for `>=` (greater than or equal)
7251
+ * You can provide a custom comparison operator
7262
7252
  *
7263
7253
  * ```ts
7264
- * db.table.where({
7265
- * numericColumn: {
7266
- * gt: 5,
7267
- * lt: 10,
7268
- * },
7254
+ * db.user.join(db.message, 'userId', '!=', 'id');
7255
+ * ```
7269
7256
  *
7270
- * date: {
7271
- * lte: new Date(),
7272
- * },
7257
+ * Join can accept raw SQL for the `ON` part of join:
7273
7258
  *
7274
- * time: {
7275
- * gte: new Date(),
7276
- * },
7277
- * });
7259
+ * ```ts
7260
+ * db.user.join(
7261
+ * db.message,
7262
+ * db.user.sql`lower("message"."text") = lower("user"."name")`,
7263
+ * );
7278
7264
  * ```
7279
7265
  *
7280
- * `between` also works with numeric, dates, and time columns, it takes an array of two elements.
7281
- *
7282
- * Both elements can be of the same type as a column, a sub-query, or a raw SQL expression.
7266
+ * Join can accept raw SQL instead of columns:
7283
7267
  *
7284
7268
  * ```ts
7285
- * db.table.where({
7286
- * column: {
7287
- * // simple values
7288
- * between: [1, 10],
7269
+ * db.user.join(
7270
+ * db.message,
7271
+ * db.user.sql`lower("message"."text")`,
7272
+ * db.user.sql`lower("user"."name")`,
7273
+ * );
7289
7274
  *
7290
- * // sub-query and raw SQL expression
7291
- * between: [OtherTable.select('column').take(), db.table.sql`2 + 2`],
7292
- * },
7293
- * });
7275
+ * // with operator:
7276
+ * db.user.join(
7277
+ * db.message,
7278
+ * db.user.sql`lower("message"."text")`,
7279
+ * '!=',
7280
+ * db.user.sql`lower("user"."name")`,
7281
+ * );
7294
7282
  * ```
7295
7283
  *
7296
- * ### Text column operators
7297
- *
7298
- * For `text`, `char`, `varchar`, and `json` columns.
7284
+ * To join based on multiple columns, you can provide an object where keys are joining table columns, and values are main table columns or a raw SQL:
7299
7285
  *
7300
- * `json` is stored as text, so it has text operators. Use the `jsonb` type for JSON operators.
7286
+ * ```ts
7287
+ * db.user.join(db.message, {
7288
+ * userId: 'id',
7301
7289
  *
7302
- * Takes a string, or sub-query returning string, or raw SQL expression as well as other operators.
7290
+ * // with table names:
7291
+ * 'message.userId': 'user.id',
7303
7292
  *
7304
- * ```ts
7305
- * db.table.where({
7306
- * textColumn: {
7307
- * // WHERE "textColumn" LIKE '%string%'
7308
- * contains: 'string',
7309
- * // WHERE "textColumn" ILIKE '%string%'
7310
- * containsInsensitive: 'string',
7311
- * // WHERE "textColumn" LIKE 'string%'
7312
- * startsWith: 'string',
7313
- * // WHERE "textColumn" ILIKE 'string%'
7314
- * startsWithInsensitive: 'string',
7315
- * // WHERE "textColumn" LIKE '%string'
7316
- * endsWith: 'string',
7317
- * // WHERE "textColumn" ILIKE '%string'
7318
- * endsWithInsensitive: 'string',
7319
- * },
7293
+ * // value can be a raw SQL expression:
7294
+ * text: db.user.sql`lower("user"."name")`,
7320
7295
  * });
7321
7296
  * ```
7322
7297
  *
7323
- * ### JSONB column operators
7324
- *
7325
- * For the `jsonb` column, note that the `json` type has text operators instead.
7326
- *
7327
- * `jsonPath` operator: compare a column value under a given JSON path with the provided value.
7328
- *
7329
- * Value can be of any type to compare with JSON value, or it can be a sub-query or a raw SQL expression.
7298
+ * Join all records without conditions by providing `true`:
7330
7299
  *
7331
7300
  * ```ts
7332
- * db.table.where({
7333
- * jsonbColumn: {
7334
- * jsonPath: [
7335
- * '$.name', // first element is JSON path
7336
- * '=', // second argument is comparison operator
7337
- * 'value', // third argument is a value to compare with
7338
- * ],
7339
- * },
7340
- * });
7301
+ * db.user.join(db.message, true);
7341
7302
  * ```
7342
7303
  *
7343
- * `jsonSupersetOf`: check if the column value is a superset of provided value.
7344
- *
7345
- * For instance, it is true if the column has JSON `{ "a": 1, "b": 2 }` and provided value is `{ "a": 1 }`.
7346
- *
7347
- * Takes the value of any type, or sub query which returns a single value, or a raw SQL expression.
7304
+ * Join methods can accept a callback with a special query builder that has `on` and `orOn` methods for handling advanced cases:
7348
7305
  *
7349
7306
  * ```ts
7350
- * db.table.where({
7351
- * jsonbColumn: {
7352
- * jsonSupersetOf: { a: 1 },
7353
- * },
7354
- * });
7355
- * ```
7356
- *
7357
- * `jsonSubsetOf`: check if the column value is a subset of provided value.
7358
- *
7359
- * For instance, it is true if the column has JSON `{ "a": 1 }` and provided value is `{ "a": 1, "b": 2 }`.
7360
- *
7361
- * Takes the value of any type, or sub query which returns a single value, or a raw SQL expression.
7362
- *
7363
- * ```ts
7364
- * db.table.where({
7365
- * jsonbColumn: {
7366
- * jsonSupersetOf: { a: 1 },
7367
- * },
7368
- * });
7307
+ * db.user.join(
7308
+ * db.message,
7309
+ * (q) =>
7310
+ * q
7311
+ * // left column is the db.message column, right column is the db.user column
7312
+ * .on('userId', 'id')
7313
+ * // table names can be provided:
7314
+ * .on('message.userId', 'user.id')
7315
+ * // operator can be specified:
7316
+ * .on('userId', '!=', 'id')
7317
+ * // operator can be specified with table names as well:
7318
+ * .on('message.userId', '!=', 'user.id')
7319
+ * // `.orOn` takes the same arguments as `.on` and acts like `.or`:
7320
+ * .on('userId', 'id') // where message.userId = user.id
7321
+ * .orOn('text', 'name'), // or message.text = user.name
7322
+ * );
7369
7323
  * ```
7370
7324
  *
7371
- * @param args - {@link WhereArgs}
7372
- */
7373
- where(...args) {
7374
- return _queryWhere(
7375
- this.clone(),
7376
- args
7377
- );
7378
- }
7379
- /**
7380
- * Use a custom SQL expression in `WHERE` statement:
7325
+ * Column names in the where conditions are applied for the joined table, but you can specify a table name to add a condition for the main table.
7381
7326
  *
7382
7327
  * ```ts
7383
- * db.table.where`a = b`;
7384
- *
7385
- * // or
7386
- * db.table.where(db.table.sql`a = b`);
7328
+ * db.user.join(db.message, (q) =>
7329
+ * q
7330
+ * .on('userId', 'id')
7331
+ * .where({
7332
+ * // not prefixed column name is for joined table:
7333
+ * text: { startsWith: 'hello' },
7334
+ * // specify a table name to set condition on the main table:
7335
+ * 'user.name': 'Bob',
7336
+ * })
7337
+ * // id is a column of a joined table Message
7338
+ * .whereIn('id', [1, 2, 3])
7339
+ * // condition for id of a user
7340
+ * .whereIn('user.id', [4, 5, 6]),
7341
+ * );
7342
+ * ```
7387
7343
  *
7388
- * // or
7389
- * import { raw } from 'orchid-orm';
7344
+ * The query above will generate the following SQL (simplified):
7390
7345
  *
7391
- * db.table.where(raw`a = b`);
7346
+ * ```sql
7347
+ * SELECT * FROM "user"
7348
+ * JOIN "message"
7349
+ * ON "message"."userId" = "user"."id"
7350
+ * AND "message"."text" ILIKE 'hello%'
7351
+ * AND "user"."name" = 'Bob'
7352
+ * AND "message"."id" IN (1, 2, 3)
7353
+ * AND "user"."id" IN (4, 5, 6)
7392
7354
  * ```
7393
7355
  *
7394
- * @param args - SQL expression
7395
- */
7396
- whereSql(...args) {
7397
- return _queryWhereSql(
7398
- this.clone(),
7399
- args
7400
- );
7401
- }
7402
- /**
7403
- * `whereNot` takes the same argument as `where`,
7404
- * multiple conditions are combined with `AND`,
7405
- * the whole group of conditions is negated with `NOT`.
7356
+ * The join argument can be a query with `select`, `where`, and other methods. In such case, it will be handled as a sub query:
7406
7357
  *
7407
7358
  * ```ts
7408
- * // find records of different colors than red
7409
- * db.table.whereNot({ color: 'red' });
7410
- * // WHERE NOT color = 'red'
7411
- * db.table.whereNot({ one: 1, two: 2 });
7412
- * // WHERE NOT (one = 1 AND two = 2)
7359
+ * db.user.join(
7360
+ * db.message
7361
+ * .select('id', 'userId', 'text')
7362
+ * .where({ text: { startsWith: 'Hi' } })
7363
+ * .as('t'),
7364
+ * 'userId',
7365
+ * 'id',
7366
+ * );
7413
7367
  * ```
7414
7368
  *
7415
- * @param args - {@link WhereArgs}
7416
- */
7417
- whereNot(...args) {
7418
- return _queryWhereNot(
7419
- this.clone(),
7420
- args
7421
- );
7422
- }
7423
- /**
7424
- * `whereNot` version accepting SQL expression:
7369
+ * It will produce such SQL:
7425
7370
  *
7426
- * ```ts
7427
- * db.table.whereNot`sql expression`
7371
+ * ```sql
7372
+ * SELECT * FROM "user"
7373
+ * JOIN (
7374
+ * SELECT "t"."id", "t"."userId", "t"."text"
7375
+ * FROM "message" AS "t"
7376
+ * ) "t" ON "t"."userId" = "user"."id"
7428
7377
  * ```
7429
7378
  *
7430
- * @param args - SQL expression
7431
- */
7432
- whereNotSql(...args) {
7433
- return _queryWhereNotSql(this.clone(), args);
7434
- }
7435
- /**
7436
- * `orWhere` is accepting the same arguments as {@link where}, joining arguments with `OR`.
7379
+ * ## implicit join lateral
7437
7380
  *
7438
- * Columns in single arguments are still joined with `AND`.
7381
+ * `JOIN`'s source expression that comes before `ON` cannot access other tables, but in some cases this may be needed.
7439
7382
  *
7440
- * The database is processing `AND` before `OR`, so this should be intuitively clear.
7383
+ * For example, let's consider joining last 10 messages of a user:
7441
7384
  *
7442
7385
  * ```ts
7443
- * db.table.where({ id: 1, color: 'red' }).orWhere({ id: 2, color: 'blue' });
7444
- * // equivalent:
7445
- * db.table.orWhere({ id: 1, color: 'red' }, { id: 2, color: 'blue' });
7386
+ * await db.user.join('messages', (q) => q.order({ createdAt: 'DESC' }).limit(10));
7446
7387
  * ```
7447
7388
  *
7448
- * This query will produce such SQL (simplified):
7389
+ * When the `join`'s callback returns a more complex query than the one that simply applies certain conditions,
7390
+ * it will implicitly generate a `JOIN LATERAL` SQL query, as the following:
7449
7391
  *
7450
7392
  * ```sql
7451
- * SELECT * FROM "table"
7452
- * WHERE id = 1 AND color = 'red'
7453
- * OR id = 2 AND color = 'blue'
7393
+ * SELECT *
7394
+ * FROM "user"
7395
+ * JOIN LATERAL (
7396
+ * SELECT *
7397
+ * FROM "message" AS "messages"
7398
+ * WHERE "message"."userId" = "user"."id"
7399
+ * ORDER BY "message"."createdAt" DESC
7400
+ * LIMIT 10
7401
+ * ) "messages" ON true
7454
7402
  * ```
7455
7403
  *
7456
- * @param args - {@link WhereArgs} will be joined with `OR`
7457
- */
7458
- orWhere(...args) {
7459
- return _queryOr(this.clone(), args);
7460
- }
7461
- /**
7462
- * `orWhereNot` takes the same arguments as {@link orWhere}, and prepends each condition with `NOT` just as {@link whereNot} does.
7463
- *
7464
- * @param args - {@link WhereArgs} will be prefixed with `NOT` and joined with `OR`
7404
+ * @param arg - {@link JoinFirstArg}
7405
+ * @param args - {@link JoinArgs}
7465
7406
  */
7466
- orWhereNot(...args) {
7467
- return _queryOrNot(
7407
+ join(arg, ...args) {
7408
+ return _join(
7468
7409
  this.clone(),
7410
+ // eslint-disable-line @typescript-eslint/no-explicit-any
7411
+ true,
7412
+ "JOIN",
7413
+ arg,
7469
7414
  args
7415
+ // eslint-disable-line @typescript-eslint/no-explicit-any
7470
7416
  );
7471
7417
  }
7472
7418
  /**
7473
- * `whereIn` and related methods are for the `IN` operator to check for inclusion in a list of values.
7474
- *
7475
- * When used with a single column it works equivalent to the `in` column operator:
7419
+ * `leftJoin` is a method for SQL `LEFT JOIN`, which is equivalent to `OUTER JOIN`, `LEFT OUTER JOIN`.
7476
7420
  *
7477
- * ```ts
7478
- * db.table.whereIn('column', [1, 2, 3]);
7479
- * // the same as:
7480
- * db.table.where({ column: [1, 2, 3] });
7481
- * ```
7421
+ * When no matching record is found, it will fill joined table columns with `NULL` values in the result rows.
7482
7422
  *
7483
- * `whereIn` can support a tuple of columns, that's what the `in` operator cannot support:
7423
+ * Works just like `join`, except for result type that may have `null`:
7484
7424
  *
7485
7425
  * ```ts
7486
- * db.table.whereIn(
7487
- * ['id', 'name'],
7488
- * [
7489
- * [1, 'Alice'],
7490
- * [2, 'Bob'],
7491
- * ],
7492
- * );
7493
- * ```
7426
+ * const result = await db.user
7427
+ * .leftJoin('messages')
7428
+ * .select('name', 'messages.text');
7494
7429
  *
7495
- * It supports sub query which should return records with columns of the same type:
7430
+ * // the same query, but joining table explicitly
7431
+ * const result2: typeof result = await db.user
7432
+ * .leftJoin(db.message, 'userId', 'id')
7433
+ * .select('name', 'message.text');
7496
7434
  *
7497
- * ```ts
7498
- * db.table.whereIn(['id', 'name'], OtherTable.select('id', 'name'));
7435
+ * // result has the following type:
7436
+ * const ok: { name: string; text: string | null }[] = result;
7499
7437
  * ```
7500
7438
  *
7501
- * It supports raw SQL expression:
7502
- *
7503
- * ```ts
7504
- * db.table.whereIn(['id', 'name'], db.table.sql`((1, 'one'), (2, 'two'))`);
7505
- * ```
7439
+ * @param arg - {@link JoinFirstArg}
7440
+ * @param args - {@link JoinArgs}
7506
7441
  */
7507
- whereIn(...args) {
7508
- return _queryWhereIn(
7442
+ leftJoin(arg, ...args) {
7443
+ return _join(
7509
7444
  this.clone(),
7510
- true,
7511
- args[0],
7512
- args[1]
7445
+ // eslint-disable-line @typescript-eslint/no-explicit-any
7446
+ false,
7447
+ "LEFT JOIN",
7448
+ arg,
7449
+ args
7450
+ // eslint-disable-line @typescript-eslint/no-explicit-any
7513
7451
  );
7514
7452
  }
7515
7453
  /**
7516
- * Takes the same arguments as {@link whereIn}.
7517
- * Add a `WHERE IN` condition prefixed with `OR` to the query:
7454
+ * `rightJoin` is a method for SQL `RIGHT JOIN`, which is equivalent to `RIGHT OUTER JOIN`.
7455
+ *
7456
+ * Takes the same arguments as `json`.
7457
+ *
7458
+ * It will load all records from the joining table, and fill the main table columns with `null` when no match is found.
7459
+ *
7460
+ * The columns of the table you're joining to are becoming nullable when using `rightJoin`.
7518
7461
  *
7519
7462
  * ```ts
7520
- * db.table.whereIn('a', [1, 2, 3]).orWhereIn('b', ['one', 'two']);
7463
+ * const result = await db.user
7464
+ * .rightJoin('messages')
7465
+ * .select('name', 'messages.text');
7466
+ *
7467
+ * // even though name is not a nullable column, it becomes nullable after using rightJoin
7468
+ * const ok: { name: string | null; text: string }[] = result;
7521
7469
  * ```
7470
+ *
7471
+ * @param arg - {@link JoinFirstArg}
7472
+ * @param args - {@link JoinArgs}
7522
7473
  */
7523
- orWhereIn(...args) {
7524
- return _queryWhereIn(
7474
+ rightJoin(arg, ...args) {
7475
+ return _join(
7525
7476
  this.clone(),
7526
- false,
7527
- args[0],
7528
- args[1]
7477
+ // eslint-disable-line @typescript-eslint/no-explicit-any
7478
+ true,
7479
+ "RIGHT JOIN",
7480
+ arg,
7481
+ args
7482
+ // eslint-disable-line @typescript-eslint/no-explicit-any
7529
7483
  );
7530
7484
  }
7531
7485
  /**
7532
- * Acts as `whereIn`, but negates the condition with `NOT`:
7486
+ * `fullJoin` is a method for SQL `FULL JOIN`, which is equivalent to `FULL OUTER JOIN`.
7487
+ *
7488
+ * Takes the same arguments as `json`.
7489
+ *
7490
+ * It will load all records from the joining table, both sides of the join may result in `null` values when there is no match.
7491
+ *
7492
+ * All columns become nullable after using `fullJoin`.
7533
7493
  *
7534
7494
  * ```ts
7535
- * db.table.whereNotIn('color', ['red', 'green', 'blue']);
7495
+ * const result = await db.user
7496
+ * .rightJoin('messages')
7497
+ * .select('name', 'messages.text');
7498
+ *
7499
+ * // all columns can be null
7500
+ * const ok: { name: string | null; text: string | null }[] = result;
7536
7501
  * ```
7502
+ *
7503
+ * @param arg - {@link JoinFirstArg}
7504
+ * @param args - {@link JoinArgs}
7537
7505
  */
7538
- whereNotIn(...args) {
7539
- return _queryWhereIn(
7506
+ fullJoin(arg, ...args) {
7507
+ return _join(
7540
7508
  this.clone(),
7541
- true,
7542
- args[0],
7543
- args[1],
7544
- true
7509
+ // eslint-disable-line @typescript-eslint/no-explicit-any
7510
+ false,
7511
+ "FULL JOIN",
7512
+ arg,
7513
+ args
7514
+ // eslint-disable-line @typescript-eslint/no-explicit-any
7545
7515
  );
7546
7516
  }
7547
7517
  /**
7548
- * Acts as `whereIn`, but prepends `OR` to the condition and negates it with `NOT`:
7518
+ * `joinLateral` allows joining a table with a sub-query that can reference the main table of current query and the other joined tables.
7549
7519
  *
7550
- * ```ts
7551
- * db.table.whereNotIn('a', [1, 2, 3]).orWhereNoIn('b', ['one', 'two']);
7552
- * ```
7553
- */
7554
- orWhereNotIn(...args) {
7555
- return _queryWhereIn(
7556
- this.clone(),
7557
- false,
7558
- args[0],
7559
- args[1],
7560
- true
7561
- );
7562
- }
7563
- /**
7564
- * `whereExists` is for support of the `WHERE EXISTS (query)` clause.
7520
+ * First argument is the other table you want to join, or a name of relation, or a name of `with` defined table.
7565
7521
  *
7566
- * This method is accepting the same arguments as `join`, see the {@link Join.join} section for more details.
7522
+ * Second argument is a callback where you can reference other tables using `on` and `orOn`, select columns, do `where` conditions, and use any other query methods to build a sub-query.
7523
+ *
7524
+ * Note that the regular `join` will also generate `JOIN LATERAL` SQL expression when the query returned from callback is complex enough (see the bottom of {@link join} description).
7567
7525
  *
7568
7526
  * ```ts
7569
- * // find users who have accounts
7570
- * // find by a relation name if it's defined
7571
- * db.user.whereExists('account');
7527
+ * // joinLateral a Message table, alias it as `m`
7528
+ * // without aliasing you can refer to the message by a table name
7529
+ * User.joinLateral(Message.as('m'), (q) =>
7530
+ * q
7531
+ * // select message columns
7532
+ * .select('text')
7533
+ * // join the message to the user, column names can be prefixed with table names
7534
+ * .on('authorId', 'id')
7535
+ * // message columns are available without prefixing,
7536
+ * // outer table columns are available with a table name
7537
+ * .where({ text: 'some text', 'user.name': 'name' })
7538
+ * .order({ createdAt: 'DESC' }),
7539
+ * )
7540
+ * // only selected message columns are available in select and where
7541
+ * .select('id', 'name', 'm.text')
7542
+ * .where({ 'm.text': messageData.text });
7543
+ * ```
7572
7544
  *
7573
- * // find using a table and a join conditions
7574
- * db.user.whereExists(db.account, 'account.id', 'user.id');
7545
+ * As well as simple `join`, `joinLateral` can select an object of full joined record:
7575
7546
  *
7576
- * // find using a query builder in a callback:
7577
- * db.user.whereExists(db.account, (q) => q.on('account.id', '=', 'user.id'));
7547
+ * ```ts
7548
+ * // join by relation name
7549
+ * const result = await User.joinLateral(
7550
+ * 'messages',
7551
+ * (q) => q.as('message'), // alias to 'message'
7552
+ * ).select('name', { message: 'message.*' });
7553
+ *
7554
+ * // result has the following type:
7555
+ * const ok: {
7556
+ * name: string;
7557
+ * // full message is included:
7558
+ * message: { id: number; text: string; updatedAt: Date; createdAt: Date };
7559
+ * }[] = result;
7560
+ * ```
7561
+ *
7562
+ * `message` can be aliased withing the `select` as well as in case of a simple `join`:
7563
+ *
7564
+ * ```ts
7565
+ * // join by relation name
7566
+ * const result = await User.joinLateral(
7567
+ * 'messages',
7568
+ * (q) => q.as('message'), // alias to 'message'
7569
+ * ).select('name', { msg: 'message.*' });
7570
+ *
7571
+ * // result has the following type:
7572
+ * const ok: {
7573
+ * name: string;
7574
+ * // full message is included as msg:
7575
+ * msg: { id: number; text: string; updatedAt: Date; createdAt: Date };
7576
+ * }[] = result;
7578
7577
  * ```
7578
+ *
7579
+ * @param arg - {@link JoinFirstArg}
7580
+ * @param cb - {@link JoinLateralCallback}
7579
7581
  */
7580
- whereExists(arg, ...args) {
7581
- return _queryWhere(
7582
+ joinLateral(arg, cb) {
7583
+ return _joinLateral(
7582
7584
  this.clone(),
7583
- existsArgs(arg, args)
7585
+ // eslint-disable-line @typescript-eslint/no-explicit-any
7586
+ "JOIN",
7587
+ arg,
7588
+ cb
7584
7589
  );
7585
7590
  }
7586
7591
  /**
7587
- * Acts as `whereExists`, but prepends the condition with `OR`:
7592
+ * The same as {@link joinLateral}, but when no records found for the join it will result in `null`:
7588
7593
  *
7589
7594
  * ```ts
7590
- * // find users who have an account or a profile,
7591
- * // imagine that the user has both `account` and `profile` relations defined.
7592
- * db.user.whereExist('account').orWhereExists('profile');
7595
+ * const result = await db.user
7596
+ * .leftJoinLateral('messages', (q) => q.as('message'))
7597
+ * .select('name', 'message.text');
7598
+ *
7599
+ * // result has the following type:
7600
+ * const ok: { name: string; text: string | null }[] = result;
7593
7601
  * ```
7602
+ *
7603
+ * @param arg - {@link JoinFirstArg}
7604
+ * @param cb - {@link JoinLateralCallback}
7594
7605
  */
7595
- orWhereExists(arg, ...args) {
7596
- return _queryOr(
7606
+ leftJoinLateral(arg, cb) {
7607
+ return _joinLateral(
7597
7608
  this.clone(),
7598
- existsArgs(arg, args)
7609
+ // eslint-disable-line @typescript-eslint/no-explicit-any
7610
+ "LEFT JOIN",
7611
+ arg,
7612
+ cb
7599
7613
  );
7600
7614
  }
7615
+ }
7616
+ const makeOnItem = (joinTo, joinFrom, args) => {
7617
+ return {
7618
+ ON: {
7619
+ joinTo,
7620
+ joinFrom,
7621
+ on: args
7622
+ }
7623
+ };
7624
+ };
7625
+ const pushQueryOn = (q, joinFrom, joinTo, ...on) => {
7626
+ return pushQueryValue(
7627
+ q,
7628
+ "and",
7629
+ makeOnItem(joinFrom, joinTo, on)
7630
+ );
7631
+ };
7632
+ const pushQueryOrOn = (q, joinFrom, joinTo, ...on) => {
7633
+ return pushQueryValue(q, "or", [
7634
+ makeOnItem(joinFrom, joinTo, on)
7635
+ ]);
7636
+ };
7637
+ const addQueryOn = (q, joinFrom, joinTo, ...args) => {
7638
+ const cloned = q.clone();
7639
+ setQueryObjectValue(
7640
+ cloned,
7641
+ "joinedShapes",
7642
+ joinFrom.q.as || joinFrom.table,
7643
+ joinFrom.q.shape
7644
+ );
7645
+ return pushQueryOn(cloned, joinFrom, joinTo, ...args);
7646
+ };
7647
+ const _queryJoinOn = (q, args) => {
7648
+ return pushQueryOn(
7649
+ q,
7650
+ q.q.joinTo,
7651
+ q,
7652
+ ...args
7653
+ );
7654
+ };
7655
+ const _queryJoinOrOn = (q, args) => {
7656
+ return pushQueryOrOn(
7657
+ q,
7658
+ q.q.joinTo,
7659
+ q,
7660
+ ...args
7661
+ );
7662
+ };
7663
+ const _queryJoinOnJsonPathEquals = (q, args) => {
7664
+ return pushQueryValue(q, "and", {
7665
+ ON: args
7666
+ });
7667
+ };
7668
+ class OnMethods {
7601
7669
  /**
7602
- * Acts as `whereExists`, but negates the condition with `NOT`:
7670
+ * Use `on` to specify columns to join records.
7603
7671
  *
7604
7672
  * ```ts
7605
- * // find users who don't have an account,
7606
- * // image that the user `belongsTo` or `hasOne` account.
7607
- * db.user.whereNotExist('account');
7673
+ * q
7674
+ * // left column is the db.message column, right column is the db.user column
7675
+ * .on('userId', 'id')
7676
+ * // table names can be provided:
7677
+ * .on('message.userId', 'user.id')
7678
+ * // operator can be specified:
7679
+ * .on('userId', '!=', 'id')
7680
+ * // operator can be specified with table names as well:
7681
+ * .on('message.userId', '!=', 'user.id')
7682
+ * // `.orOn` takes the same arguments as `.on` and acts like `.or`:
7683
+ * .on('userId', 'id') // where message.userId = user.id
7608
7684
  * ```
7609
7685
  *
7610
- * @param arg - relation name, or a query object, or a `with` table alias, or a callback returning a query object.
7611
- * @param args - no arguments needed when the first argument is a relation name, or conditions to join the table with.
7686
+ * @param args - columns to join with
7612
7687
  */
7613
- whereNotExists(arg, ...args) {
7614
- return _queryWhereNot(
7615
- this.clone(),
7616
- existsArgs(arg, args)
7617
- );
7688
+ on(...args) {
7689
+ return _queryJoinOn(this.clone(), args);
7618
7690
  }
7619
7691
  /**
7620
- * Acts as `whereExists`, but prepends the condition with `OR` and negates it with `NOT`:
7692
+ * Works as {@link on}, but the added conditions will be separated from previous with `OR`.
7693
+ *
7694
+ * @param args - columns to join with
7695
+ */
7696
+ orOn(...args) {
7697
+ return _queryJoinOrOn(this.clone(), args);
7698
+ }
7699
+ /**
7700
+ * Use `onJsonPathEquals` to join record based on a field of their JSON column:
7621
7701
  *
7622
7702
  * ```ts
7623
- * // find users who don't have an account OR who don't have a profile
7624
- * // imagine that the user has both `account` and `profile` relations defined.
7625
- * db.user.whereNotExists('account').orWhereNotExists('profile');
7703
+ * db.table.join(db.otherTable, (q) =>
7704
+ * // '$.key' is a JSON path
7705
+ * q.onJsonPathEquals('otherTable.data', '$.key', 'table.data', '$.key'),
7706
+ * );
7626
7707
  * ```
7708
+ *
7709
+ * @param args - columns and JSON paths to join with.
7627
7710
  */
7628
- orWhereNotExists(arg, ...args) {
7629
- return _queryOrNot(
7630
- this.clone(),
7631
- existsArgs(arg, args)
7632
- );
7711
+ onJsonPathEquals(...args) {
7712
+ return _queryJoinOnJsonPathEquals(this.clone(), args);
7633
7713
  }
7634
7714
  }
7635
- class WhereQueryBase extends QueryBase {
7636
- }
7637
- orchidCore.applyMixins(WhereQueryBase, [Where]);
7638
7715
 
7639
- class Join {
7716
+ class JsonModifiers {
7640
7717
  /**
7641
- * ## Select relation
7642
- *
7643
- * Before joining a table, consider if selecting a relation is enough for your case:
7644
- *
7645
- * ```ts
7646
- * // select users with profiles
7647
- * // result type is Array<{ name: string, profile: Profile }>
7648
- * await db.user.select('name', {
7649
- * profile: (q) => q.profile,
7650
- * });
7651
- *
7652
- * // select posts with counts of comments, order by comments count
7653
- * // result type is Array<Post & { commentsCount: number }>
7654
- * await db.post
7655
- * .select('*', {
7656
- * commentsCount: (q) => q.comments.count(),
7657
- * })
7658
- * .order({
7659
- * commentsCount: 'DESC',
7660
- * });
7661
- *
7662
- * // select authors with array of their book titles
7663
- * // result type is Array<Author & { books: string[] }>
7664
- * await db.author.select('*', {
7665
- * books: (q) => q.books.pluck('title'),
7666
- * });
7667
- * ```
7718
+ * Return a JSON value/object/array where a given value is set at the given path.
7719
+ * The path is an array of keys to access the value.
7668
7720
  *
7669
- * Internally, such selects will use `LEFT JOIN LATERAL` to join a relation.
7670
- * If you're loading users with profiles (one-to-one relation), and some users don't have a profile, `profile` property will have `NULL` for such users.
7671
- * If you want to load only users that have profiles, and filter out the rest, add `.join()` method to the relation without arguments:
7721
+ * Can be used in `update` callback.
7672
7722
  *
7673
7723
  * ```ts
7674
- * // load only users who have a profile
7675
- * await db.user.select('*', {
7676
- * profile: (q) => q.profile.join(),
7677
- * });
7724
+ * const result = await db.table.jsonSet('data', ['name'], 'new value').take();
7678
7725
  *
7679
- * // load only users who have a specific profile
7680
- * await db.user.select('*', {
7681
- * profile: (q) => q.profile.join().where({ age: { gt: 20 } }),
7682
- * });
7726
+ * expect(result.data).toEqual({ name: 'new value' });
7683
7727
  * ```
7684
7728
  *
7685
- * You can also use this `.join()` method on the one-to-many relations, and records with empty array will be filtered out:
7729
+ * Optionally takes parameters of type `{ as?: string, createIfMissing?: boolean }`
7686
7730
  *
7687
7731
  * ```ts
7688
- * // posts that have no tags won't be loaded
7689
- * // result type is Array<Post & { tags: Tag[] }>
7690
- * db.post.select('*', {
7691
- * tags: (q) => q.tags.join(),
7732
+ * await db.table.jsonSet('data', ['name'], 'new value', {
7733
+ * as: 'alias', // select data as `alias`
7734
+ * createIfMissing: true, // ignored if missing by default
7692
7735
  * });
7693
7736
  * ```
7694
7737
  *
7695
- * # Joins
7696
- *
7697
- * `join` methods allows to join other tables, relations by name, [with](/guide/advanced-queries#with) statements, sub queries.
7698
- *
7699
- * All the `join` methods accept the same arguments, but returning type is different because with `join` it's guaranteed to load joined table, and with `leftJoin` the joined table columns may be `NULL` when no matching record was found.
7700
- *
7701
- * For the following examples, imagine we have a `User` table with `id` and `name`, and `Message` table with `id`, `text`, messages belongs to user via `userId` column:
7702
- *
7703
- * ```ts
7704
- * export class UserTable extends BaseTable {
7705
- * readonly table = 'user';
7706
- * columns = this.setColumns((t) => ({
7707
- * id: t.identity().primaryKey(),
7708
- * name: t.text(),
7709
- * }));
7738
+ * @param column - name of JSON column, or a result of a nested json method
7739
+ * @param path - path to value inside the json
7740
+ * @param value - value to set into the json
7741
+ * @param options - `as` to alias the json value when selecting, `createIfMissing: true` will create a new JSON property if it didn't exist before
7742
+ */
7743
+ jsonSet(column, path, value, options) {
7744
+ var _a;
7745
+ const q = this.clone();
7746
+ const json = {
7747
+ __json: [
7748
+ "set",
7749
+ (_a = options == null ? void 0 : options.as) != null ? _a : typeof column === "string" ? column : column.__json[1],
7750
+ typeof column === "string" ? q.q.shape[column] : column.__json[2],
7751
+ column,
7752
+ path,
7753
+ value,
7754
+ options
7755
+ ]
7756
+ };
7757
+ return Object.assign(
7758
+ pushQueryValue(q, "select", json),
7759
+ json
7760
+ );
7761
+ }
7762
+ /**
7763
+ * Return a JSON value/object/array where a given value is inserted at the given JSON path. Value can be a single value or JSON object. If a value exists at the given path, the value is not replaced.
7710
7764
  *
7711
- * relations = {
7712
- * messages: this.hasMany(() => MessageTable, {
7713
- * primaryKey: 'id',
7714
- * foreignKey: 'userId',
7715
- * }),
7716
- * };
7717
- * }
7765
+ * Can be used in `update` callback.
7718
7766
  *
7719
- * export class MessageTable extends BaseTable {
7720
- * readonly table = 'message';
7721
- * columns = this.setColumns((t) => ({
7722
- * id: t.identity().primaryKey(),
7723
- * text: t.text(),
7724
- * ...t.timestamps(),
7725
- * }));
7767
+ * ```ts
7768
+ * // imagine user has data = { tags: ['two'] }
7769
+ * const result = await db.table.jsonInsert('data', ['tags', 0], 'one').take();
7726
7770
  *
7727
- * relations = {
7728
- * user: this.belongsTo(() => UserTable, {
7729
- * primaryKey: 'id',
7730
- * foreignKey: 'userId',
7731
- * }),
7732
- * };
7733
- * }
7771
+ * // 'one' is inserted to 0 position
7772
+ * expect(result.data).toEqual({ tags: ['one', 'two'] });
7734
7773
  * ```
7735
7774
  *
7736
- * ## join
7737
- *
7738
- * `join` is a method for SQL `JOIN`, which is equivalent to `INNER JOIN`, `LEFT INNERT JOIN`.
7739
- *
7740
- * When no matching record is found, it will skip records of the main table.
7741
- *
7742
- * When joining the same table with the same condition more than once, duplicated joins will be ignored:
7775
+ * Optionally takes parameters of type `{ as?: string, insertAfter?: boolean }`
7743
7776
  *
7744
7777
  * ```ts
7745
- * // joining a relation
7746
- * db.post.join('comments').join('comments');
7747
- *
7748
- * // joining a table with a condition
7749
- * db.post
7750
- * .join('comments', 'comments.postId', 'post.id')
7751
- * .join('comments', 'comments.postId', 'post.id');
7752
- * ```
7753
- *
7754
- * Both queries will produce SQL with only 1 join
7778
+ * // imagine user has data = { tags: ['one'] }
7779
+ * const result = await db.table
7780
+ * .jsonInsert('data', ['tags', 0], 'two', {
7781
+ * as: 'alias', // select as an alias
7782
+ * insertAfter: true, // insert after the specified position
7783
+ * })
7784
+ * .take();
7755
7785
  *
7756
- * ```sql
7757
- * SELECT * FROM post JOIN comments ON comments.postId = post.id
7786
+ * // 'one' is inserted to 0 position
7787
+ * expect(result.alias).toEqual({ tags: ['one', 'two'] });
7758
7788
  * ```
7789
+ * @param column - name of JSON column, or a result of a nested json method
7790
+ * @param path - path to the array inside the json, last path element is index to insert into
7791
+ * @param value - value to insert into the json array
7792
+ * @param options - `as` to alias the json value when selecting, `insertAfter: true` to insert after the specified position
7793
+ */
7794
+ jsonInsert(column, path, value, options) {
7795
+ var _a;
7796
+ const q = this.clone();
7797
+ const json = {
7798
+ __json: [
7799
+ "insert",
7800
+ (_a = options == null ? void 0 : options.as) != null ? _a : typeof column === "string" ? column : column.__json[1],
7801
+ typeof column === "string" ? q.q.shape[column] : column.__json[2],
7802
+ column,
7803
+ path,
7804
+ value,
7805
+ options
7806
+ ]
7807
+ };
7808
+ return Object.assign(
7809
+ pushQueryValue(q, "select", json),
7810
+ json
7811
+ );
7812
+ }
7813
+ /**
7814
+ * Return a JSON value/object/array where a given value is removed at the given JSON path.
7759
7815
  *
7760
- * However, this is only possible if the join has no dynamic values:
7816
+ * Can be used in `update` callback.
7761
7817
  *
7762
7818
  * ```ts
7763
- * db.post
7764
- * .join('comments', (q) => q.where({ rating: { gt: 5 } }))
7765
- * .join('comments', (q) => q.where({ rating: { gt: 5 } }));
7766
- * ```
7767
- *
7768
- * Both joins above have the same `{ gt: 5 }`, but still, the `5` is a dynamic value and in this case joins will be duplicated,
7769
- * resulting in a database error.
7819
+ * // imagine a user has data = { tags: ['one', 'two'] }
7820
+ * const result = await db.table
7821
+ * .jsonRemove(
7822
+ * 'data',
7823
+ * ['tags', 0],
7824
+ * // optional parameters:
7825
+ * {
7826
+ * as: 'alias', // select as an alias
7827
+ * },
7828
+ * )
7829
+ * .take();
7770
7830
  *
7771
- * ### join relation
7831
+ * expect(result.alias).toEqual({ tags: ['two'] });
7832
+ * ```
7772
7833
  *
7773
- * When relations are defined between the tables, you can join them by a relation name.
7774
- * Joined table can be references from `where` and `select` by a relation name.
7834
+ * @param column - name of JSON column, or a result of a nested json method
7835
+ * @param path - path to the array inside the json, last path element is index to remove this element
7836
+ * @param options - `as` to alias the json value when selecting
7837
+ */
7838
+ jsonRemove(column, path, options) {
7839
+ var _a;
7840
+ const q = this.clone();
7841
+ const json = {
7842
+ __json: [
7843
+ "remove",
7844
+ (_a = options == null ? void 0 : options.as) != null ? _a : typeof column === "string" ? column : column.__json[1],
7845
+ typeof column === "string" ? q.q.shape[column] : column.__json[2],
7846
+ column,
7847
+ path
7848
+ ]
7849
+ };
7850
+ return Object.assign(
7851
+ pushQueryValue(q, "select", json),
7852
+ json
7853
+ );
7854
+ }
7855
+ /**
7856
+ * Selects a value from JSON data using a JSON path.
7775
7857
  *
7776
7858
  * ```ts
7777
- * const result = await db.user
7778
- * .join('messages')
7779
- * // after joining a table, we can use it in `where` conditions:
7780
- * .where({ 'messages.text': { startsWith: 'Hi' } })
7781
- * .select(
7782
- * 'name', // name is User column, table name may be omitted
7783
- * 'messages.text', // text is the Message column, and the table name is required
7784
- * );
7785
- *
7786
- * // result has the following type:
7787
- * const ok: { name: string; text: string }[] = result;
7788
- * ```
7859
+ * import { columnTypes } from 'orchid-orm';
7789
7860
  *
7790
- * The first argument can also be a callback, where instead of relation name as a string we're picking it as a property of `q`.
7791
- * In such a way, we can alias the relation with `as`, add `where` conditions, use other query methods.
7861
+ * db.table.jsonPathQuery(
7862
+ * columnTypes.text(3, 100), // type of the value
7863
+ * 'data', // name of the JSON column
7864
+ * '$.name', // JSON path
7865
+ * 'name', // select value as name
7792
7866
  *
7793
- * ```ts
7794
- * const result = await db.user.join((q) =>
7795
- * q.messages.as('m').where({ text: 'some text' }),
7867
+ * // Optionally supports `vars` and `silent` options
7868
+ * // check Postgres docs for jsonb_path_query for details
7869
+ * {
7870
+ * vars: 'vars',
7871
+ * silent: true,
7872
+ * },
7796
7873
  * );
7797
7874
  * ```
7798
7875
  *
7799
- * Optionally, you can pass a second callback argument, it makes `on` and `orOn` methods available.
7800
- *
7801
- * But remember that when joining a relation, the needed `ON` conditions are already handled automatically.
7876
+ * Nested JSON operations can be used in place of JSON column name:
7802
7877
  *
7803
7878
  * ```ts
7804
- * const result = await db.user.join(
7805
- * (q) => q.messages.as('m'),
7806
- * (q) =>
7807
- * q
7808
- * .on('text', 'name') // additionally, match message with user name
7809
- * .where({ text: 'some text' }), // you can add `where` in a second callback as well.
7879
+ * db.table.jsonPathQuery(
7880
+ * columnTypes.text(3, 100),
7881
+ * // Available: .jsonSet, .jsonInsert, .jsonRemove
7882
+ * db.table.jsonSet('data', ['key'], 'value'),
7883
+ * '$.name',
7884
+ * 'name',
7810
7885
  * );
7811
7886
  * ```
7812
7887
  *
7813
- * ### Selecting full joined records
7814
- *
7815
- * `select` supports selecting a full record of a previously joined table by passing a table name with `.*` at the end:
7888
+ * @param type - provide a column type to have a correct result type
7889
+ * @param column - name of JSON column, or a result of a nested json method
7890
+ * @param path - special JSON path string to reference a JSON value
7891
+ * @param as - optional alias for the selected value
7892
+ * @param options - supports `vars` and `silent`, check Postgres docs of `json_path_query` for these
7893
+ */
7894
+ jsonPathQuery(type, column, path, as, options) {
7895
+ const q = this.clone();
7896
+ const json = {
7897
+ __json: ["pathQuery", as, type, column, path, options]
7898
+ };
7899
+ return Object.assign(
7900
+ pushQueryValue(q, "select", json),
7901
+ json
7902
+ );
7903
+ }
7904
+ }
7905
+ class JsonMethods {
7906
+ /**
7907
+ * Wraps the query in a way to select a single JSON string.
7908
+ * So that JSON encoding is done on a database side, and the application doesn't have to turn a response to a JSON.
7909
+ * It may be better for performance in some cases.
7816
7910
  *
7817
7911
  * ```ts
7818
- * const result = await db.book.join('author').select('title', {
7819
- * author: 'author.*',
7820
- * });
7821
- *
7822
- * // result has the following type:
7823
- * const ok: {
7824
- * // title of the book
7825
- * title: string;
7826
- * // a full author record is included:
7827
- * author: { id: number; name: string; updatedAt: Date; createdAt: Date };
7828
- * }[] = result;
7912
+ * // json is a JSON string that you can directly send as a response.
7913
+ * const json = await db.table.select('id', 'name').json();
7829
7914
  * ```
7830
7915
  *
7831
- * It works fine for `1:1` (`belongsTo`, `hasOne`) relations, but it may have an unexpected result for `1:M` or `M:M` (`hasMany`, `hasAndBelongsToMany`) relations.
7832
- * For any kind of relation, it results in one main table record with data of exactly one joined table record, i.e. when selecting in this way, the records **won't** be collected into arrays.
7833
- *
7834
- * ```ts
7835
- * const result = await db.user
7836
- * .join('messages')
7837
- * .where({ 'messages.text': { startsWith: 'Hi' } })
7838
- * .select('name', { messages: 'messages.*' });
7839
- *
7840
- * // result has the following type:
7841
- * const ok: {
7842
- * name: string;
7843
- * // full message is included:
7844
- * messages: { id: number; text: string; updatedAt: Date; createdAt: Date };
7845
- * }[] = result;
7846
- * ```
7916
+ * @param coalesce
7917
+ */
7918
+ json(coalesce) {
7919
+ return queryJson(
7920
+ this.clone(),
7921
+ coalesce
7922
+ );
7923
+ }
7924
+ }
7925
+
7926
+ const logColors = {
7927
+ boldCyanBright: (message) => `\x1B[1m\x1B[96m${message}\x1B[39m\x1B[22m`,
7928
+ boldBlue: (message) => `\x1B[1m\x1B[34m${message}\x1B[39m\x1B[22m`,
7929
+ boldYellow: (message) => `\x1B[1m\x1B[33m${message}\x1B[39m\x1B[22m`,
7930
+ boldMagenta: (message) => `\x1B[1m\x1B[33m${message}\x1B[39m\x1B[22m`,
7931
+ boldRed: (message) => `\x1B[1m\x1B[31m${message}\x1B[39m\x1B[22m`
7932
+ };
7933
+ const makeMessage = (colors, timeColor, time, sqlColor, sql, valuesColor, values) => {
7934
+ const elapsed = process.hrtime(time);
7935
+ const formattedTime = `(${elapsed[0] ? `${elapsed[0]}s ` : ""}${(elapsed[1] / 1e6).toFixed(1)}ms)`;
7936
+ const result = `${colors ? timeColor(formattedTime) : formattedTime} ${colors ? sqlColor(sql) : sql}`;
7937
+ if (!values.length) {
7938
+ return result;
7939
+ }
7940
+ const formattedValues = `[${values.map(quote).join(", ")}]`;
7941
+ return `${result} ${colors ? valuesColor(formattedValues) : formattedValues}`;
7942
+ };
7943
+ const logParamToLogObject = (logger, log) => {
7944
+ if (!log)
7945
+ return;
7946
+ const logObject = Object.assign(
7947
+ {
7948
+ colors: true,
7949
+ beforeQuery() {
7950
+ return process.hrtime();
7951
+ },
7952
+ afterQuery(sql, time) {
7953
+ logger.log(
7954
+ makeMessage(
7955
+ colors,
7956
+ logColors.boldCyanBright,
7957
+ time,
7958
+ logColors.boldBlue,
7959
+ sql.text,
7960
+ logColors.boldYellow,
7961
+ sql.values
7962
+ )
7963
+ );
7964
+ },
7965
+ onError(error, sql, time) {
7966
+ const message = `Error: ${error.message}`;
7967
+ logger.error(
7968
+ `${makeMessage(
7969
+ colors,
7970
+ logColors.boldMagenta,
7971
+ time,
7972
+ logColors.boldRed,
7973
+ sql.text,
7974
+ logColors.boldYellow,
7975
+ sql.values
7976
+ )} ${colors ? logColors.boldRed(message) : message}`
7977
+ );
7978
+ }
7979
+ },
7980
+ log === true ? {} : log
7981
+ );
7982
+ const colors = logObject.colors;
7983
+ return logObject;
7984
+ };
7985
+ class QueryLog {
7986
+ log(log = true) {
7987
+ const q = this.clone();
7988
+ q.q.log = logParamToLogObject(q.q.logger, log);
7989
+ return q;
7990
+ }
7991
+ }
7992
+
7993
+ var __defProp$5 = Object.defineProperty;
7994
+ var __getOwnPropSymbols$5 = Object.getOwnPropertySymbols;
7995
+ var __hasOwnProp$5 = Object.prototype.hasOwnProperty;
7996
+ var __propIsEnum$5 = Object.prototype.propertyIsEnumerable;
7997
+ var __defNormalProp$5 = (obj, key, value) => key in obj ? __defProp$5(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
7998
+ var __spreadValues$5 = (a, b) => {
7999
+ for (var prop in b || (b = {}))
8000
+ if (__hasOwnProp$5.call(b, prop))
8001
+ __defNormalProp$5(a, prop, b[prop]);
8002
+ if (__getOwnPropSymbols$5)
8003
+ for (var prop of __getOwnPropSymbols$5(b)) {
8004
+ if (__propIsEnum$5.call(b, prop))
8005
+ __defNormalProp$5(a, prop, b[prop]);
8006
+ }
8007
+ return a;
8008
+ };
8009
+ const mergableObjects = {
8010
+ shape: true,
8011
+ withShapes: true,
8012
+ parsers: true,
8013
+ defaults: true,
8014
+ joinedShapes: true,
8015
+ joinedParsers: true
8016
+ };
8017
+ class MergeQueryMethods {
8018
+ merge(q) {
8019
+ const query = this.clone();
8020
+ const a = query.q;
8021
+ const b = q.q;
8022
+ for (const key in b) {
8023
+ const value = b[key];
8024
+ switch (typeof value) {
8025
+ case "boolean":
8026
+ case "string":
8027
+ case "number":
8028
+ a[key] = value;
8029
+ break;
8030
+ case "object":
8031
+ if (Array.isArray(value)) {
8032
+ a[key] = a[key] ? [...a[key], ...value] : value;
8033
+ } else if (mergableObjects[key]) {
8034
+ a[key] = a[key] ? __spreadValues$5(__spreadValues$5({}, a[key]), value) : value;
8035
+ } else {
8036
+ a[key] = value;
8037
+ }
8038
+ break;
8039
+ }
8040
+ }
8041
+ a[orchidCore.getValueKey] = b[orchidCore.getValueKey];
8042
+ if (b.returnType)
8043
+ a.returnType = b.returnType;
8044
+ return query;
8045
+ }
8046
+ }
8047
+
8048
+ var __defProp$4 = Object.defineProperty;
8049
+ var __defProps$2 = Object.defineProperties;
8050
+ var __getOwnPropDescs$2 = Object.getOwnPropertyDescriptors;
8051
+ var __getOwnPropSymbols$4 = Object.getOwnPropertySymbols;
8052
+ var __hasOwnProp$4 = Object.prototype.hasOwnProperty;
8053
+ var __propIsEnum$4 = Object.prototype.propertyIsEnumerable;
8054
+ var __defNormalProp$4 = (obj, key, value) => key in obj ? __defProp$4(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
8055
+ var __spreadValues$4 = (a, b) => {
8056
+ for (var prop in b || (b = {}))
8057
+ if (__hasOwnProp$4.call(b, prop))
8058
+ __defNormalProp$4(a, prop, b[prop]);
8059
+ if (__getOwnPropSymbols$4)
8060
+ for (var prop of __getOwnPropSymbols$4(b)) {
8061
+ if (__propIsEnum$4.call(b, prop))
8062
+ __defNormalProp$4(a, prop, b[prop]);
8063
+ }
8064
+ return a;
8065
+ };
8066
+ var __spreadProps$2 = (a, b) => __defProps$2(a, __getOwnPropDescs$2(b));
8067
+ class With {
8068
+ /**
8069
+ * Add Common Table Expression (CTE) to the query.
7847
8070
  *
7848
- * Because it's a one-to-many relation, one user has many messages, the user data will be duplicated for different messages data:
8071
+ * ```ts
8072
+ * import { columnTypes } from 'orchid-orm';
8073
+ * import { NumberColumn } from './number';
7849
8074
  *
7850
- * | name | msg |
7851
- * | ------ | ------------------------------ |
7852
- * | user 1 | `{ id: 1, text: 'message 1' }` |
7853
- * | user 1 | `{ id: 2, text: 'message 2' }` |
7854
- * | user 1 | `{ id: 3, text: 'message 3' }` |
8075
+ * // .with optionally accepts such options:
8076
+ * type WithOptions = {
8077
+ * // list of columns returned by this WITH statement
8078
+ * // by default all columns from provided column shape will be included
8079
+ * // true is for default behavior
8080
+ * columns?: string[] | boolean;
7855
8081
  *
7856
- * ### join table
8082
+ * // Adds RECURSIVE keyword:
8083
+ * recursive?: true;
7857
8084
  *
7858
- * If relation wasn't defined, provide a `db.table` instance and specify columns for the join.
7859
- * Joined table can be references from `where` and `select` by a table name.
8085
+ * // Adds MATERIALIZED keyword:
8086
+ * materialized?: true;
8087
+ *
8088
+ * // Adds NOT MATERIALIZED keyword:
8089
+ * notMaterialized?: true;
8090
+ * };
8091
+ *
8092
+ * // accepts columns shape and a raw expression:
8093
+ * db.table.with(
8094
+ * 'alias',
8095
+ * {
8096
+ * id: columnTypes.integer(),
8097
+ * name: columnTypes.text(3, 100),
8098
+ * },
8099
+ * db.table.sql`SELECT id, name FROM "someTable"`,
8100
+ * );
8101
+ *
8102
+ * // accepts query:
8103
+ * db.table.with('alias', db.table.all());
8104
+ *
8105
+ * // accepts a callback for a query builder:
8106
+ * db.table.with('alias', (qb) =>
8107
+ * qb.select({ one: db.table.sql((t) => t.integer())`1` }),
8108
+ * );
8109
+ *
8110
+ * // All mentioned forms can accept options as a second argument:
8111
+ * db.table.with(
8112
+ * 'alias',
8113
+ * {
8114
+ * recursive: true,
8115
+ * materialized: true,
8116
+ * },
8117
+ * rawOrQueryOrCallback,
8118
+ * );
8119
+ * ```
8120
+ *
8121
+ * Defined `WITH` table can be used in `.from` or `.join` with all the type safeness:
7860
8122
  *
7861
8123
  * ```ts
7862
- * // Join message where userId = id:
7863
- * db.user
7864
- * .join(db.message, 'userId', 'id')
7865
- * .where({ 'message.text': { startsWith: 'Hi' } })
7866
- * .select('name', 'message.text');
8124
+ * db.table.with('alias', db.table.all()).from('alias').select('alias.id');
8125
+ *
8126
+ * db.table
8127
+ * .with('alias', db.table.all())
8128
+ * .join('alias', 'alias.id', 'user.id')
8129
+ * .select('alias.id');
7867
8130
  * ```
7868
8131
  *
7869
- * Columns in the join list may be prefixed with table names for clarity:
8132
+ * @param args - first argument is an alias for this CTE, other arguments can be column shape, query object, or raw SQL.
8133
+ */
8134
+ with(...args) {
8135
+ const q = this.clone();
8136
+ let options = args.length === 3 && !orchidCore.isExpression(args[2]) || args.length === 4 ? args[1] : void 0;
8137
+ const last = args[args.length - 1];
8138
+ const query = typeof last === "function" ? last(q.queryBuilder) : last;
8139
+ const shape = args.length === 4 ? args[2] : orchidCore.isExpression(query) ? args[1] : query.q.shape;
8140
+ if ((options == null ? void 0 : options.columns) === true) {
8141
+ options = __spreadProps$2(__spreadValues$4({}, options), {
8142
+ columns: Object.keys(shape)
8143
+ });
8144
+ }
8145
+ pushQueryValue(q, "with", [args[0], options || orchidCore.emptyObject, query]);
8146
+ return setQueryObjectValue(q, "withShapes", args[0], shape);
8147
+ }
8148
+ }
8149
+
8150
+ class Union {
8151
+ /**
8152
+ * Creates a union query, taking an array or a list of callbacks, builders, or raw statements to build the union statement, with optional boolean `wrap`.
8153
+ * If the `wrap` parameter is true, the queries will be individually wrapped in parentheses.
7870
8154
  *
7871
8155
  * ```ts
7872
- * db.user.join(db.message, 'message.userId', 'user.id');
8156
+ * SomeTable.select('id', 'name').union(
8157
+ * [
8158
+ * OtherTable.select('id', 'name'),
8159
+ * SomeTable.sql`SELECT id, name FROM "thirdTable"`,
8160
+ * ],
8161
+ * true, // optional wrap parameter
8162
+ * );
7873
8163
  * ```
7874
8164
  *
7875
- * Joined table can have an alias for referencing it further:
8165
+ * @param args - array of queries or raw SQLs
8166
+ * @param wrap - provide `true` if you want the queries to be wrapped into parentheses
8167
+ */
8168
+ union(args, wrap) {
8169
+ return pushQueryArray(
8170
+ this.clone(),
8171
+ "union",
8172
+ args.map((arg) => ({ arg, kind: "UNION", wrap }))
8173
+ );
8174
+ }
8175
+ /**
8176
+ * Same as `union`, but allows duplicated rows.
8177
+ *
8178
+ * @param args - array of queries or raw SQLs
8179
+ * @param wrap - provide `true` if you want the queries to be wrapped into parentheses
8180
+ */
8181
+ unionAll(args, wrap) {
8182
+ return pushQueryArray(
8183
+ this.clone(),
8184
+ "union",
8185
+ args.map((arg) => ({ arg, kind: "UNION ALL", wrap }))
8186
+ );
8187
+ }
8188
+ /**
8189
+ * Same as `union`, but uses a `INTERSECT` SQL keyword instead
8190
+ *
8191
+ * @param args - array of queries or raw SQLs
8192
+ * @param wrap - provide `true` if you want the queries to be wrapped into parentheses
8193
+ */
8194
+ intersect(args, wrap) {
8195
+ return pushQueryArray(
8196
+ this.clone(),
8197
+ "union",
8198
+ args.map((arg) => ({ arg, kind: "INTERSECT", wrap }))
8199
+ );
8200
+ }
8201
+ /**
8202
+ * Same as `intersect`, but allows duplicated rows.
8203
+ *
8204
+ * @param args - array of queries or raw SQLs
8205
+ * @param wrap - provide `true` if you want the queries to be wrapped into parentheses
8206
+ */
8207
+ intersectAll(args, wrap) {
8208
+ return pushQueryArray(
8209
+ this.clone(),
8210
+ "union",
8211
+ args.map((arg) => ({ arg, kind: "INTERSECT ALL", wrap }))
8212
+ );
8213
+ }
8214
+ /**
8215
+ * Same as `union`, but uses an `EXCEPT` SQL keyword instead
8216
+ *
8217
+ * @param args - array of queries or raw SQLs
8218
+ * @param wrap - provide `true` if you want the queries to be wrapped into parentheses
8219
+ */
8220
+ except(args, wrap) {
8221
+ return pushQueryArray(
8222
+ this.clone(),
8223
+ "union",
8224
+ args.map((arg) => ({ arg, kind: "EXCEPT", wrap }))
8225
+ );
8226
+ }
8227
+ /**
8228
+ * Same as `except`, but allows duplicated rows.
8229
+ *
8230
+ * @param args - array of queries or raw SQLs
8231
+ * @param wrap - provide `true` if you want the queries to be wrapped into parentheses
8232
+ */
8233
+ exceptAll(args, wrap) {
8234
+ return pushQueryArray(
8235
+ this.clone(),
8236
+ "union",
8237
+ args.map((arg) => ({ arg, kind: "EXCEPT ALL", wrap }))
8238
+ );
8239
+ }
8240
+ }
8241
+
8242
+ const _queryWhere = (q, args) => {
8243
+ return pushQueryArray(
8244
+ q,
8245
+ "and",
8246
+ args
8247
+ );
8248
+ };
8249
+ const _queryWhereSql = (q, args) => {
8250
+ return pushQueryValue(
8251
+ q,
8252
+ "and",
8253
+ sqlQueryArgsToExpression(args)
8254
+ );
8255
+ };
8256
+ const _queryWhereNot = (q, args) => {
8257
+ return pushQueryValue(q, "and", {
8258
+ NOT: args
8259
+ });
8260
+ };
8261
+ const _queryWhereNotSql = (q, args) => {
8262
+ return pushQueryValue(q, "and", {
8263
+ NOT: sqlQueryArgsToExpression(args)
8264
+ });
8265
+ };
8266
+ const _queryOr = (q, args) => {
8267
+ return pushQueryArray(
8268
+ q,
8269
+ "or",
8270
+ args.map((item) => [item])
8271
+ );
8272
+ };
8273
+ const _queryOrNot = (q, args) => {
8274
+ return pushQueryArray(
8275
+ q,
8276
+ "or",
8277
+ args.map((item) => [{ NOT: item }])
8278
+ );
8279
+ };
8280
+ const _queryWhereIn = (q, and, arg, values, not) => {
8281
+ let item;
8282
+ if (values) {
8283
+ if (Array.isArray(arg)) {
8284
+ item = {
8285
+ IN: {
8286
+ columns: arg,
8287
+ values
8288
+ }
8289
+ };
8290
+ } else {
8291
+ item = { [arg]: { in: values } };
8292
+ }
8293
+ } else {
8294
+ item = {};
8295
+ for (const key in arg) {
8296
+ item[key] = { in: arg[key] };
8297
+ }
8298
+ }
8299
+ if (not)
8300
+ item = { NOT: item };
8301
+ if (and) {
8302
+ pushQueryValue(q, "and", item);
8303
+ } else {
8304
+ pushQueryValue(q, "or", [item]);
8305
+ }
8306
+ return q;
8307
+ };
8308
+ const existsArgs = (self, q, args) => {
8309
+ let joinSubQuery;
8310
+ if (typeof q === "object") {
8311
+ joinSubQuery = getIsJoinSubQuery(q);
8312
+ if (joinSubQuery) {
8313
+ q = q.clone();
8314
+ q.shape = getShapeFromSelect(
8315
+ q,
8316
+ true
8317
+ );
8318
+ }
8319
+ } else {
8320
+ joinSubQuery = false;
8321
+ }
8322
+ const joinArgs = processJoinArgs(self, q, args, joinSubQuery);
8323
+ return [
8324
+ {
8325
+ EXISTS: joinArgs
8326
+ }
8327
+ ];
8328
+ };
8329
+ const _queryWhereExists = (q, arg, args) => {
8330
+ return _queryWhere(
8331
+ q,
8332
+ existsArgs(q, arg, args)
8333
+ );
8334
+ };
8335
+ class Where {
8336
+ /**
8337
+ * Constructing `WHERE` conditions:
7876
8338
  *
7877
8339
  * ```ts
7878
- * db.user
7879
- * .join(db.message.as('m'), 'message.userId', 'user.id')
7880
- * .where({ 'm.text': { startsWith: 'Hi' } })
7881
- * .select('name', 'm.text');
7882
- * ```
8340
+ * db.table.where({
8341
+ * // column of the current table
8342
+ * name: 'John',
7883
8343
  *
7884
- * Joined table can be selected as an object as well as the relation join above:
8344
+ * // table name may be specified, it can be the name of a joined table
8345
+ * 'table.lastName': 'Johnsonuk',
7885
8346
  *
7886
- * ```ts
7887
- * const result = await db.user
7888
- * .join(db.message.as('m'), 'message.userId', 'user.id')
7889
- * .where({ 'm.text': { startsWith: 'Hi' } })
7890
- * .select('name', { msg: 'm.*' });
8347
+ * // object with operators, see the "column operators" section to see a full list of them:
8348
+ * age: {
8349
+ * gt: 30,
8350
+ * lt: 70,
8351
+ * },
7891
8352
  *
7892
- * // result has the following type:
7893
- * const ok: {
7894
- * name: string;
7895
- * // full message is included as msg:
7896
- * msg: { id: number; text: string; updatedAt: Date; createdAt: Date };
7897
- * }[] = result;
8353
+ * // where column equals to raw SQL
8354
+ * column: db.table.sql`sql expression`,
8355
+ * });
7898
8356
  * ```
7899
8357
  *
7900
- * You can provide a custom comparison operator
8358
+ * Multiple `where`s are joined with `AND`:
7901
8359
  *
7902
8360
  * ```ts
7903
- * db.user.join(db.message, 'userId', '!=', 'id');
8361
+ * db.table.where({ foo: 'foo' }).where({ bar: 'bar' });
7904
8362
  * ```
7905
8363
  *
7906
- * Join can accept raw SQL for the `ON` part of join:
7907
- *
7908
- * ```ts
7909
- * db.user.join(
7910
- * db.message,
7911
- * db.user.sql`lower("message"."text") = lower("user"."name")`,
7912
- * );
8364
+ * ```sql
8365
+ * SELECT * FROM table WHERE foo = 'foo' AND bar = 'bar'
7913
8366
  * ```
7914
8367
  *
7915
- * Join can accept raw SQL instead of columns:
8368
+ * `undefined` values are ignored, so you can supply a partial object with conditions:
7916
8369
  *
7917
8370
  * ```ts
7918
- * db.user.join(
7919
- * db.message,
7920
- * db.user.sql`lower("message"."text")`,
7921
- * db.user.sql`lower("user"."name")`,
7922
- * );
8371
+ * type Params = {
8372
+ * // allow providing exact age, or lower or greater than
8373
+ * age?: number | { lt?: number; gt?: number };
8374
+ * };
7923
8375
  *
7924
- * // with operator:
7925
- * db.user.join(
7926
- * db.message,
7927
- * db.user.sql`lower("message"."text")`,
7928
- * '!=',
7929
- * db.user.sql`lower("user"."name")`,
7930
- * );
8376
+ * const loadRecords = async (params: Params) => {
8377
+ * // this will load all records if params is an empty object
8378
+ * const records = await db.table.where(params);
8379
+ * };
7931
8380
  * ```
7932
8381
  *
7933
- * To join based on multiple columns, you can provide an object where keys are joining table columns, and values are main table columns or a raw SQL:
8382
+ * It supports a sub-query that is selecting a single value to compare it with a column:
7934
8383
  *
7935
8384
  * ```ts
7936
- * db.user.join(db.message, {
7937
- * userId: 'id',
7938
- *
7939
- * // with table names:
7940
- * 'message.userId': 'user.id',
7941
- *
7942
- * // value can be a raw SQL expression:
7943
- * text: db.user.sql`lower("user"."name")`,
8385
+ * db.table.where({
8386
+ * // compare `someColumn` in one table with the `column` value returned from another query.
8387
+ * someColumn: db.otherTable.where(...conditions).get('column'),
7944
8388
  * });
7945
8389
  * ```
7946
8390
  *
7947
- * Join all records without conditions by providing `true`:
7948
- *
7949
- * ```ts
7950
- * db.user.join(db.message, true);
7951
- * ```
7952
- *
7953
- * Join methods can accept a callback with a special query builder that has `on` and `orOn` methods for handling advanced cases:
7954
- *
7955
- * ```ts
7956
- * db.user.join(
7957
- * db.message,
7958
- * (q) =>
7959
- * q
7960
- * // left column is the db.message column, right column is the db.user column
7961
- * .on('userId', 'id')
7962
- * // table names can be provided:
7963
- * .on('message.userId', 'user.id')
7964
- * // operator can be specified:
7965
- * .on('userId', '!=', 'id')
7966
- * // operator can be specified with table names as well:
7967
- * .on('message.userId', '!=', 'user.id')
7968
- * // `.orOn` takes the same arguments as `.on` and acts like `.or`:
7969
- * .on('userId', 'id') // where message.userId = user.id
7970
- * .orOn('text', 'name'), // or message.text = user.name
7971
- * );
7972
- * ```
7973
- *
7974
- * Join query builder supports all `where` methods: `.where`, `.whereIn`, `.whereExists`, and all `.or`, `.not`, and `.orNot` forms.
7975
- *
7976
- * Column names in the where conditions are applied for the joined table, but you can specify a table name to add a condition for the main table.
7977
- *
7978
- * ```ts
7979
- * db.user.join(db.message, (q) =>
7980
- * q
7981
- * .on('userId', 'id')
7982
- * .where({
7983
- * // not prefixed column name is for joined table:
7984
- * text: { startsWith: 'hello' },
7985
- * // specify a table name to set condition on the main table:
7986
- * 'user.name': 'Bob',
7987
- * })
7988
- * // id is a column of a joined table Message
7989
- * .whereIn('id', [1, 2, 3])
7990
- * // condition for id of a user
7991
- * .whereIn('user.id', [4, 5, 6]),
7992
- * );
7993
- * ```
7994
- *
7995
- * The query above will generate the following SQL (simplified):
7996
- *
7997
- * ```sql
7998
- * SELECT * FROM "user"
7999
- * JOIN "message"
8000
- * ON "message"."userId" = "user"."id"
8001
- * AND "message"."text" ILIKE 'hello%'
8002
- * AND "user"."name" = 'Bob'
8003
- * AND "message"."id" IN (1, 2, 3)
8004
- * AND "user"."id" IN (4, 5, 6)
8005
- * ```
8006
- *
8007
- * The join argument can be a query with `select`, `where`, and other methods. In such case, it will be handled as a sub query:
8391
+ * `where` can accept other queries and merge their conditions:
8008
8392
  *
8009
8393
  * ```ts
8010
- * db.user.join(
8011
- * db.message
8012
- * .select('id', 'userId', 'text')
8013
- * .where({ text: { startsWith: 'Hi' } })
8014
- * .as('t'),
8015
- * 'userId',
8016
- * 'id',
8017
- * );
8018
- * ```
8019
- *
8020
- * It will produce such SQL:
8394
+ * const otherQuery = db.table.where({ name: 'John' });
8021
8395
  *
8022
- * ```sql
8023
- * SELECT * FROM "user"
8024
- * JOIN (
8025
- * SELECT "t"."id", "t"."userId", "t"."text"
8026
- * FROM "message" AS "t"
8027
- * ) "t" ON "t"."userId" = "user"."id"
8396
+ * db.table.where({ id: 1 }, otherQuery);
8397
+ * // this will produce WHERE "table"."id" = 1 AND "table"."name' = 'John'
8028
8398
  * ```
8029
8399
  *
8030
- * @param arg - {@link JoinFirstArg}
8031
- * @param args - {@link JoinArgs}
8032
- */
8033
- join(arg, ...args) {
8034
- return _join(this.clone(), true, "JOIN", arg, args);
8035
- }
8036
- /**
8037
- * `leftJoin` is a method for SQL `LEFT JOIN`, which is equivalent to `OUTER JOIN`, `LEFT OUTER JOIN`.
8038
- *
8039
- * When no matching record is found, it will fill joined table columns with `NULL` values in the result rows.
8040
- *
8041
- * Works just like `join`, except for result type that may have `null`:
8400
+ * `where` supports raw SQL:
8042
8401
  *
8043
8402
  * ```ts
8044
- * const result = await db.user
8045
- * .leftJoin('messages')
8046
- * .select('name', 'messages.text');
8047
- *
8048
- * // the same query, but joining table explicitly
8049
- * const result2: typeof result = await db.user
8050
- * .leftJoin(db.message, 'userId', 'id')
8051
- * .select('name', 'message.text');
8052
- *
8053
- * // result has the following type:
8054
- * const ok: { name: string; text: string | null }[] = result;
8055
- * ```
8056
- *
8057
- * @param arg - {@link JoinFirstArg}
8058
- * @param args - {@link JoinArgs}
8059
- */
8060
- leftJoin(arg, ...args) {
8061
- return _join(this.clone(), false, "LEFT JOIN", arg, args);
8062
- }
8063
- /**
8064
- * `rightJoin` is a method for SQL `RIGHT JOIN`, which is equivalent to `RIGHT OUTER JOIN`.
8403
+ * db.table.where(db.table.sql`a = b`);
8065
8404
  *
8066
- * Takes the same arguments as `json`.
8405
+ * // or
8406
+ * import { raw } from 'orchid-orm';
8067
8407
  *
8068
- * It will load all records from the joining table, and fill the main table columns with `null` when no match is found.
8408
+ * db.table.where(raw`a = b`);
8409
+ * ```
8069
8410
  *
8070
- * The columns of the table you're joining to are becoming nullable when using `rightJoin`.
8411
+ * `where` can accept a callback with a specific query builder containing all "where" methods such as `where`, `orWhere`, `whereNot`, `whereIn`, `whereExists`:
8071
8412
  *
8072
8413
  * ```ts
8073
- * const result = await db.user
8074
- * .rightJoin('messages')
8075
- * .select('name', 'messages.text');
8076
- *
8077
- * // even though name is not a nullable column, it becomes nullable after using rightJoin
8078
- * const ok: { name: string | null; text: string }[] = result;
8414
+ * db.table.where((q) =>
8415
+ * q
8416
+ * .where({ name: 'Name' })
8417
+ * .orWhere({ id: 1 }, { id: 2 })
8418
+ * .whereIn('letter', ['a', 'b', 'c'])
8419
+ * .whereExists(Message, 'authorId', 'id'),
8420
+ * );
8079
8421
  * ```
8080
8422
  *
8081
- * @param arg - {@link JoinFirstArg}
8082
- * @param args - {@link JoinArgs}
8083
- */
8084
- rightJoin(arg, ...args) {
8085
- return _join(this.clone(), true, "RIGHT JOIN", arg, args);
8086
- }
8087
- /**
8088
- * `fullJoin` is a method for SQL `FULL JOIN`, which is equivalent to `FULL OUTER JOIN`.
8423
+ * `where` can accept multiple arguments, conditions are joined with `AND`:
8089
8424
  *
8090
- * Takes the same arguments as `json`.
8425
+ * ```ts
8426
+ * db.table.where(
8427
+ * { id: 1 },
8428
+ * db.table.where({ name: 'John' }),
8429
+ * db.table.sql`a = b`,
8430
+ * );
8431
+ * ```
8091
8432
  *
8092
- * It will load all records from the joining table, both sides of the join may result in `null` values when there is no match.
8433
+ * ## where sub query
8093
8434
  *
8094
- * All columns become nullable after using `fullJoin`.
8435
+ * `where` handles a special callback where you can query a relation to get some value and filter by that value.
8436
+ *
8437
+ * It is useful for a faceted search. For instance, posts have tags, and we want to find all posts that have all the given tags.
8095
8438
  *
8096
8439
  * ```ts
8097
- * const result = await db.user
8098
- * .rightJoin('messages')
8099
- * .select('name', 'messages.text');
8440
+ * const givenTags = ['typescript', 'node.js'];
8100
8441
  *
8101
- * // all columns can be null
8102
- * const ok: { name: string | null; text: string | null }[] = result;
8442
+ * const posts = await db.post.where(
8443
+ * (post) =>
8444
+ * post.tags // query tags of the post
8445
+ * .whereIn('tagName', givenTags) // where name of the tag is inside array
8446
+ * .count() // count how many such tags were found
8447
+ * .equals(wantedTags.length), // the count must be exactly the length of array
8448
+ * // if the post has ony `typescript` tag but not the `node.js` it will be omitted
8449
+ * );
8103
8450
  * ```
8104
8451
  *
8105
- * @param arg - {@link JoinFirstArg}
8106
- * @param args - {@link JoinArgs}
8107
- */
8108
- fullJoin(arg, ...args) {
8109
- return _join(this.clone(), false, "FULL JOIN", arg, args);
8110
- }
8111
- /**
8112
- * `joinLateral` allows joining a table with a sub-query that can reference the main table of current query and the other joined tables.
8452
+ * This will produce an efficient SQL query:
8453
+ *
8454
+ * ```sql
8455
+ * SELECT * FROM "post"
8456
+ * WHERE (
8457
+ * SELECT count(*) = 3
8458
+ * FROM "tag" AS "tags"
8459
+ * WHERE "tag"."tagName" IN ('typescript', 'node.js')
8460
+ * -- join tags to the post via "postTag" table
8461
+ * AND EXISTS (
8462
+ * SELECT 1 FROM "postTag"
8463
+ * WHERE "postTag"."postId" = "post"."id"
8464
+ * AND "postTag"."tagId" = "tag"."id"
8465
+ * )
8466
+ * )
8467
+ * ```
8113
8468
  *
8114
- * Regular `JOIN` also can have a sub-query in its definition, but it cannot reference other tables of this query.
8469
+ * In the example above we use `count()`, you can also use any other aggregate method instead, such as `min`, `max`, `avg`.
8115
8470
  *
8116
- * `JOIN LATERAL` of Postgres can have conditions in the `ON` statement, but `Orchid ORM` decided that there are no useful use-cases for such conditions, and it is only building a sub-query.
8471
+ * The `count()` is chained with `equals` to check for a strict equality, any other operation is also allowed, such as `not`, `lt`, `gt`.
8117
8472
  *
8118
- * First argument is the other table you want to join, or a name of relation, or a name of `with` defined table.
8473
+ * ## where special keys
8119
8474
  *
8120
- * Second argument is a callback where you can reference other tables using `on` and `orOn`, select columns, do `where` conditions, and use any other query methods to build a sub-query.
8475
+ * The object passed to `where` can contain special keys, each of the keys corresponds to its own method and takes the same value as the type of argument of the method.
8476
+ *
8477
+ * For example:
8121
8478
  *
8122
8479
  * ```ts
8123
- * // joinLateral a Message table, alias it as `m`
8124
- * // without aliasing you can refer to the message by a table name
8125
- * User.joinLateral(Message.as('m'), (q) =>
8126
- * q
8127
- * // select message columns
8128
- * .select('text')
8129
- * // join the message to the user, column names can be prefixed with table names
8130
- * .on('authorId', 'id')
8131
- * // message columns are available without prefixing,
8132
- * // outer table columns are available with a table name
8133
- * .where({ text: 'some text', 'user.name': 'name' })
8134
- * .order({ createdAt: 'DESC' }),
8135
- * )
8136
- * // only selected message columns are available in select and where
8137
- * .select('id', 'name', 'm.text')
8138
- * .where({ 'm.text': messageData.text });
8480
+ * db.table.where({
8481
+ * NOT: { key: 'value' },
8482
+ * OR: [{ name: 'a' }, { name: 'b' }],
8483
+ * IN: {
8484
+ * columns: ['id', 'name'],
8485
+ * values: [
8486
+ * [1, 'a'],
8487
+ * [2, 'b'],
8488
+ * ],
8489
+ * },
8490
+ * });
8139
8491
  * ```
8140
8492
  *
8141
- * As well as simple `join`, `joinLateral` can select an object of full joined record:
8493
+ * Using methods `whereNot`, `orWhere`, `whereIn` instead of this is a shorter and cleaner way, but in some cases, such object keys way may be more convenient.
8142
8494
  *
8143
8495
  * ```ts
8144
- * // join by relation name
8145
- * const result = await User.joinLateral(
8146
- * 'messages',
8147
- * (q) => q.as('message'), // alias to 'message'
8148
- * ).select('name', { message: 'message.*' });
8496
+ * db.table.where({
8497
+ * // see .whereNot
8498
+ * NOT: { id: 1 },
8499
+ * // can be an array:
8500
+ * NOT: [{ id: 1 }, { id: 2 }],
8149
8501
  *
8150
- * // result has the following type:
8151
- * const ok: {
8152
- * name: string;
8153
- * // full message is included:
8154
- * message: { id: number; text: string; updatedAt: Date; createdAt: Date };
8155
- * }[] = result;
8502
+ * // see .orWhere
8503
+ * OR: [{ name: 'a' }, { name: 'b' }],
8504
+ * // can be an array:
8505
+ * // this will give id = 1 AND id = 2 OR id = 3 AND id = 4
8506
+ * OR: [
8507
+ * [{ id: 1 }, { id: 2 }],
8508
+ * [{ id: 3 }, { id: 4 }],
8509
+ * ],
8510
+ *
8511
+ * // see .in, the key syntax requires an object with columns and values
8512
+ * IN: {
8513
+ * columns: ['id', 'name'],
8514
+ * values: [
8515
+ * [1, 'a'],
8516
+ * [2, 'b'],
8517
+ * ],
8518
+ * },
8519
+ * // can be an array:
8520
+ * IN: [
8521
+ * {
8522
+ * columns: ['id', 'name'],
8523
+ * values: [
8524
+ * [1, 'a'],
8525
+ * [2, 'b'],
8526
+ * ],
8527
+ * },
8528
+ * { columns: ['someColumn'], values: [['foo', 'bar']] },
8529
+ * ],
8530
+ * });
8156
8531
  * ```
8157
8532
  *
8158
- * `message` can be aliased withing the `select` as well as in case of a simple `join`:
8533
+ * ## column operators
8534
+ *
8535
+ * `where` argument can take an object where the key is the name of the operator and the value is its argument.
8536
+ *
8537
+ * Different types of columns support different sets of operators.
8538
+ *
8539
+ * All column operators can take a value of the same type as the column, a sub-query, or a raw SQL expression:
8159
8540
  *
8160
8541
  * ```ts
8161
- * // join by relation name
8162
- * const result = await User.joinLateral(
8163
- * 'messages',
8164
- * (q) => q.as('message'), // alias to 'message'
8165
- * ).select('name', { msg: 'message.*' });
8542
+ * import { sql } from 'orchid-orm';
8166
8543
  *
8167
- * // result has the following type:
8168
- * const ok: {
8169
- * name: string;
8170
- * // full message is included as msg:
8171
- * msg: { id: number; text: string; updatedAt: Date; createdAt: Date };
8172
- * }[] = result;
8544
+ * db.table.where({
8545
+ * numericColumn: {
8546
+ * // lower than 5
8547
+ * lt: 5,
8548
+ *
8549
+ * // lower than the value returned by sub-query
8550
+ * lt: OtherTable.select('someNumber').take(),
8551
+ *
8552
+ * // raw SQL expression produces WHERE "numericColumn" < "otherColumn" + 10
8553
+ * lt: sql`"otherColumn" + 10`,
8554
+ * },
8555
+ * });
8173
8556
  * ```
8174
8557
  *
8175
- * @param arg - {@link JoinFirstArg}
8176
- * @param cb - {@link JoinLateralCallback}
8177
- */
8178
- joinLateral(arg, cb) {
8179
- return _joinLateral(this.clone(), "JOIN", arg, cb);
8180
- }
8181
- /**
8182
- * The same as {@link joinLateral}, but when no records found for the join it will result in `null`:
8558
+ * ### Any type of column operators
8559
+ *
8560
+ * `equals` is a simple `=` operator, it may be useful for comparing column value with JSON object:
8183
8561
  *
8184
8562
  * ```ts
8185
- * const result = await db.user
8186
- * .leftJoinLateral('messages', (q) => q.as('message'))
8187
- * .select('name', 'message.text');
8563
+ * db.table.where({
8564
+ * // when searching for an exact same JSON value, this won't work:
8565
+ * jsonColumn: someObject,
8188
8566
  *
8189
- * // result has the following type:
8190
- * const ok: { name: string; text: string | null }[] = result;
8567
+ * // use `{ equals: ... }` instead:
8568
+ * jsonColumn: { equals: someObject },
8569
+ * });
8191
8570
  * ```
8192
8571
  *
8193
- * @param arg - {@link JoinFirstArg}
8194
- * @param cb - {@link JoinLateralCallback}
8195
- */
8196
- leftJoinLateral(arg, cb) {
8197
- return _joinLateral(this.clone(), "LEFT JOIN", arg, cb);
8198
- }
8199
- }
8200
- const makeOnItem = (joinTo, joinFrom, args) => {
8201
- return {
8202
- ON: {
8203
- joinTo,
8204
- joinFrom,
8205
- on: args
8206
- }
8207
- };
8208
- };
8209
- const pushQueryOn = (q, joinFrom, joinTo, ...on) => {
8210
- return pushQueryValue(q, "and", makeOnItem(joinFrom, joinTo, on));
8211
- };
8212
- const pushQueryOrOn = (q, joinFrom, joinTo, ...on) => {
8213
- return pushQueryValue(q, "or", [makeOnItem(joinFrom, joinTo, on)]);
8214
- };
8215
- const addQueryOn = (q, joinFrom, joinTo, ...args) => {
8216
- const cloned = q.clone();
8217
- setQueryObjectValue(
8218
- cloned,
8219
- "joinedShapes",
8220
- joinFrom.q.as || joinFrom.table,
8221
- joinFrom.q.shape
8222
- );
8223
- return pushQueryOn(cloned, joinFrom, joinTo, ...args);
8224
- };
8225
- const _queryJoinOn = (q, args) => {
8226
- return pushQueryOn(q, q.q.joinTo, q, ...args);
8227
- };
8228
- const _queryJoinOrOn = (q, args) => {
8229
- return pushQueryOrOn(q, q.q.joinTo, q, ...args);
8230
- };
8231
- const _queryJoinOnJsonPathEquals = (q, args) => {
8232
- return pushQueryValue(q, "and", { ON: args });
8233
- };
8234
- class OnQueryBuilder extends WhereQueryBase {
8235
- constructor(q, { shape, joinedShapes }, joinTo) {
8236
- super();
8237
- this.withData = orchidCore.emptyObject;
8238
- this.internal = q.internal;
8239
- this.table = typeof q === "object" ? q.table : q;
8240
- this.shape = shape;
8241
- this.q = {
8242
- shape,
8243
- joinedShapes
8244
- };
8245
- this.baseQuery = this;
8246
- if (typeof q === "object" && q.q.as) {
8247
- this.q.as = q.q.as;
8248
- }
8249
- this.q.joinTo = joinTo;
8250
- }
8251
- /**
8252
- * Use `on` to specify columns to join records.
8572
+ * `not` is `!=` (aka `<>`) not equal operator:
8253
8573
  *
8254
8574
  * ```ts
8255
- * q
8256
- * // left column is the db.message column, right column is the db.user column
8257
- * .on('userId', 'id')
8258
- * // table names can be provided:
8259
- * .on('message.userId', 'user.id')
8260
- * // operator can be specified:
8261
- * .on('userId', '!=', 'id')
8262
- * // operator can be specified with table names as well:
8263
- * .on('message.userId', '!=', 'user.id')
8264
- * // `.orOn` takes the same arguments as `.on` and acts like `.or`:
8265
- * .on('userId', 'id') // where message.userId = user.id
8575
+ * db.table.where({
8576
+ * anyColumn: { not: value },
8577
+ * });
8266
8578
  * ```
8267
8579
  *
8268
- * @param args - columns to join with
8269
- */
8270
- on(...args) {
8271
- return _queryJoinOn(this.clone(), args);
8272
- }
8273
- /**
8274
- * Works as {@link on}, but the added conditions will be separated from previous with `OR`.
8580
+ * `in` is for the `IN` operator to check if the column value is included in a list of values.
8275
8581
  *
8276
- * @param args - columns to join with
8277
- */
8278
- orOn(...args) {
8279
- return _queryJoinOrOn(this.clone(), args);
8280
- }
8281
- /**
8282
- * Use `onJsonPathEquals` to join record based on a field of their JSON column:
8582
+ * Takes an array of the same type as a column, a sub-query that returns a list of values, or a raw SQL expression that returns a list.
8283
8583
  *
8284
8584
  * ```ts
8285
- * db.table.join(db.otherTable, (q) =>
8286
- * // '$.key' is a JSON path
8287
- * q.onJsonPathEquals('otherTable.data', '$.key', 'table.data', '$.key'),
8288
- * );
8585
+ * db.table.where({
8586
+ * column: {
8587
+ * in: ['a', 'b', 'c'],
8588
+ *
8589
+ * // WHERE "column" IN (SELECT "column" FROM "otherTable")
8590
+ * in: OtherTable.select('column'),
8591
+ *
8592
+ * in: db.table.sql`('a', 'b')`,
8593
+ * },
8594
+ * });
8289
8595
  * ```
8290
8596
  *
8291
- * @param args - columns and JSON paths to join with.
8292
- */
8293
- onJsonPathEquals(...args) {
8294
- return _queryJoinOnJsonPathEquals(this.clone(), args);
8295
- }
8296
- }
8297
-
8298
- class JsonModifiers {
8299
- /**
8300
- * Return a JSON value/object/array where a given value is set at the given path.
8301
- * The path is an array of keys to access the value.
8597
+ * `notIn` is for the `NOT IN` operator, and takes the same arguments as `in`
8302
8598
  *
8303
- * Can be used in `update` callback.
8599
+ * ### Numeric, Date, and Time column operators
8304
8600
  *
8305
- * ```ts
8306
- * const result = await db.table.jsonSet('data', ['name'], 'new value').take();
8601
+ * To compare numbers, dates, and times.
8307
8602
  *
8308
- * expect(result.data).toEqual({ name: 'new value' });
8309
- * ```
8603
+ * `lt` is for `<` (lower than)
8310
8604
  *
8311
- * Optionally takes parameters of type `{ as?: string, createIfMissing?: boolean }`
8605
+ * `lte` is for `<=` (lower than or equal)
8606
+ *
8607
+ * `gt` is for `>` (greater than)
8608
+ *
8609
+ * `gte` is for `>=` (greater than or equal)
8312
8610
  *
8313
8611
  * ```ts
8314
- * await db.table.jsonSet('data', ['name'], 'new value', {
8315
- * as: 'alias', // select data as `alias`
8316
- * createIfMissing: true, // ignored if missing by default
8612
+ * db.table.where({
8613
+ * numericColumn: {
8614
+ * gt: 5,
8615
+ * lt: 10,
8616
+ * },
8617
+ *
8618
+ * date: {
8619
+ * lte: new Date(),
8620
+ * },
8621
+ *
8622
+ * time: {
8623
+ * gte: new Date(),
8624
+ * },
8317
8625
  * });
8318
8626
  * ```
8319
8627
  *
8320
- * @param column - name of JSON column, or a result of a nested json method
8321
- * @param path - path to value inside the json
8322
- * @param value - value to set into the json
8323
- * @param options - `as` to alias the json value when selecting, `createIfMissing: true` will create a new JSON property if it didn't exist before
8324
- */
8325
- jsonSet(column, path, value, options) {
8326
- var _a;
8327
- const q = this.clone();
8328
- const json = {
8329
- __json: [
8330
- "set",
8331
- (_a = options == null ? void 0 : options.as) != null ? _a : typeof column === "string" ? column : column.__json[1],
8332
- typeof column === "string" ? q.q.shape[column] : column.__json[2],
8333
- column,
8334
- path,
8335
- value,
8336
- options
8337
- ]
8338
- };
8339
- return Object.assign(
8340
- pushQueryValue(q, "select", json),
8341
- json
8342
- );
8343
- }
8344
- /**
8345
- * Return a JSON value/object/array where a given value is inserted at the given JSON path. Value can be a single value or JSON object. If a value exists at the given path, the value is not replaced.
8628
+ * `between` also works with numeric, dates, and time columns, it takes an array of two elements.
8346
8629
  *
8347
- * Can be used in `update` callback.
8630
+ * Both elements can be of the same type as a column, a sub-query, or a raw SQL expression.
8348
8631
  *
8349
8632
  * ```ts
8350
- * // imagine user has data = { tags: ['two'] }
8351
- * const result = await db.table.jsonInsert('data', ['tags', 0], 'one').take();
8633
+ * db.table.where({
8634
+ * column: {
8635
+ * // simple values
8636
+ * between: [1, 10],
8352
8637
  *
8353
- * // 'one' is inserted to 0 position
8354
- * expect(result.data).toEqual({ tags: ['one', 'two'] });
8638
+ * // sub-query and raw SQL expression
8639
+ * between: [OtherTable.select('column').take(), db.table.sql`2 + 2`],
8640
+ * },
8641
+ * });
8355
8642
  * ```
8356
8643
  *
8357
- * Optionally takes parameters of type `{ as?: string, insertAfter?: boolean }`
8644
+ * ### Text column operators
8358
8645
  *
8359
- * ```ts
8360
- * // imagine user has data = { tags: ['one'] }
8361
- * const result = await db.table
8362
- * .jsonInsert('data', ['tags', 0], 'two', {
8363
- * as: 'alias', // select as an alias
8364
- * insertAfter: true, // insert after the specified position
8365
- * })
8366
- * .take();
8646
+ * For `text`, `char`, `varchar`, and `json` columns.
8367
8647
  *
8368
- * // 'one' is inserted to 0 position
8369
- * expect(result.alias).toEqual({ tags: ['one', 'two'] });
8370
- * ```
8371
- * @param column - name of JSON column, or a result of a nested json method
8372
- * @param path - path to the array inside the json, last path element is index to insert into
8373
- * @param value - value to insert into the json array
8374
- * @param options - `as` to alias the json value when selecting, `insertAfter: true` to insert after the specified position
8375
- */
8376
- jsonInsert(column, path, value, options) {
8377
- var _a;
8378
- const q = this.clone();
8379
- const json = {
8380
- __json: [
8381
- "insert",
8382
- (_a = options == null ? void 0 : options.as) != null ? _a : typeof column === "string" ? column : column.__json[1],
8383
- typeof column === "string" ? q.q.shape[column] : column.__json[2],
8384
- column,
8385
- path,
8386
- value,
8387
- options
8388
- ]
8389
- };
8390
- return Object.assign(
8391
- pushQueryValue(q, "select", json),
8392
- json
8393
- );
8394
- }
8395
- /**
8396
- * Return a JSON value/object/array where a given value is removed at the given JSON path.
8648
+ * `json` is stored as text, so it has text operators. Use the `jsonb` type for JSON operators.
8397
8649
  *
8398
- * Can be used in `update` callback.
8650
+ * Takes a string, or sub-query returning string, or raw SQL expression as well as other operators.
8399
8651
  *
8400
8652
  * ```ts
8401
- * // imagine a user has data = { tags: ['one', 'two'] }
8402
- * const result = await db.table
8403
- * .jsonRemove(
8404
- * 'data',
8405
- * ['tags', 0],
8406
- * // optional parameters:
8407
- * {
8408
- * as: 'alias', // select as an alias
8409
- * },
8410
- * )
8411
- * .take();
8412
- *
8413
- * expect(result.alias).toEqual({ tags: ['two'] });
8653
+ * db.table.where({
8654
+ * textColumn: {
8655
+ * // WHERE "textColumn" LIKE '%string%'
8656
+ * contains: 'string',
8657
+ * // WHERE "textColumn" ILIKE '%string%'
8658
+ * containsInsensitive: 'string',
8659
+ * // WHERE "textColumn" LIKE 'string%'
8660
+ * startsWith: 'string',
8661
+ * // WHERE "textColumn" ILIKE 'string%'
8662
+ * startsWithInsensitive: 'string',
8663
+ * // WHERE "textColumn" LIKE '%string'
8664
+ * endsWith: 'string',
8665
+ * // WHERE "textColumn" ILIKE '%string'
8666
+ * endsWithInsensitive: 'string',
8667
+ * },
8668
+ * });
8414
8669
  * ```
8415
8670
  *
8416
- * @param column - name of JSON column, or a result of a nested json method
8417
- * @param path - path to the array inside the json, last path element is index to remove this element
8418
- * @param options - `as` to alias the json value when selecting
8419
- */
8420
- jsonRemove(column, path, options) {
8421
- var _a;
8422
- const q = this.clone();
8423
- const json = {
8424
- __json: [
8425
- "remove",
8426
- (_a = options == null ? void 0 : options.as) != null ? _a : typeof column === "string" ? column : column.__json[1],
8427
- typeof column === "string" ? q.q.shape[column] : column.__json[2],
8428
- column,
8429
- path
8430
- ]
8431
- };
8432
- return Object.assign(
8433
- pushQueryValue(q, "select", json),
8434
- json
8435
- );
8436
- }
8437
- /**
8438
- * Selects a value from JSON data using a JSON path.
8671
+ * ### JSONB column operators
8672
+ *
8673
+ * For the `jsonb` column, note that the `json` type has text operators instead.
8674
+ *
8675
+ * `jsonPath` operator: compare a column value under a given JSON path with the provided value.
8676
+ *
8677
+ * Value can be of any type to compare with JSON value, or it can be a sub-query or a raw SQL expression.
8439
8678
  *
8440
8679
  * ```ts
8441
- * import { columnTypes } from 'orchid-orm';
8680
+ * db.table.where({
8681
+ * jsonbColumn: {
8682
+ * jsonPath: [
8683
+ * '$.name', // first element is JSON path
8684
+ * '=', // second argument is comparison operator
8685
+ * 'value', // third argument is a value to compare with
8686
+ * ],
8687
+ * },
8688
+ * });
8689
+ * ```
8442
8690
  *
8443
- * db.table.jsonPathQuery(
8444
- * columnTypes.text(3, 100), // type of the value
8445
- * 'data', // name of the JSON column
8446
- * '$.name', // JSON path
8447
- * 'name', // select value as name
8691
+ * `jsonSupersetOf`: check if the column value is a superset of provided value.
8448
8692
  *
8449
- * // Optionally supports `vars` and `silent` options
8450
- * // check Postgres docs for jsonb_path_query for details
8451
- * {
8452
- * vars: 'vars',
8453
- * silent: true,
8693
+ * For instance, it is true if the column has JSON `{ "a": 1, "b": 2 }` and provided value is `{ "a": 1 }`.
8694
+ *
8695
+ * Takes the value of any type, or sub query which returns a single value, or a raw SQL expression.
8696
+ *
8697
+ * ```ts
8698
+ * db.table.where({
8699
+ * jsonbColumn: {
8700
+ * jsonSupersetOf: { a: 1 },
8454
8701
  * },
8455
- * );
8702
+ * });
8456
8703
  * ```
8457
8704
  *
8458
- * Nested JSON operations can be used in place of JSON column name:
8705
+ * `jsonSubsetOf`: check if the column value is a subset of provided value.
8706
+ *
8707
+ * For instance, it is true if the column has JSON `{ "a": 1 }` and provided value is `{ "a": 1, "b": 2 }`.
8708
+ *
8709
+ * Takes the value of any type, or sub query which returns a single value, or a raw SQL expression.
8459
8710
  *
8460
8711
  * ```ts
8461
- * db.table.jsonPathQuery(
8462
- * columnTypes.text(3, 100),
8463
- * // Available: .jsonSet, .jsonInsert, .jsonRemove
8464
- * db.table.jsonSet('data', ['key'], 'value'),
8465
- * '$.name',
8466
- * 'name',
8467
- * );
8712
+ * db.table.where({
8713
+ * jsonbColumn: {
8714
+ * jsonSupersetOf: { a: 1 },
8715
+ * },
8716
+ * });
8468
8717
  * ```
8469
8718
  *
8470
- * @param type - provide a column type to have a correct result type
8471
- * @param column - name of JSON column, or a result of a nested json method
8472
- * @param path - special JSON path string to reference a JSON value
8473
- * @param as - optional alias for the selected value
8474
- * @param options - supports `vars` and `silent`, check Postgres docs of `json_path_query` for these
8719
+ * @param args - {@link WhereArgs}
8475
8720
  */
8476
- jsonPathQuery(type, column, path, as, options) {
8477
- const q = this.clone();
8478
- const json = {
8479
- __json: ["pathQuery", as, type, column, path, options]
8480
- };
8481
- return Object.assign(
8482
- pushQueryValue(q, "select", json),
8483
- json
8721
+ where(...args) {
8722
+ return _queryWhere(
8723
+ this.clone(),
8724
+ args
8484
8725
  );
8485
8726
  }
8486
- }
8487
- class JsonMethods {
8488
8727
  /**
8489
- * Wraps the query in a way to select a single JSON string.
8490
- * So that JSON encoding is done on a database side, and the application doesn't have to turn a response to a JSON.
8491
- * It may be better for performance in some cases.
8728
+ * Use a custom SQL expression in `WHERE` statement:
8492
8729
  *
8493
8730
  * ```ts
8494
- * // json is a JSON string that you can directly send as a response.
8495
- * const json = await db.table.select('id', 'name').json();
8731
+ * db.table.where`a = b`;
8732
+ *
8733
+ * // or
8734
+ * db.table.where(db.table.sql`a = b`);
8735
+ *
8736
+ * // or
8737
+ * import { raw } from 'orchid-orm';
8738
+ *
8739
+ * db.table.where(raw`a = b`);
8496
8740
  * ```
8497
8741
  *
8498
- * @param coalesce
8742
+ * @param args - SQL expression
8499
8743
  */
8500
- json(coalesce) {
8501
- return queryJson(
8744
+ whereSql(...args) {
8745
+ return _queryWhereSql(
8502
8746
  this.clone(),
8503
- coalesce
8747
+ args
8504
8748
  );
8505
8749
  }
8506
- }
8507
-
8508
- const logColors = {
8509
- boldCyanBright: (message) => `\x1B[1m\x1B[96m${message}\x1B[39m\x1B[22m`,
8510
- boldBlue: (message) => `\x1B[1m\x1B[34m${message}\x1B[39m\x1B[22m`,
8511
- boldYellow: (message) => `\x1B[1m\x1B[33m${message}\x1B[39m\x1B[22m`,
8512
- boldMagenta: (message) => `\x1B[1m\x1B[33m${message}\x1B[39m\x1B[22m`,
8513
- boldRed: (message) => `\x1B[1m\x1B[31m${message}\x1B[39m\x1B[22m`
8514
- };
8515
- const makeMessage = (colors, timeColor, time, sqlColor, sql, valuesColor, values) => {
8516
- const elapsed = process.hrtime(time);
8517
- const formattedTime = `(${elapsed[0] ? `${elapsed[0]}s ` : ""}${(elapsed[1] / 1e6).toFixed(1)}ms)`;
8518
- const result = `${colors ? timeColor(formattedTime) : formattedTime} ${colors ? sqlColor(sql) : sql}`;
8519
- if (!values.length) {
8520
- return result;
8521
- }
8522
- const formattedValues = `[${values.map(quote).join(", ")}]`;
8523
- return `${result} ${colors ? valuesColor(formattedValues) : formattedValues}`;
8524
- };
8525
- const logParamToLogObject = (logger, log) => {
8526
- if (!log)
8527
- return;
8528
- const logObject = Object.assign(
8529
- {
8530
- colors: true,
8531
- beforeQuery() {
8532
- return process.hrtime();
8533
- },
8534
- afterQuery(sql, time) {
8535
- logger.log(
8536
- makeMessage(
8537
- colors,
8538
- logColors.boldCyanBright,
8539
- time,
8540
- logColors.boldBlue,
8541
- sql.text,
8542
- logColors.boldYellow,
8543
- sql.values
8544
- )
8545
- );
8546
- },
8547
- onError(error, sql, time) {
8548
- const message = `Error: ${error.message}`;
8549
- logger.error(
8550
- `${makeMessage(
8551
- colors,
8552
- logColors.boldMagenta,
8553
- time,
8554
- logColors.boldRed,
8555
- sql.text,
8556
- logColors.boldYellow,
8557
- sql.values
8558
- )} ${colors ? logColors.boldRed(message) : message}`
8559
- );
8560
- }
8561
- },
8562
- log === true ? {} : log
8563
- );
8564
- const colors = logObject.colors;
8565
- return logObject;
8566
- };
8567
- class QueryLog {
8568
- log(log = true) {
8569
- const q = this.clone();
8570
- q.q.log = logParamToLogObject(q.q.logger, log);
8571
- return q;
8572
- }
8573
- }
8574
-
8575
- var __defProp$5 = Object.defineProperty;
8576
- var __getOwnPropSymbols$5 = Object.getOwnPropertySymbols;
8577
- var __hasOwnProp$5 = Object.prototype.hasOwnProperty;
8578
- var __propIsEnum$5 = Object.prototype.propertyIsEnumerable;
8579
- var __defNormalProp$5 = (obj, key, value) => key in obj ? __defProp$5(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
8580
- var __spreadValues$5 = (a, b) => {
8581
- for (var prop in b || (b = {}))
8582
- if (__hasOwnProp$5.call(b, prop))
8583
- __defNormalProp$5(a, prop, b[prop]);
8584
- if (__getOwnPropSymbols$5)
8585
- for (var prop of __getOwnPropSymbols$5(b)) {
8586
- if (__propIsEnum$5.call(b, prop))
8587
- __defNormalProp$5(a, prop, b[prop]);
8588
- }
8589
- return a;
8590
- };
8591
- const mergableObjects = {
8592
- shape: true,
8593
- withShapes: true,
8594
- parsers: true,
8595
- defaults: true,
8596
- joinedShapes: true,
8597
- joinedParsers: true
8598
- };
8599
- class MergeQueryMethods {
8600
- merge(q) {
8601
- const query = this.clone();
8602
- const a = query.q;
8603
- const b = q.q;
8604
- for (const key in b) {
8605
- const value = b[key];
8606
- switch (typeof value) {
8607
- case "boolean":
8608
- case "string":
8609
- case "number":
8610
- a[key] = value;
8611
- break;
8612
- case "object":
8613
- if (Array.isArray(value)) {
8614
- a[key] = a[key] ? [...a[key], ...value] : value;
8615
- } else if (mergableObjects[key]) {
8616
- a[key] = a[key] ? __spreadValues$5(__spreadValues$5({}, a[key]), value) : value;
8617
- } else {
8618
- a[key] = value;
8619
- }
8620
- break;
8621
- }
8622
- }
8623
- a[orchidCore.getValueKey] = b[orchidCore.getValueKey];
8624
- if (b.returnType)
8625
- a.returnType = b.returnType;
8626
- return query;
8627
- }
8628
- }
8629
-
8630
- var __defProp$4 = Object.defineProperty;
8631
- var __defProps$2 = Object.defineProperties;
8632
- var __getOwnPropDescs$2 = Object.getOwnPropertyDescriptors;
8633
- var __getOwnPropSymbols$4 = Object.getOwnPropertySymbols;
8634
- var __hasOwnProp$4 = Object.prototype.hasOwnProperty;
8635
- var __propIsEnum$4 = Object.prototype.propertyIsEnumerable;
8636
- var __defNormalProp$4 = (obj, key, value) => key in obj ? __defProp$4(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
8637
- var __spreadValues$4 = (a, b) => {
8638
- for (var prop in b || (b = {}))
8639
- if (__hasOwnProp$4.call(b, prop))
8640
- __defNormalProp$4(a, prop, b[prop]);
8641
- if (__getOwnPropSymbols$4)
8642
- for (var prop of __getOwnPropSymbols$4(b)) {
8643
- if (__propIsEnum$4.call(b, prop))
8644
- __defNormalProp$4(a, prop, b[prop]);
8645
- }
8646
- return a;
8647
- };
8648
- var __spreadProps$2 = (a, b) => __defProps$2(a, __getOwnPropDescs$2(b));
8649
- class With {
8650
8750
  /**
8651
- * Add Common Table Expression (CTE) to the query.
8751
+ * `whereNot` takes the same argument as `where`,
8752
+ * multiple conditions are combined with `AND`,
8753
+ * the whole group of conditions is negated with `NOT`.
8652
8754
  *
8653
8755
  * ```ts
8654
- * import { columnTypes } from 'orchid-orm';
8655
- * import { NumberColumn } from './number';
8656
- *
8657
- * // .with optionally accepts such options:
8658
- * type WithOptions = {
8659
- * // list of columns returned by this WITH statement
8660
- * // by default all columns from provided column shape will be included
8661
- * // true is for default behavior
8662
- * columns?: string[] | boolean;
8663
- *
8664
- * // Adds RECURSIVE keyword:
8665
- * recursive?: true;
8666
- *
8667
- * // Adds MATERIALIZED keyword:
8668
- * materialized?: true;
8669
- *
8670
- * // Adds NOT MATERIALIZED keyword:
8671
- * notMaterialized?: true;
8672
- * };
8756
+ * // find records of different colors than red
8757
+ * db.table.whereNot({ color: 'red' });
8758
+ * // WHERE NOT color = 'red'
8759
+ * db.table.whereNot({ one: 1, two: 2 });
8760
+ * // WHERE NOT (one = 1 AND two = 2)
8761
+ * ```
8673
8762
  *
8674
- * // accepts columns shape and a raw expression:
8675
- * db.table.with(
8676
- * 'alias',
8677
- * {
8678
- * id: columnTypes.integer(),
8679
- * name: columnTypes.text(3, 100),
8680
- * },
8681
- * db.table.sql`SELECT id, name FROM "someTable"`,
8682
- * );
8763
+ * @param args - {@link WhereArgs}
8764
+ */
8765
+ whereNot(...args) {
8766
+ return _queryWhereNot(
8767
+ this.clone(),
8768
+ args
8769
+ );
8770
+ }
8771
+ /**
8772
+ * `whereNot` version accepting SQL expression:
8683
8773
  *
8684
- * // accepts query:
8685
- * db.table.with('alias', db.table.all());
8774
+ * ```ts
8775
+ * db.table.whereNot`sql expression`
8776
+ * ```
8686
8777
  *
8687
- * // accepts a callback for a query builder:
8688
- * db.table.with('alias', (qb) =>
8689
- * qb.select({ one: db.table.sql((t) => t.integer())`1` }),
8690
- * );
8778
+ * @param args - SQL expression
8779
+ */
8780
+ whereNotSql(...args) {
8781
+ return _queryWhereNotSql(this.clone(), args);
8782
+ }
8783
+ /**
8784
+ * `orWhere` is accepting the same arguments as {@link where}, joining arguments with `OR`.
8691
8785
  *
8692
- * // All mentioned forms can accept options as a second argument:
8693
- * db.table.with(
8694
- * 'alias',
8695
- * {
8696
- * recursive: true,
8697
- * materialized: true,
8698
- * },
8699
- * rawOrQueryOrCallback,
8700
- * );
8701
- * ```
8786
+ * Columns in single arguments are still joined with `AND`.
8702
8787
  *
8703
- * Defined `WITH` table can be used in `.from` or `.join` with all the type safeness:
8788
+ * The database is processing `AND` before `OR`, so this should be intuitively clear.
8704
8789
  *
8705
8790
  * ```ts
8706
- * db.table.with('alias', db.table.all()).from('alias').select('alias.id');
8791
+ * db.table.where({ id: 1, color: 'red' }).orWhere({ id: 2, color: 'blue' });
8792
+ * // equivalent:
8793
+ * db.table.orWhere({ id: 1, color: 'red' }, { id: 2, color: 'blue' });
8794
+ * ```
8707
8795
  *
8708
- * db.table
8709
- * .with('alias', db.table.all())
8710
- * .join('alias', 'alias.id', 'user.id')
8711
- * .select('alias.id');
8796
+ * This query will produce such SQL (simplified):
8797
+ *
8798
+ * ```sql
8799
+ * SELECT * FROM "table"
8800
+ * WHERE id = 1 AND color = 'red'
8801
+ * OR id = 2 AND color = 'blue'
8712
8802
  * ```
8713
8803
  *
8714
- * @param args - first argument is an alias for this CTE, other arguments can be column shape, query object, or raw SQL.
8804
+ * @param args - {@link WhereArgs} will be joined with `OR`
8715
8805
  */
8716
- with(...args) {
8717
- const q = this.clone();
8718
- let options = args.length === 3 && !orchidCore.isExpression(args[2]) || args.length === 4 ? args[1] : void 0;
8719
- const last = args[args.length - 1];
8720
- const query = typeof last === "function" ? last(q.queryBuilder) : last;
8721
- const shape = args.length === 4 ? args[2] : orchidCore.isExpression(query) ? args[1] : query.q.shape;
8722
- if ((options == null ? void 0 : options.columns) === true) {
8723
- options = __spreadProps$2(__spreadValues$4({}, options), {
8724
- columns: Object.keys(shape)
8725
- });
8726
- }
8727
- pushQueryValue(q, "with", [args[0], options || orchidCore.emptyObject, query]);
8728
- return setQueryObjectValue(q, "withShapes", args[0], shape);
8806
+ orWhere(...args) {
8807
+ return _queryOr(this.clone(), args);
8729
8808
  }
8730
- }
8731
-
8732
- class Union {
8733
8809
  /**
8734
- * Creates a union query, taking an array or a list of callbacks, builders, or raw statements to build the union statement, with optional boolean `wrap`.
8735
- * If the `wrap` parameter is true, the queries will be individually wrapped in parentheses.
8810
+ * `orWhereNot` takes the same arguments as {@link orWhere}, and prepends each condition with `NOT` just as {@link whereNot} does.
8811
+ *
8812
+ * @param args - {@link WhereArgs} will be prefixed with `NOT` and joined with `OR`
8813
+ */
8814
+ orWhereNot(...args) {
8815
+ return _queryOrNot(
8816
+ this.clone(),
8817
+ args
8818
+ );
8819
+ }
8820
+ /**
8821
+ * `whereIn` and related methods are for the `IN` operator to check for inclusion in a list of values.
8822
+ *
8823
+ * When used with a single column it works equivalent to the `in` column operator:
8736
8824
  *
8737
8825
  * ```ts
8738
- * SomeTable.select('id', 'name').union(
8826
+ * db.table.whereIn('column', [1, 2, 3]);
8827
+ * // the same as:
8828
+ * db.table.where({ column: [1, 2, 3] });
8829
+ * ```
8830
+ *
8831
+ * `whereIn` can support a tuple of columns, that's what the `in` operator cannot support:
8832
+ *
8833
+ * ```ts
8834
+ * db.table.whereIn(
8835
+ * ['id', 'name'],
8739
8836
  * [
8740
- * OtherTable.select('id', 'name'),
8741
- * SomeTable.sql`SELECT id, name FROM "thirdTable"`,
8837
+ * [1, 'Alice'],
8838
+ * [2, 'Bob'],
8742
8839
  * ],
8743
- * true, // optional wrap parameter
8744
8840
  * );
8745
8841
  * ```
8746
8842
  *
8747
- * @param args - array of queries or raw SQLs
8748
- * @param wrap - provide `true` if you want the queries to be wrapped into parentheses
8843
+ * It supports sub query which should return records with columns of the same type:
8844
+ *
8845
+ * ```ts
8846
+ * db.table.whereIn(['id', 'name'], OtherTable.select('id', 'name'));
8847
+ * ```
8848
+ *
8849
+ * It supports raw SQL expression:
8850
+ *
8851
+ * ```ts
8852
+ * db.table.whereIn(['id', 'name'], db.table.sql`((1, 'one'), (2, 'two'))`);
8853
+ * ```
8749
8854
  */
8750
- union(args, wrap) {
8751
- return pushQueryArray(
8855
+ whereIn(...args) {
8856
+ return _queryWhereIn(
8752
8857
  this.clone(),
8753
- "union",
8754
- args.map((arg) => ({ arg, kind: "UNION", wrap }))
8858
+ true,
8859
+ args[0],
8860
+ args[1]
8755
8861
  );
8756
8862
  }
8757
8863
  /**
8758
- * Same as `union`, but allows duplicated rows.
8864
+ * Takes the same arguments as {@link whereIn}.
8865
+ * Add a `WHERE IN` condition prefixed with `OR` to the query:
8759
8866
  *
8760
- * @param args - array of queries or raw SQLs
8761
- * @param wrap - provide `true` if you want the queries to be wrapped into parentheses
8867
+ * ```ts
8868
+ * db.table.whereIn('a', [1, 2, 3]).orWhereIn('b', ['one', 'two']);
8869
+ * ```
8762
8870
  */
8763
- unionAll(args, wrap) {
8764
- return pushQueryArray(
8871
+ orWhereIn(...args) {
8872
+ return _queryWhereIn(
8765
8873
  this.clone(),
8766
- "union",
8767
- args.map((arg) => ({ arg, kind: "UNION ALL", wrap }))
8874
+ false,
8875
+ args[0],
8876
+ args[1]
8768
8877
  );
8769
8878
  }
8770
8879
  /**
8771
- * Same as `union`, but uses a `INTERSECT` SQL keyword instead
8880
+ * Acts as `whereIn`, but negates the condition with `NOT`:
8772
8881
  *
8773
- * @param args - array of queries or raw SQLs
8774
- * @param wrap - provide `true` if you want the queries to be wrapped into parentheses
8882
+ * ```ts
8883
+ * db.table.whereNotIn('color', ['red', 'green', 'blue']);
8884
+ * ```
8775
8885
  */
8776
- intersect(args, wrap) {
8777
- return pushQueryArray(
8886
+ whereNotIn(...args) {
8887
+ return _queryWhereIn(
8778
8888
  this.clone(),
8779
- "union",
8780
- args.map((arg) => ({ arg, kind: "INTERSECT", wrap }))
8889
+ true,
8890
+ args[0],
8891
+ args[1],
8892
+ true
8781
8893
  );
8782
8894
  }
8783
8895
  /**
8784
- * Same as `intersect`, but allows duplicated rows.
8896
+ * Acts as `whereIn`, but prepends `OR` to the condition and negates it with `NOT`:
8785
8897
  *
8786
- * @param args - array of queries or raw SQLs
8787
- * @param wrap - provide `true` if you want the queries to be wrapped into parentheses
8898
+ * ```ts
8899
+ * db.table.whereNotIn('a', [1, 2, 3]).orWhereNoIn('b', ['one', 'two']);
8900
+ * ```
8788
8901
  */
8789
- intersectAll(args, wrap) {
8790
- return pushQueryArray(
8902
+ orWhereNotIn(...args) {
8903
+ return _queryWhereIn(
8791
8904
  this.clone(),
8792
- "union",
8793
- args.map((arg) => ({ arg, kind: "INTERSECT ALL", wrap }))
8905
+ false,
8906
+ args[0],
8907
+ args[1],
8908
+ true
8794
8909
  );
8795
8910
  }
8796
8911
  /**
8797
- * Same as `union`, but uses an `EXCEPT` SQL keyword instead
8912
+ * `whereExists` is for support of the `WHERE EXISTS (query)` clause.
8798
8913
  *
8799
- * @param args - array of queries or raw SQLs
8800
- * @param wrap - provide `true` if you want the queries to be wrapped into parentheses
8914
+ * This method is accepting the same arguments as `join`, see the {@link Join.join} section for more details.
8915
+ *
8916
+ * ```ts
8917
+ * // find users who have accounts
8918
+ * // find by a relation name if it's defined
8919
+ * db.user.whereExists('account');
8920
+ *
8921
+ * // find using a table and a join conditions
8922
+ * db.user.whereExists(db.account, 'account.id', 'user.id');
8923
+ *
8924
+ * // find using a query builder in a callback:
8925
+ * db.user.whereExists(db.account, (q) => q.on('account.id', '=', 'user.id'));
8926
+ * ```
8801
8927
  */
8802
- except(args, wrap) {
8803
- return pushQueryArray(
8928
+ whereExists(arg, ...args) {
8929
+ return _queryWhereExists(
8804
8930
  this.clone(),
8805
- "union",
8806
- args.map((arg) => ({ arg, kind: "EXCEPT", wrap }))
8931
+ arg,
8932
+ args
8807
8933
  );
8808
8934
  }
8809
8935
  /**
8810
- * Same as `except`, but allows duplicated rows.
8936
+ * Acts as `whereExists`, but prepends the condition with `OR`:
8811
8937
  *
8812
- * @param args - array of queries or raw SQLs
8813
- * @param wrap - provide `true` if you want the queries to be wrapped into parentheses
8938
+ * ```ts
8939
+ * // find users who have an account or a profile,
8940
+ * // imagine that the user has both `account` and `profile` relations defined.
8941
+ * db.user.whereExist('account').orWhereExists('profile');
8942
+ * ```
8814
8943
  */
8815
- exceptAll(args, wrap) {
8816
- return pushQueryArray(
8817
- this.clone(),
8818
- "union",
8819
- args.map((arg) => ({ arg, kind: "EXCEPT ALL", wrap }))
8944
+ orWhereExists(arg, ...args) {
8945
+ const q = this.clone();
8946
+ return _queryOr(q, existsArgs(q, arg, args));
8947
+ }
8948
+ /**
8949
+ * Acts as `whereExists`, but negates the condition with `NOT`:
8950
+ *
8951
+ * ```ts
8952
+ * // find users who don't have an account,
8953
+ * // image that the user `belongsTo` or `hasOne` account.
8954
+ * db.user.whereNotExist('account');
8955
+ * ```
8956
+ *
8957
+ * @param arg - relation name, or a query object, or a `with` table alias, or a callback returning a query object.
8958
+ * @param args - no arguments needed when the first argument is a relation name, or conditions to join the table with.
8959
+ */
8960
+ whereNotExists(arg, ...args) {
8961
+ const q = this.clone();
8962
+ return _queryWhereNot(
8963
+ q,
8964
+ existsArgs(q, arg, args)
8820
8965
  );
8821
8966
  }
8967
+ /**
8968
+ * Acts as `whereExists`, but prepends the condition with `OR` and negates it with `NOT`:
8969
+ *
8970
+ * ```ts
8971
+ * // find users who don't have an account OR who don't have a profile
8972
+ * // imagine that the user has both `account` and `profile` relations defined.
8973
+ * db.user.whereNotExists('account').orWhereNotExists('profile');
8974
+ * ```
8975
+ */
8976
+ orWhereNotExists(arg, ...args) {
8977
+ const q = this.clone();
8978
+ return _queryOrNot(q, existsArgs(q, arg, args));
8979
+ }
8822
8980
  }
8823
8981
 
8824
8982
  var __defProp$3 = Object.defineProperty;
@@ -9822,6 +9980,22 @@ class RawSqlMethods {
9822
9980
  }
9823
9981
  }
9824
9982
 
9983
+ class QueryBase {
9984
+ constructor() {
9985
+ this.q = {};
9986
+ }
9987
+ /**
9988
+ * Clones the current query chain, useful for re-using partial query snippets in other queries without mutating the original.
9989
+ *
9990
+ * Used under the hood, and not really needed on the app side.
9991
+ */
9992
+ clone() {
9993
+ const cloned = Object.create(this.baseQuery);
9994
+ cloned.q = getClonedQueryData(this.q);
9995
+ return cloned;
9996
+ }
9997
+ }
9998
+
9825
9999
  class TransformMethods {
9826
10000
  /**
9827
10001
  * Transform the result of the query right after loading it.
@@ -10659,7 +10833,7 @@ orchidCore.applyMixins(QueryMethods, [
10659
10833
  Select,
10660
10834
  From,
10661
10835
  Join,
10662
- OnQueryBuilder,
10836
+ OnMethods,
10663
10837
  With,
10664
10838
  Union,
10665
10839
  JsonModifiers,
@@ -10960,7 +11134,6 @@ const performQuery = async (q, args, method) => {
10960
11134
  };
10961
11135
  orchidCore.applyMixins(Db, [QueryMethods]);
10962
11136
  Db.prototype.constructor = Db;
10963
- Db.prototype.onQueryBuilder = OnQueryBuilder;
10964
11137
  const createDb = (_a) => {
10965
11138
  var _b = _a, {
10966
11139
  log,
@@ -11207,7 +11380,7 @@ exports.NotFoundError = NotFoundError;
11207
11380
  exports.NumberAsStringBaseColumn = NumberAsStringBaseColumn;
11208
11381
  exports.NumberBaseColumn = NumberBaseColumn;
11209
11382
  exports.OnConflictQueryBuilder = OnConflictQueryBuilder;
11210
- exports.OnQueryBuilder = OnQueryBuilder;
11383
+ exports.OnMethods = OnMethods;
11211
11384
  exports.Operators = Operators;
11212
11385
  exports.OrchidOrmError = OrchidOrmError;
11213
11386
  exports.OrchidOrmInternalError = OrchidOrmInternalError;
@@ -11249,7 +11422,6 @@ exports.Update = Update;
11249
11422
  exports.VarCharColumn = VarCharColumn;
11250
11423
  exports.VirtualColumn = VirtualColumn;
11251
11424
  exports.Where = Where;
11252
- exports.WhereQueryBase = WhereQueryBase;
11253
11425
  exports.With = With;
11254
11426
  exports.XMLColumn = XMLColumn;
11255
11427
  exports._queryAfterSaveCommit = _queryAfterSaveCommit;
@@ -11301,6 +11473,7 @@ exports._queryUpdate = _queryUpdate;
11301
11473
  exports._queryUpdateOrThrow = _queryUpdateOrThrow;
11302
11474
  exports._queryUpdateRaw = _queryUpdateRaw;
11303
11475
  exports._queryWhere = _queryWhere;
11476
+ exports._queryWhereExists = _queryWhereExists;
11304
11477
  exports._queryWhereIn = _queryWhereIn;
11305
11478
  exports._queryWhereNot = _queryWhereNot;
11306
11479
  exports._queryWhereNotSql = _queryWhereNotSql;