@vibeorm/sql 5.1.0 → 5.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/ast.d.ts CHANGED
@@ -45,6 +45,8 @@ export type ColRef = {
45
45
  * storage and read-back stay the exact text.
46
46
  */
47
47
  readonly castReal?: boolean;
48
+ /** Native enum order key through a scalar subquery: MySQL reads its member index, other dialects keep the column. */
49
+ readonly enumOrdinal?: true;
48
50
  /**
49
51
  * Order/distinct/compare cast, postgres `json` columns only (`Json @db.Json`,
50
52
  * release-3.0 D3): `json` has no ordering or equality operator, so ORDER BY,
@@ -93,6 +95,8 @@ export type AggRef = {
93
95
  } | {
94
96
  readonly fn: AggFn;
95
97
  readonly column: ColRef;
98
+ /** Native enum MIN/MAX: projections and HAVING return the database label; order keys keep its ordinal. */
99
+ readonly enumOrder?: "value" | "ordinal";
96
100
  };
97
101
  /**
98
102
  * One aggregate projection: `FN("col") AS "alias"`. The alias is part of the
@@ -463,18 +467,27 @@ export type OrderByAggregate = {
463
467
  };
464
468
  /**
465
469
  * ORDER BY a correlated scalar subquery — relation orderBy
466
- * (`orderBy: { posts: { _count: "desc" } }` → `(SELECT COUNT(*) …)` /
470
+ * (`orderBy: { posts: { _count: "desc" } }` → `(SELECT COUNT(*) …)`,
471
+ * `orderBy: { posts: { _max: { createdAt: "desc" } } }` → `(SELECT MAX(…) …)`,
467
472
  * `orderBy: { author: { email: "asc" } }` → `(SELECT "email" …)`).
468
473
  * Portable: all three dialects order by scalar subqueries. The subquery
469
474
  * correlates to the outer scope through table-qualified `cmpCol` predicates,
470
475
  * exactly like `exists`; its parameters merge into the statement's single
471
- * sequence in render order. NULLS placement is deliberately unsupported here
472
- * (emulating it would render the subquery twice and duplicate its params) —
473
- * missing relations sort at each engine's native NULL position.
476
+ * sequence in render order.
477
+ *
478
+ * `nulls` places the NULL a missing to-one row or a parent without related
479
+ * rows (under MIN/MAX/AVG/SUM) sorts as. Without it, NULL sorts at the
480
+ * engine's native position. Postgres and sqlite render `NULLS FIRST|LAST`;
481
+ * mysql renders the bare term when its native placement already matches, and
482
+ * otherwise a leading `(<subquery>) IS NULL` term — the subquery then renders
483
+ * twice and its parameters bind twice, in text order.
474
484
  */
475
485
  export type OrderBySubquery = {
476
486
  readonly select: SelectStatement;
487
+ /** A count behind a missing to-one row is zero, not NULL. */
488
+ readonly coalesce?: 0;
477
489
  readonly direction: "asc" | "desc";
490
+ readonly nulls?: "first" | "last";
478
491
  };
479
492
  export type OrderTerm = OrderByColumn | OrderByAggregate | OrderBySubquery;
480
493
  /**
@@ -519,6 +532,17 @@ export type LateralJoin = {
519
532
  /** The correlated child select — may reference outer columns via table-qualified refs. */
520
533
  readonly select: SelectStatement;
521
534
  };
535
+ /**
536
+ * One scalar subquery in a projection. The builder guarantees a single column
537
+ * and at most one row. Hidden sort values stay inside derived tables rather
538
+ * than becoming result fields. `coalesce` preserves a count across a missing
539
+ * to-one hop.
540
+ */
541
+ export type ScalarSubquery = {
542
+ readonly alias: string;
543
+ readonly select: SelectStatement;
544
+ readonly coalesce?: 0;
545
+ };
522
546
  /**
523
547
  * One correlated scalar `COUNT(*)` projected into a select list:
524
548
  * `(SELECT COUNT(*) FROM … WHERE <correlation>) AS "alias"` — the relation
@@ -765,6 +789,8 @@ export type SelectStatement = {
765
789
  readonly aggregates?: readonly AggSelect[];
766
790
  /** Correlated `COUNT(*)` subqueries, rendered after `aggregates` — see {@link CountSubquery}. */
767
791
  readonly countSubqueries?: readonly CountSubquery[];
792
+ /** Scalar values projected after count subqueries and before a row number. */
793
+ readonly scalarSubqueries?: readonly ScalarSubquery[];
768
794
  /**
769
795
  * `SELECT DISTINCT ON (cols)` — postgres only (`capabilities.distinctOn`;
770
796
  * other dialects refuse at render time and the runtime emulates client-side
package/dist/dialect.d.ts CHANGED
@@ -446,6 +446,16 @@ export type SqlDialect = {
446
446
  quoteStringLiteral(params: {
447
447
  value: string;
448
448
  }): string;
449
+ /** Keep a native enum's ordering through a scalar subquery that may lose its physical enum type. */
450
+ renderEnumOrdinal(params: {
451
+ column: string;
452
+ }): string;
453
+ /** Render native enum MIN/MAX in database declaration order, returning its label or an order key. */
454
+ renderEnumMinMax(params: {
455
+ fn: "min" | "max";
456
+ column: string;
457
+ result: "value" | "ordinal";
458
+ }): string;
449
459
  /** Positional placeholder for the 1-based parameter index ($n or ?). */
450
460
  placeholder(params: {
451
461
  index: number;
package/dist/index.d.ts CHANGED
@@ -40,7 +40,7 @@ export type { ResultDecoder, ResultReadDecoder, ResultParameterEncoder, ResultSc
40
40
  * lives in this package; see the root sql-catalog.md for the reviewable surface.
41
41
  */
42
42
  export { compileExclusion, renderExclusionSetup, renderExclusionDdl, renderExclusionCatalog, renderExclusionProbe } from "./exclusion.ts";
43
- export type { AdvisoryLockKey, AdvisoryLockStatement, AggFn, AggRef, AggSelect, ChainResult, ChainRowSource, ChainStatement, ChainStep, ChainStepStatement, ChainTally, ChainVeto, ColRef, ComparisonOp, DateTruncation, CountSubquery, DeleteStatement, ExcludedRef, InnerJoin, KeyListMember, KeyPage, KeyRows, KeyTableJoin, KeyTableMember, InsertSelectStatement, InsertStatement, ReturningColumn, JsonFilterOp, LateralJoin, LeftJoin, ListSetOperation, OnConflictClause, OnConflictDoNothing, OnConflictUpdate, OrderByAggregate, OrderByColumn, OrderBySubquery, OrderTerm, RowLock, RowNumberProjection, DbNowMarker, JsonParamCell, SelectExpr, SelectStatement, SetOperation, SetOperator, SqlExpr, SqlStatement, StepColumnRef, TableRef, UpdateStatement, } from "./ast.ts";
43
+ export type { AdvisoryLockKey, AdvisoryLockStatement, AggFn, AggRef, AggSelect, ChainResult, ChainRowSource, ChainStatement, ChainStep, ChainStepStatement, ChainTally, ChainVeto, ColRef, ComparisonOp, DateTruncation, CountSubquery, ScalarSubquery, DeleteStatement, ExcludedRef, InnerJoin, KeyListMember, KeyPage, KeyRows, KeyTableJoin, KeyTableMember, InsertSelectStatement, InsertStatement, ReturningColumn, JsonFilterOp, LateralJoin, LeftJoin, ListSetOperation, OnConflictClause, OnConflictDoNothing, OnConflictUpdate, OrderByAggregate, OrderByColumn, OrderBySubquery, OrderTerm, RowLock, RowNumberProjection, DbNowMarker, JsonParamCell, SelectExpr, SelectStatement, SetOperation, SetOperator, SqlExpr, SqlStatement, StepColumnRef, TableRef, UpdateStatement, } from "./ast.ts";
44
44
  export { ADVISORY_LOCK_RESULT_COLUMN, CHAIN_VETO_PREFIX, CHAIN_VETO_TOKEN_PATTERN, SQL_DEFAULT_CELL, SQL_KEEP_CELL, chainVetoToken, dbNowMarker, excludedRef, isDbNowMarker, isExcludedRef, isJsonParamCell, isListSetOperation, isSetOperation, isSqlDefaultCell, isSqlKeepCell, isStepColumnRef, jsonParamCell, returningColumnName, stepColumnRef, } from "./ast.ts";
45
45
  export type { AdvisoryLockFunctions, AdvisoryLockModeFunctions, RowLockSyntax, EmptyInsertSyntax, SqlDialect, } from "./dialect.ts";
46
46
  export { ADVISORY_KEY_BOUNDS, describeRowLock, renderAdvisoryLock, renderRowLockClause } from "./locks.ts";
package/dist/index.js CHANGED
@@ -2402,6 +2402,12 @@ var sqliteDialect = {
2402
2402
  quoteStringLiteral({ value }) {
2403
2403
  return `'${value.replaceAll("'", "''")}'`;
2404
2404
  },
2405
+ renderEnumOrdinal({ column }) {
2406
+ return column;
2407
+ },
2408
+ renderEnumMinMax({ fn, column }) {
2409
+ return `${fn === "min" ? "MIN" : "MAX"}(${column})`;
2410
+ },
2405
2411
  placeholder() {
2406
2412
  return "?";
2407
2413
  },
@@ -3075,6 +3081,12 @@ var postgresDialect = {
3075
3081
  quoteStringLiteral({ value }) {
3076
3082
  return `'${value.replaceAll("'", "''")}'`;
3077
3083
  },
3084
+ renderEnumOrdinal({ column }) {
3085
+ return column;
3086
+ },
3087
+ renderEnumMinMax({ fn, column }) {
3088
+ return `${fn === "min" ? "MIN" : "MAX"}(${column})`;
3089
+ },
3078
3090
  placeholder({ index }) {
3079
3091
  return `$${index}`;
3080
3092
  },
@@ -3148,6 +3160,15 @@ var mysqlDialect = {
3148
3160
  quoteStringLiteral({ value }) {
3149
3161
  return `'${value.replaceAll("\\", "\\\\").replaceAll("'", "''")}'`;
3150
3162
  },
3163
+ renderEnumOrdinal({ column }) {
3164
+ return `${column} + 0`;
3165
+ },
3166
+ renderEnumMinMax({ fn, column, result }) {
3167
+ const aggregate = fn === "min" ? "MIN" : "MAX";
3168
+ if (result === "ordinal")
3169
+ return `${aggregate}(${column} + 0)`;
3170
+ return `SUBSTRING(${aggregate}(CONCAT(LPAD(${column} + 0, 5, '0'), ${column})), 6)`;
3171
+ },
3151
3172
  placeholder() {
3152
3173
  return "?";
3153
3174
  },
@@ -4129,6 +4150,8 @@ function quoteCol(dialect, column) {
4129
4150
  const ref = column.table === undefined ? name : `${dialect.quoteIdent({ name: column.table })}.${name}`;
4130
4151
  if (column.truncate !== undefined)
4131
4152
  return renderDateTruncation({ dialect, reference: ref, truncation: column.truncate });
4153
+ if (column.enumOrdinal === true)
4154
+ return dialect.renderEnumOrdinal({ column: ref });
4132
4155
  if (column.castReal === true) {
4133
4156
  if (dialect.name !== "sqlite") {
4134
4157
  throw internalError4({ message: "ColRef.castReal is a sqlite Decimal construct" });
@@ -4229,7 +4252,11 @@ var AGG_FN_SQL = {
4229
4252
  function renderAggRef(dialect, agg) {
4230
4253
  if (agg.fn === "countAll")
4231
4254
  return "COUNT(*)";
4232
- return `${AGG_FN_SQL[agg.fn]}(${quoteCol(dialect, agg.column)})`;
4255
+ const column = quoteCol(dialect, agg.column);
4256
+ if ((agg.fn === "min" || agg.fn === "max") && agg.enumOrder !== undefined) {
4257
+ return dialect.renderEnumMinMax({ fn: agg.fn, column, result: agg.enumOrder });
4258
+ }
4259
+ return `${AGG_FN_SQL[agg.fn]}(${column})`;
4233
4260
  }
4234
4261
  function assertScalarArrays(dialect, feature) {
4235
4262
  if (dialect.capabilities.scalarArrays)
@@ -4770,23 +4797,20 @@ function renderOrderTerm(params) {
4770
4797
  if ("agg" in term) {
4771
4798
  return `${renderAggRef(dialect, term.agg)} ${term.direction === "asc" ? "ASC" : "DESC"}`;
4772
4799
  }
4773
- if ("select" in term) {
4774
- const sub = renderSelect({ dialect, statement: term.select, collector });
4775
- return `(${sub.text}) ${term.direction === "asc" ? "ASC" : "DESC"}`;
4776
- }
4777
- const column = quoteCol(dialect, term.column);
4800
+ const expression = "select" in term ? () => renderScalarSubquery({ dialect, subquery: term, collector }) : () => quoteCol(dialect, term.column);
4778
4801
  const direction = term.direction === "asc" ? "ASC" : "DESC";
4779
4802
  if (term.nulls === undefined) {
4780
- return `${column} ${direction}`;
4803
+ return `${expression()} ${direction}`;
4781
4804
  }
4782
4805
  if (dialect.orderByNullsMode === "native") {
4783
- return `${column} ${direction} NULLS ${term.nulls === "first" ? "FIRST" : "LAST"}`;
4806
+ return `${expression()} ${direction} NULLS ${term.nulls === "first" ? "FIRST" : "LAST"}`;
4784
4807
  }
4785
4808
  if (term.nulls === "first" === (term.direction === "asc")) {
4786
- return `${column} ${direction}`;
4809
+ return `${expression()} ${direction}`;
4787
4810
  }
4788
- const nullsTerm = term.nulls === "first" ? `${column} IS NULL DESC` : `${column} IS NULL`;
4789
- return `${nullsTerm}, ${column} ${direction}`;
4811
+ const tested = expression();
4812
+ const nullsTerm = term.nulls === "first" ? `${tested} IS NULL DESC` : `${tested} IS NULL`;
4813
+ return `${nullsTerm}, ${expression()} ${direction}`;
4790
4814
  }
4791
4815
  function renderSelectExpr(params) {
4792
4816
  const { dialect, expr } = params;
@@ -4837,7 +4861,7 @@ function renderLateralJoin(params) {
4837
4861
  const json = lateralJsonOf({ dialect });
4838
4862
  let aggOrder = "";
4839
4863
  if (json.order === "inAggregate" && join.agg === "jsonArray" && join.select.orderBy !== undefined && join.select.orderBy.length > 0) {
4840
- const projected = new Set(join.select.columns.filter((column) => column.transform === undefined && column.castReal !== true).map((column) => column.name));
4864
+ const projected = new Set(join.select.columns.filter((column) => column.transform === undefined && column.castReal !== true).map((column) => column.name).concat((join.select.scalarSubqueries ?? []).map((subquery) => subquery.alias)));
4841
4865
  const terms = join.select.orderBy;
4842
4866
  const columnTerms = terms.filter((term) => ("column" in term));
4843
4867
  if (columnTerms.length === terms.length && columnTerms.every((term) => projected.has(term.column.name))) {
@@ -4863,12 +4887,17 @@ function renderLateralJoin(params) {
4863
4887
  function renderCountSubquery(params) {
4864
4888
  const { dialect, subquery, collector } = params;
4865
4889
  const select = subquery.select;
4866
- if (select.columns.length !== 0 || select.selectExpr?.kind !== "countAll" || (select.aggregates?.length ?? 0) > 0 || select.rowNumber !== undefined || select.groupBy !== undefined || select.having !== undefined || select.limit !== undefined || select.offset !== undefined || select.distinctOn !== undefined || select.distinct !== undefined || select.rowLock !== undefined || select.countSubqueries !== undefined) {
4890
+ if (select.columns.length !== 0 || select.selectExpr?.kind !== "countAll" || (select.aggregates?.length ?? 0) > 0 || select.rowNumber !== undefined || select.groupBy !== undefined || select.having !== undefined || select.limit !== undefined || select.offset !== undefined || select.distinctOn !== undefined || select.distinct !== undefined || select.rowLock !== undefined || select.countSubqueries !== undefined || select.scalarSubqueries !== undefined) {
4867
4891
  throw internalError4({ message: "count subquery must be one ungrouped COUNT(*)" });
4868
4892
  }
4869
4893
  const inner = renderSelect({ dialect, statement: select, collector });
4870
4894
  return `(${inner.text}) AS ${dialect.quoteIdent({ name: subquery.alias })}`;
4871
4895
  }
4896
+ function renderScalarSubquery(params) {
4897
+ const { dialect, subquery, collector } = params;
4898
+ const value = `(${renderSelect({ dialect, statement: subquery.select, collector }).text})`;
4899
+ return subquery.coalesce === 0 ? `COALESCE(${value}, 0)` : value;
4900
+ }
4872
4901
  function applyReturning(params) {
4873
4902
  const { dialect, requested } = params;
4874
4903
  if (requested === undefined || requested.length === 0) {
@@ -4977,6 +5006,11 @@ function renderSelect(params) {
4977
5006
  columnList.push(renderCountSubquery({ dialect, subquery, collector }));
4978
5007
  }
4979
5008
  }
5009
+ if (statement.scalarSubqueries !== undefined) {
5010
+ for (const subquery of statement.scalarSubqueries) {
5011
+ columnList.push(`${renderScalarSubquery({ dialect, subquery, collector })} AS ${dialect.quoteIdent({ name: subquery.alias })}`);
5012
+ }
5013
+ }
4980
5014
  if (statement.rowNumber !== undefined) {
4981
5015
  const rowNumber = statement.rowNumber;
4982
5016
  if (rowNumber.partitionBy.length === 0 && rowNumber.orderBy.length === 0) {
@@ -6380,4 +6414,4 @@ export {
6380
6414
  toPgArrayLiteral
6381
6415
  };
6382
6416
 
6383
- //# debugId=DB7441716F57EC8864756E2164756E21
6417
+ //# debugId=7A09C0BE2E07A46664756E2164756E21