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