@atscript/db-sql-tools 0.1.139 → 0.1.141

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.cjs CHANGED
@@ -642,9 +642,12 @@ function viewAggExpr(dialect, c, resolveFieldRef) {
642
642
  * Builds a CREATE VIEW statement from a view plan and column mappings.
643
643
  *
644
644
  * Joins render in declaration order — `JOIN` (inner, the default) or
645
- * `LEFT JOIN` for `kind: "left"`. Column mappings carry PHYSICAL source
646
- * names; `resolveFieldRef` renders predicate refs (join ON, WHERE, HAVING
647
- * fallbacks) as `"table"."column"`.
645
+ * `LEFT JOIN` for `kind: "left"`; a `@db.alias` target renders as
646
+ * `JOIN "table" AS "Alias"` and is addressed by the alias everywhere else
647
+ * (since 0.1.141). Column mappings carry PHYSICAL source names (the alias
648
+ * name for an aliased join); `resolveFieldRef` renders predicate refs (join
649
+ * ON, WHERE, HAVING fallbacks) as `"table"."column"`. The entry table and a
650
+ * join target may be views.
648
651
  */
649
652
  function buildCreateView(dialect, viewName, plan, columns, resolveFieldRef) {
650
653
  const selectCols = columns.map((c) => {
@@ -654,7 +657,8 @@ function buildCreateView(dialect, viewName, plan, columns, resolveFieldRef) {
654
657
  for (const join of plan.joins) {
655
658
  const onClause = queryNodeToSql(join.condition, resolveFieldRef);
656
659
  const keyword = join.kind === "left" ? "LEFT JOIN" : "JOIN";
657
- sql += ` ${keyword} ${dialect.quoteIdentifier(join.targetTable)} ON ${onClause}`;
660
+ const target = join.scope === join.targetTable ? dialect.quoteIdentifier(join.targetTable) : `${dialect.quoteIdentifier(join.targetTable)} AS ${dialect.quoteIdentifier(join.scope)}`;
661
+ sql += ` ${keyword} ${target} ON ${onClause}`;
658
662
  }
659
663
  if (plan.filter) {
660
664
  const whereClause = queryNodeToSql(plan.filter, resolveFieldRef);
@@ -762,16 +766,18 @@ function buildSelect(dialect, table, where, controls) {
762
766
  */
763
767
  const SQL_DEFAULT = Symbol("SQL_DEFAULT");
764
768
  /**
765
- * The columns a full replace assigns on a SQL adapter: every non-ignored
766
- * descriptor (the same set `CREATE TABLE` emits) except the primary key —
767
- * the row is matched by the filter, and an omitted PK must never be nulled
768
- * or re-defaulted. Static value defaults are filled SDK-side before the
769
- * adapter sees the row, so only native function defaults are flagged.
769
+ * The columns a full replace assigns on a SQL adapter: the readable's
770
+ * `storedDescriptors` (non-ignored, not derived — a generated column is
771
+ * never assigned) except the primary key — the row is matched by the filter,
772
+ * and an omitted PK must never be nulled or re-defaulted. Static value
773
+ * defaults are filled SDK-side before the adapter sees the row, so only
774
+ * native function defaults are flagged. Ignored and derived descriptors in
775
+ * `fields` are skipped, so `fieldDescriptors` works too.
770
776
  */
771
777
  function replaceColumnsFor(fields, nativeFns) {
772
778
  const out = [];
773
779
  for (const fd of fields) {
774
- if (fd.ignored || fd.isPrimaryKey) continue;
780
+ if (fd.ignored || fd.isPrimaryKey || fd.derived) continue;
775
781
  const def = fd.defaultValue;
776
782
  out.push({
777
783
  name: fd.physicalName,
@@ -868,6 +874,22 @@ function buildDelete(dialect, table, where, limit) {
868
874
  });
869
875
  }
870
876
  /**
877
+ * The expression a `@db.column.derived` column is generated from: the
878
+ * dialect's typed {@link SqlDialect.jsonExtract} over the (unqualified,
879
+ * quoted) JSON source column — the same extraction a view's JSON leaf uses,
880
+ * so both read a leaf identically. Parameter-free by contract; rendered in
881
+ * `CREATE TABLE` / `ADD COLUMN` as `GENERATED ALWAYS AS (<expr>)`.
882
+ *
883
+ * @throws when `field` is not derived or the dialect has no `jsonExtract`.
884
+ * @since 0.1.141
885
+ */
886
+ function derivedColumnExpr(dialect, field) {
887
+ const derived = field.derived;
888
+ if (!derived) throw new Error(`Column "${field.physicalName}" is not a derived column`);
889
+ if (!dialect.jsonExtract) throw new Error(`Derived column "${field.physicalName}": JSON extraction is not supported by this adapter`);
890
+ return dialect.jsonExtract(dialect.quoteIdentifier(derived.sourceColumn), derived.jsonPath, derived.type);
891
+ }
892
+ /**
871
893
  * Builds a column projection (SELECT clause fields).
872
894
  */
873
895
  function buildProjection(dialect, select) {
@@ -926,6 +948,7 @@ exports.buildWhere = buildWhere;
926
948
  exports.createFilterVisitor = createFilterVisitor;
927
949
  exports.defaultValueForType = defaultValueForType;
928
950
  exports.defaultValueToSqlLiteral = defaultValueToSqlLiteral;
951
+ exports.derivedColumnExpr = derivedColumnExpr;
929
952
  exports.fillReplacePayload = fillReplacePayload;
930
953
  exports.finalizeParams = finalizeParams;
931
954
  exports.geoWindowFromControls = geoWindowFromControls;
package/dist/index.d.cts CHANGED
@@ -182,9 +182,12 @@ declare function renameGeoDistance(row: Record<string, unknown>): Record<string,
182
182
  * Builds a CREATE VIEW statement from a view plan and column mappings.
183
183
  *
184
184
  * Joins render in declaration order — `JOIN` (inner, the default) or
185
- * `LEFT JOIN` for `kind: "left"`. Column mappings carry PHYSICAL source
186
- * names; `resolveFieldRef` renders predicate refs (join ON, WHERE, HAVING
187
- * fallbacks) as `"table"."column"`.
185
+ * `LEFT JOIN` for `kind: "left"`; a `@db.alias` target renders as
186
+ * `JOIN "table" AS "Alias"` and is addressed by the alias everywhere else
187
+ * (since 0.1.141). Column mappings carry PHYSICAL source names (the alias
188
+ * name for an aliased join); `resolveFieldRef` renders predicate refs (join
189
+ * ON, WHERE, HAVING fallbacks) as `"table"."column"`. The entry table and a
190
+ * join target may be views.
188
191
  */
189
192
  declare function buildCreateView(dialect: SqlDialect, viewName: string, plan: TViewPlan, columns: TViewColumnMapping[], resolveFieldRef: (ref: AtscriptQueryFieldRef) => string): string;
190
193
  //#endregion
@@ -230,11 +233,13 @@ interface TReplaceColumn {
230
233
  useDefault: boolean;
231
234
  }
232
235
  /**
233
- * The columns a full replace assigns on a SQL adapter: every non-ignored
234
- * descriptor (the same set `CREATE TABLE` emits) except the primary key —
235
- * the row is matched by the filter, and an omitted PK must never be nulled
236
- * or re-defaulted. Static value defaults are filled SDK-side before the
237
- * adapter sees the row, so only native function defaults are flagged.
236
+ * The columns a full replace assigns on a SQL adapter: the readable's
237
+ * `storedDescriptors` (non-ignored, not derived — a generated column is
238
+ * never assigned) except the primary key — the row is matched by the filter,
239
+ * and an omitted PK must never be nulled or re-defaulted. Static value
240
+ * defaults are filled SDK-side before the adapter sees the row, so only
241
+ * native function defaults are flagged. Ignored and derived descriptors in
242
+ * `fields` are skipped, so `fieldDescriptors` works too.
238
243
  */
239
244
  declare function replaceColumnsFor(fields: readonly TDbFieldMeta[], nativeFns: ReadonlySet<TDbDefaultFn>): TReplaceColumn[];
240
245
  /**
@@ -268,6 +273,17 @@ declare function buildUpdate(dialect: SqlDialect, table: string, data: Record<st
268
273
  * Builds a DELETE ... WHERE statement with optional LIMIT.
269
274
  */
270
275
  declare function buildDelete(dialect: SqlDialect, table: string, where: TSqlFragment, limit?: number): TSqlFragment;
276
+ /**
277
+ * The expression a `@db.column.derived` column is generated from: the
278
+ * dialect's typed {@link SqlDialect.jsonExtract} over the (unqualified,
279
+ * quoted) JSON source column — the same extraction a view's JSON leaf uses,
280
+ * so both read a leaf identically. Parameter-free by contract; rendered in
281
+ * `CREATE TABLE` / `ADD COLUMN` as `GENERATED ALWAYS AS (<expr>)`.
282
+ *
283
+ * @throws when `field` is not derived or the dialect has no `jsonExtract`.
284
+ * @since 0.1.141
285
+ */
286
+ declare function derivedColumnExpr(dialect: SqlDialect, field: TDbFieldMeta): string;
271
287
  /**
272
288
  * Builds a column projection (SELECT clause fields).
273
289
  */
@@ -368,4 +384,4 @@ declare function parseRegexString(value: unknown): {
368
384
  flags: string;
369
385
  };
370
386
  //#endregion
371
- export { AGG_FN_SQL, EMPTY_AND, EMPTY_OR, GEO_DISTANCE_ALIAS, SQL_DEFAULT, type SqlDialect, type TFilterVisitorOptions, type TGeoCircle, type TGeoWindow, type TReplaceColumn, type TSqlFragment, buildAggregateCount, buildAggregateSelect, buildCreateView, buildDelete, buildGeoSearchCount, buildGeoSearchSelect, buildInsert, buildInsertMany, buildProjection, buildSelect, buildUpdate, buildWhere, createFilterVisitor, defaultValueForType, defaultValueToSqlLiteral, fillReplacePayload, finalizeParams, geoWindowFromControls, groupKeySql, insertManyColumns, jsonDollarPath, normalizeGeoPointValue, parseRegexString, queryNodeToSql, queryOpToSql, quotedJsonPathSegments, refActionToSql, renameGeoDistance, replaceColumnsFor, sqlStringLiteral, sqlTimeZoneLiteral, toSqlValue };
387
+ export { AGG_FN_SQL, EMPTY_AND, EMPTY_OR, GEO_DISTANCE_ALIAS, SQL_DEFAULT, type SqlDialect, type TFilterVisitorOptions, type TGeoCircle, type TGeoWindow, type TReplaceColumn, type TSqlFragment, buildAggregateCount, buildAggregateSelect, buildCreateView, buildDelete, buildGeoSearchCount, buildGeoSearchSelect, buildInsert, buildInsertMany, buildProjection, buildSelect, buildUpdate, buildWhere, createFilterVisitor, defaultValueForType, defaultValueToSqlLiteral, derivedColumnExpr, fillReplacePayload, finalizeParams, geoWindowFromControls, groupKeySql, insertManyColumns, jsonDollarPath, normalizeGeoPointValue, parseRegexString, queryNodeToSql, queryOpToSql, quotedJsonPathSegments, refActionToSql, renameGeoDistance, replaceColumnsFor, sqlStringLiteral, sqlTimeZoneLiteral, toSqlValue };
package/dist/index.d.mts CHANGED
@@ -182,9 +182,12 @@ declare function renameGeoDistance(row: Record<string, unknown>): Record<string,
182
182
  * Builds a CREATE VIEW statement from a view plan and column mappings.
183
183
  *
184
184
  * Joins render in declaration order — `JOIN` (inner, the default) or
185
- * `LEFT JOIN` for `kind: "left"`. Column mappings carry PHYSICAL source
186
- * names; `resolveFieldRef` renders predicate refs (join ON, WHERE, HAVING
187
- * fallbacks) as `"table"."column"`.
185
+ * `LEFT JOIN` for `kind: "left"`; a `@db.alias` target renders as
186
+ * `JOIN "table" AS "Alias"` and is addressed by the alias everywhere else
187
+ * (since 0.1.141). Column mappings carry PHYSICAL source names (the alias
188
+ * name for an aliased join); `resolveFieldRef` renders predicate refs (join
189
+ * ON, WHERE, HAVING fallbacks) as `"table"."column"`. The entry table and a
190
+ * join target may be views.
188
191
  */
189
192
  declare function buildCreateView(dialect: SqlDialect, viewName: string, plan: TViewPlan, columns: TViewColumnMapping[], resolveFieldRef: (ref: AtscriptQueryFieldRef) => string): string;
190
193
  //#endregion
@@ -230,11 +233,13 @@ interface TReplaceColumn {
230
233
  useDefault: boolean;
231
234
  }
232
235
  /**
233
- * The columns a full replace assigns on a SQL adapter: every non-ignored
234
- * descriptor (the same set `CREATE TABLE` emits) except the primary key —
235
- * the row is matched by the filter, and an omitted PK must never be nulled
236
- * or re-defaulted. Static value defaults are filled SDK-side before the
237
- * adapter sees the row, so only native function defaults are flagged.
236
+ * The columns a full replace assigns on a SQL adapter: the readable's
237
+ * `storedDescriptors` (non-ignored, not derived — a generated column is
238
+ * never assigned) except the primary key — the row is matched by the filter,
239
+ * and an omitted PK must never be nulled or re-defaulted. Static value
240
+ * defaults are filled SDK-side before the adapter sees the row, so only
241
+ * native function defaults are flagged. Ignored and derived descriptors in
242
+ * `fields` are skipped, so `fieldDescriptors` works too.
238
243
  */
239
244
  declare function replaceColumnsFor(fields: readonly TDbFieldMeta[], nativeFns: ReadonlySet<TDbDefaultFn>): TReplaceColumn[];
240
245
  /**
@@ -268,6 +273,17 @@ declare function buildUpdate(dialect: SqlDialect, table: string, data: Record<st
268
273
  * Builds a DELETE ... WHERE statement with optional LIMIT.
269
274
  */
270
275
  declare function buildDelete(dialect: SqlDialect, table: string, where: TSqlFragment, limit?: number): TSqlFragment;
276
+ /**
277
+ * The expression a `@db.column.derived` column is generated from: the
278
+ * dialect's typed {@link SqlDialect.jsonExtract} over the (unqualified,
279
+ * quoted) JSON source column — the same extraction a view's JSON leaf uses,
280
+ * so both read a leaf identically. Parameter-free by contract; rendered in
281
+ * `CREATE TABLE` / `ADD COLUMN` as `GENERATED ALWAYS AS (<expr>)`.
282
+ *
283
+ * @throws when `field` is not derived or the dialect has no `jsonExtract`.
284
+ * @since 0.1.141
285
+ */
286
+ declare function derivedColumnExpr(dialect: SqlDialect, field: TDbFieldMeta): string;
271
287
  /**
272
288
  * Builds a column projection (SELECT clause fields).
273
289
  */
@@ -368,4 +384,4 @@ declare function parseRegexString(value: unknown): {
368
384
  flags: string;
369
385
  };
370
386
  //#endregion
371
- export { AGG_FN_SQL, EMPTY_AND, EMPTY_OR, GEO_DISTANCE_ALIAS, SQL_DEFAULT, type SqlDialect, type TFilterVisitorOptions, type TGeoCircle, type TGeoWindow, type TReplaceColumn, type TSqlFragment, buildAggregateCount, buildAggregateSelect, buildCreateView, buildDelete, buildGeoSearchCount, buildGeoSearchSelect, buildInsert, buildInsertMany, buildProjection, buildSelect, buildUpdate, buildWhere, createFilterVisitor, defaultValueForType, defaultValueToSqlLiteral, fillReplacePayload, finalizeParams, geoWindowFromControls, groupKeySql, insertManyColumns, jsonDollarPath, normalizeGeoPointValue, parseRegexString, queryNodeToSql, queryOpToSql, quotedJsonPathSegments, refActionToSql, renameGeoDistance, replaceColumnsFor, sqlStringLiteral, sqlTimeZoneLiteral, toSqlValue };
387
+ export { AGG_FN_SQL, EMPTY_AND, EMPTY_OR, GEO_DISTANCE_ALIAS, SQL_DEFAULT, type SqlDialect, type TFilterVisitorOptions, type TGeoCircle, type TGeoWindow, type TReplaceColumn, type TSqlFragment, buildAggregateCount, buildAggregateSelect, buildCreateView, buildDelete, buildGeoSearchCount, buildGeoSearchSelect, buildInsert, buildInsertMany, buildProjection, buildSelect, buildUpdate, buildWhere, createFilterVisitor, defaultValueForType, defaultValueToSqlLiteral, derivedColumnExpr, fillReplacePayload, finalizeParams, geoWindowFromControls, groupKeySql, insertManyColumns, jsonDollarPath, normalizeGeoPointValue, parseRegexString, queryNodeToSql, queryOpToSql, quotedJsonPathSegments, refActionToSql, renameGeoDistance, replaceColumnsFor, sqlStringLiteral, sqlTimeZoneLiteral, toSqlValue };
package/dist/index.mjs CHANGED
@@ -641,9 +641,12 @@ function viewAggExpr(dialect, c, resolveFieldRef) {
641
641
  * Builds a CREATE VIEW statement from a view plan and column mappings.
642
642
  *
643
643
  * Joins render in declaration order — `JOIN` (inner, the default) or
644
- * `LEFT JOIN` for `kind: "left"`. Column mappings carry PHYSICAL source
645
- * names; `resolveFieldRef` renders predicate refs (join ON, WHERE, HAVING
646
- * fallbacks) as `"table"."column"`.
644
+ * `LEFT JOIN` for `kind: "left"`; a `@db.alias` target renders as
645
+ * `JOIN "table" AS "Alias"` and is addressed by the alias everywhere else
646
+ * (since 0.1.141). Column mappings carry PHYSICAL source names (the alias
647
+ * name for an aliased join); `resolveFieldRef` renders predicate refs (join
648
+ * ON, WHERE, HAVING fallbacks) as `"table"."column"`. The entry table and a
649
+ * join target may be views.
647
650
  */
648
651
  function buildCreateView(dialect, viewName, plan, columns, resolveFieldRef) {
649
652
  const selectCols = columns.map((c) => {
@@ -653,7 +656,8 @@ function buildCreateView(dialect, viewName, plan, columns, resolveFieldRef) {
653
656
  for (const join of plan.joins) {
654
657
  const onClause = queryNodeToSql(join.condition, resolveFieldRef);
655
658
  const keyword = join.kind === "left" ? "LEFT JOIN" : "JOIN";
656
- sql += ` ${keyword} ${dialect.quoteIdentifier(join.targetTable)} ON ${onClause}`;
659
+ const target = join.scope === join.targetTable ? dialect.quoteIdentifier(join.targetTable) : `${dialect.quoteIdentifier(join.targetTable)} AS ${dialect.quoteIdentifier(join.scope)}`;
660
+ sql += ` ${keyword} ${target} ON ${onClause}`;
657
661
  }
658
662
  if (plan.filter) {
659
663
  const whereClause = queryNodeToSql(plan.filter, resolveFieldRef);
@@ -761,16 +765,18 @@ function buildSelect(dialect, table, where, controls) {
761
765
  */
762
766
  const SQL_DEFAULT = Symbol("SQL_DEFAULT");
763
767
  /**
764
- * The columns a full replace assigns on a SQL adapter: every non-ignored
765
- * descriptor (the same set `CREATE TABLE` emits) except the primary key —
766
- * the row is matched by the filter, and an omitted PK must never be nulled
767
- * or re-defaulted. Static value defaults are filled SDK-side before the
768
- * adapter sees the row, so only native function defaults are flagged.
768
+ * The columns a full replace assigns on a SQL adapter: the readable's
769
+ * `storedDescriptors` (non-ignored, not derived — a generated column is
770
+ * never assigned) except the primary key — the row is matched by the filter,
771
+ * and an omitted PK must never be nulled or re-defaulted. Static value
772
+ * defaults are filled SDK-side before the adapter sees the row, so only
773
+ * native function defaults are flagged. Ignored and derived descriptors in
774
+ * `fields` are skipped, so `fieldDescriptors` works too.
769
775
  */
770
776
  function replaceColumnsFor(fields, nativeFns) {
771
777
  const out = [];
772
778
  for (const fd of fields) {
773
- if (fd.ignored || fd.isPrimaryKey) continue;
779
+ if (fd.ignored || fd.isPrimaryKey || fd.derived) continue;
774
780
  const def = fd.defaultValue;
775
781
  out.push({
776
782
  name: fd.physicalName,
@@ -867,6 +873,22 @@ function buildDelete(dialect, table, where, limit) {
867
873
  });
868
874
  }
869
875
  /**
876
+ * The expression a `@db.column.derived` column is generated from: the
877
+ * dialect's typed {@link SqlDialect.jsonExtract} over the (unqualified,
878
+ * quoted) JSON source column — the same extraction a view's JSON leaf uses,
879
+ * so both read a leaf identically. Parameter-free by contract; rendered in
880
+ * `CREATE TABLE` / `ADD COLUMN` as `GENERATED ALWAYS AS (<expr>)`.
881
+ *
882
+ * @throws when `field` is not derived or the dialect has no `jsonExtract`.
883
+ * @since 0.1.141
884
+ */
885
+ function derivedColumnExpr(dialect, field) {
886
+ const derived = field.derived;
887
+ if (!derived) throw new Error(`Column "${field.physicalName}" is not a derived column`);
888
+ if (!dialect.jsonExtract) throw new Error(`Derived column "${field.physicalName}": JSON extraction is not supported by this adapter`);
889
+ return dialect.jsonExtract(dialect.quoteIdentifier(derived.sourceColumn), derived.jsonPath, derived.type);
890
+ }
891
+ /**
870
892
  * Builds a column projection (SELECT clause fields).
871
893
  */
872
894
  function buildProjection(dialect, select) {
@@ -905,4 +927,4 @@ function parseRegexString(value) {
905
927
  };
906
928
  }
907
929
  //#endregion
908
- export { AGG_FN_SQL, EMPTY_AND, EMPTY_OR, GEO_DISTANCE_ALIAS, SQL_DEFAULT, buildAggregateCount, buildAggregateSelect, buildCreateView, buildDelete, buildGeoSearchCount, buildGeoSearchSelect, buildInsert, buildInsertMany, buildProjection, buildSelect, buildUpdate, buildWhere, createFilterVisitor, defaultValueForType, defaultValueToSqlLiteral, fillReplacePayload, finalizeParams, geoWindowFromControls, groupKeySql, insertManyColumns, jsonDollarPath, normalizeGeoPointValue, parseRegexString, queryNodeToSql, queryOpToSql, quotedJsonPathSegments, refActionToSql, renameGeoDistance, replaceColumnsFor, sqlStringLiteral, sqlTimeZoneLiteral, toSqlValue };
930
+ export { AGG_FN_SQL, EMPTY_AND, EMPTY_OR, GEO_DISTANCE_ALIAS, SQL_DEFAULT, buildAggregateCount, buildAggregateSelect, buildCreateView, buildDelete, buildGeoSearchCount, buildGeoSearchSelect, buildInsert, buildInsertMany, buildProjection, buildSelect, buildUpdate, buildWhere, createFilterVisitor, defaultValueForType, defaultValueToSqlLiteral, derivedColumnExpr, fillReplacePayload, finalizeParams, geoWindowFromControls, groupKeySql, insertManyColumns, jsonDollarPath, normalizeGeoPointValue, parseRegexString, queryNodeToSql, queryOpToSql, quotedJsonPathSegments, refActionToSql, renameGeoDistance, replaceColumnsFor, sqlStringLiteral, sqlTimeZoneLiteral, toSqlValue };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atscript/db-sql-tools",
3
- "version": "0.1.139",
3
+ "version": "0.1.141",
4
4
  "description": "Shared SQL builder utilities for @atscript database adapters.",
5
5
  "keywords": [
6
6
  "atscript",
@@ -38,11 +38,11 @@
38
38
  },
39
39
  "devDependencies": {
40
40
  "@uniqu/core": "^0.1.11",
41
- "unplugin-atscript": "^0.1.92"
41
+ "unplugin-atscript": "^0.1.95"
42
42
  },
43
43
  "peerDependencies": {
44
44
  "@uniqu/core": "^0.1.11",
45
- "@atscript/db": "^0.1.139"
45
+ "@atscript/db": "^0.1.141"
46
46
  },
47
47
  "scripts": {
48
48
  "build": "vp pack",