joist-core 2.3.0-next.90 → 2.3.0-next.91

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/build/query.cjs CHANGED
@@ -1,6 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_conditions = require("./conditions.cjs");
3
- require("./typeMap.cjs");
4
3
  const require_keywords = require("./keywords.cjs");
5
4
  const require_EntityMetadata = require("./EntityMetadata.cjs");
6
5
  const require_utils = require("./utils.cjs");
@@ -413,10 +412,6 @@ function validateReadQuery(arg) {
413
412
  else {
414
413
  validateQueryKeys(arg, READ_KEYS, "Read queries");
415
414
  if (!("from" in arg && "select" in arg)) require_utils.fail("em.query expects a { from, select, ... } object or a query(...) value");
416
- if (require_Tables.isTable(arg.select)) {
417
- const meta = require_Tables.getTableMetadata(arg.select);
418
- if (meta.inheritanceType || meta.baseType || meta.baseTypes.length || meta.subTypes.length) require_utils.fail(`Inherited table ${meta.type} cannot be selected as entities; select its columns individually`);
419
- }
420
415
  }
421
416
  const options = arg;
422
417
  for (const key of ["limit", "offset"]) {
@@ -777,7 +772,7 @@ function parseQuery(q, parent, assigner, recursiveSelf) {
777
772
  });
778
773
  const softDeletes = q.softDeletes ?? "exclude";
779
774
  const from = parseFrom();
780
- const joins = pendingJoins.map((j) => {
775
+ const userJoins = pendingJoins.map((j) => {
781
776
  const source = j.parseSource();
782
777
  const userOn = conditionToSql(j.on, ctx, true);
783
778
  const injected = injectedConditions(source, j.softDeletes ? softDeletes : "include");
@@ -790,8 +785,11 @@ function parseQuery(q, parent, assigner, recursiveSelf) {
790
785
  fullOn
791
786
  };
792
787
  });
793
- const { selects, decodeRows, output } = selectsToSql(q, ctx, from);
794
- const fromInjected = injectedConditions(from, softDeletes);
788
+ const cti = ctiEntityPlan(q, from, assigner);
789
+ const joins = [...cti.joins, ...userJoins];
790
+ const sti = stiEntityPlan(q, from);
791
+ const { selects, decodeRows, output } = selectsToSql(q, ctx, from, cti.selects ?? sti.selects);
792
+ const fromInjected = [...injectedConditions(from, softDeletes), ...sti.conditions];
795
793
  const where = conditionToSql(fromInjected.length > 0 ? { and: [q.where, ...fromInjected] } : q.where, ctx, true);
796
794
  const having = conditionToSql(q.having, ctx, true);
797
795
  const groupBys = (q.groupBy ?? []).map((g) => asExpr(g, "groupBy").toSql(ctx));
@@ -963,14 +961,14 @@ function registerJoinTable(handle, ctx, assigner) {
963
961
  });
964
962
  }
965
963
  /** Generates the `select` clause SQL and returns how to decode the resulting rows. */
966
- function selectsToSql(q, ctx, from) {
964
+ function selectsToSql(q, ctx, from, entitySelects) {
967
965
  const { select } = q;
968
966
  if (require_Tables.isTable(select)) {
969
967
  if (from.handle !== require_Tables.getTableMgmt(select)) require_utils.fail("Selecting a joined table is not supported yet; select the from table, or select its columns individually");
970
968
  const alias = ctx.aliasFor(require_Tables.getTableMgmt(select));
971
969
  const meta = require_Tables.getTableMetadata(select);
972
970
  return {
973
- selects: from.entitySelects.map((s) => ({
971
+ selects: entitySelects ?? from.entitySelects.map((s) => ({
974
972
  sql: s,
975
973
  bindings: [],
976
974
  refs: [alias]
@@ -1000,6 +998,139 @@ function selectsToSql(q, ctx, from) {
1000
998
  projection.output = joinedOutput(projection.output, q.join);
1001
999
  return projection;
1002
1000
  }
1001
+ /**
1002
+ * Builds the mandatory CTI joins and flat row projection needed by entity hydration.
1003
+ *
1004
+ * The joins use the same ParsedJoin path as user joins, but `keep: true` makes them survive pruning.
1005
+ * They are generated only for an entity selection of the from source; POJO and scalar reads keep their
1006
+ * physical-table behavior. User source aliases have already been allocated, so the preferred `_bN` and
1007
+ * `_sN` aliases can be made unique before their projection fragments are built.
1008
+ */
1009
+ function ctiEntityPlan(q, from, assigner) {
1010
+ if (!require_Tables.isTable(q.select) || from.handle !== require_Tables.getTableMgmt(q.select)) return {
1011
+ joins: [],
1012
+ selects: void 0
1013
+ };
1014
+ const meta = require_Tables.getTableMetadata(q.select);
1015
+ if (meta.inheritanceType !== "cti") return {
1016
+ joins: [],
1017
+ selects: void 0
1018
+ };
1019
+ const joins = [];
1020
+ const selects = from.entitySelects.map((sql) => ({
1021
+ sql,
1022
+ bindings: [],
1023
+ refs: [from.alias]
1024
+ }));
1025
+ for (const [i, baseMeta] of meta.baseTypes.entries()) {
1026
+ const alias = assigner.getLiteralAlias(`${from.alias}_b${i}`);
1027
+ joins.push(ctiJoin(from, baseMeta, alias));
1028
+ selects.push(...entitySelectFragments(baseMeta, alias));
1029
+ }
1030
+ const subtypeColumns = /* @__PURE__ */ new Map();
1031
+ for (const [i, subtypeMeta] of meta.subTypes.entries()) {
1032
+ const alias = assigner.getLiteralAlias(`${from.alias}_s${i}`);
1033
+ joins.push(ctiJoin(from, subtypeMeta, alias));
1034
+ selects.push(...entitySelectFragments(subtypeMeta, alias));
1035
+ for (const field of Object.values(subtypeMeta.fields)) {
1036
+ if (field.fieldName === "id" || !field.serde || field.kind === "primitive" && field.lazy) continue;
1037
+ for (const column of field.serde.columns) {
1038
+ const aliases = subtypeColumns.get(column.columnName);
1039
+ if (aliases) aliases.push(alias);
1040
+ else subtypeColumns.set(column.columnName, [alias]);
1041
+ }
1042
+ }
1043
+ }
1044
+ selects.push({
1045
+ sql: `${require_keywords.safeKq(from.alias)}.${require_keywords.kq("id")} AS ${require_keywords.kq("id")}`,
1046
+ bindings: [],
1047
+ refs: [from.alias]
1048
+ });
1049
+ for (const [column, aliases] of subtypeColumns) {
1050
+ if (aliases.length < 2) continue;
1051
+ selects.push({
1052
+ sql: `COALESCE(${aliases.map((alias) => `${require_keywords.safeKq(alias)}.${require_keywords.kq(column)}`).join(", ")}) AS ${require_keywords.kq(column)}`,
1053
+ bindings: [],
1054
+ refs: aliases
1055
+ });
1056
+ }
1057
+ if (meta.subTypes.length > 0) {
1058
+ const subtypeAliases = joins.slice(meta.baseTypes.length).map((join) => join.source.alias);
1059
+ selects.push({
1060
+ sql: `CASE ${subtypeAliases.map((alias) => `WHEN ${require_keywords.safeKq(alias)}.${require_keywords.kq("id")} IS NOT NULL THEN ?`).join(" ")} ELSE '_' END AS ${require_keywords.kq("__class")}`,
1061
+ bindings: meta.subTypes.map((subtype) => subtype.type),
1062
+ refs: subtypeAliases
1063
+ });
1064
+ }
1065
+ return {
1066
+ joins,
1067
+ selects
1068
+ };
1069
+ }
1070
+ /** Adds the discriminator projection and subtype filter required by STI entity hydration. */
1071
+ function stiEntityPlan(q, from) {
1072
+ if (!require_Tables.isTable(q.select) || from.handle !== require_Tables.getTableMgmt(q.select)) return {
1073
+ selects: void 0,
1074
+ conditions: []
1075
+ };
1076
+ const meta = require_Tables.getTableMetadata(q.select);
1077
+ if (meta.inheritanceType !== "sti") return {
1078
+ selects: void 0,
1079
+ conditions: []
1080
+ };
1081
+ const selects = from.entitySelects.map((sql) => ({
1082
+ sql,
1083
+ bindings: [],
1084
+ refs: [from.alias]
1085
+ }));
1086
+ if (!(from.entitySelects.length === 1 && from.entitySelects[0] === require_keywords.kqStar(from.alias))) {
1087
+ const discriminator = require_EntityMetadata.getBaseMeta(meta).stiDiscriminatorColumnName;
1088
+ selects.push({
1089
+ sql: `${require_keywords.safeKq(from.alias)}.${require_keywords.kq(discriminator)} AS ${require_keywords.kq(discriminator)}`,
1090
+ bindings: [],
1091
+ refs: [from.alias]
1092
+ });
1093
+ }
1094
+ const condition = require_QueryParser.stiSubtypeFilter(meta, from.alias);
1095
+ return {
1096
+ selects,
1097
+ conditions: condition ? [condition] : []
1098
+ };
1099
+ }
1100
+ /** Creates one mandatory CTI join through the selected source's primary key. */
1101
+ function ctiJoin(from, meta, alias) {
1102
+ const on = {
1103
+ sql: `${require_keywords.safeKq(from.alias)}.${require_keywords.kq("id")} = ${require_keywords.safeKq(alias)}.${require_keywords.kq("id")}`,
1104
+ bindings: [],
1105
+ refs: [from.alias, alias]
1106
+ };
1107
+ return {
1108
+ kind: "left",
1109
+ source: {
1110
+ handle: {
1111
+ tableName: meta.tableName,
1112
+ meta
1113
+ },
1114
+ alias,
1115
+ sql: `${require_keywords.kq(meta.tableName)} AS ${require_keywords.safeKq(alias)}`,
1116
+ bindings: [],
1117
+ refs: [],
1118
+ entitySelects: [],
1119
+ meta
1120
+ },
1121
+ userOn: on,
1122
+ fullOn: on,
1123
+ keep: true
1124
+ };
1125
+ }
1126
+ /** Selects one physical CTI table while preserving lazy-field exclusions. */
1127
+ function entitySelectFragments(meta, alias) {
1128
+ return (meta.hasLazyColumns ? require_QueryParser.lazyExcludedSelects(meta, alias) : [require_keywords.kqStar(alias)]).map((sql) => ({
1129
+ sql,
1130
+ bindings: [],
1131
+ refs: [alias]
1132
+ }));
1133
+ }
1003
1134
  function decodeRow(row, decoders) {
1004
1135
  const result = {};
1005
1136
  for (const [key, expr] of decoders) {