Repository navigation
Commit e2456aa
authored
feat(reporting): lower view-backed object.report to SQL views and read it in every port (FR-044 Plan 2) (#399)
* feat(metadata): reportShape, the derived fields of an object.report (FR-044)
* feat(codegen-ts): time-grain and relative-date SQL per dialect (FR-044)
* refactor(codegen-ts): extract walkViaPath from buildJoinTree
* feat(codegen-ts): extractReportSpec, an object.report as a view spec (FR-044)
* feat(codegen-ts): emitReportViewDdl for Postgres, SQLite and MySQL (FR-044)
* feat(codegen-ts): lower view-backed reports through buildProjectionViews; canonical reports and shape artifact (FR-044)
* fix(integration-tests): keep the report-shapes artifact outside the canonical metadata directory (FR-044)
* test(integration): report views converge and return the expected rows on Postgres and SQLite (FR-044)
Postgres: convergence x3, the Task 10 values read straight off the six canonical
views, UTC buckets under a New York session zone, empty-group row, INNER vs LEFT OUTER
join, relative window, and a changed report taking the drop-and-create path.
SQLite: convergence (verbatim text), the same values, week boundary, quarter and year
grains, relative window, tuple distinct count via json_array, and the hour bucket's
literal pinned to the .000Z spelling the TS adapters store. No emitter change.
* feat(codegen-ts): MySQL report view SQL through buildReportViews; recipe (FR-044)
A MySQL 8.4 value test creates the six canonical report views under the default
sql_mode (ONLY_FULL_GROUP_BY asserted, not assumed) and pins the Task 10 rows, the
2/3 ratio at 0.6667 (Review Focus 5), the bigint type of a SIGNED-cast sum, and the
Table D grain and Table E relative-date values. No emitter change was needed.
docs/recipes/mysql.md gains a Reports section (meta migrate still does not own a
MySQL schema); the skill reference and the regenerated agent-context goldens follow.
* feat(runtime-ts): ObjectManager reads a view-backed report (FR-044)
* fix(docs): MySQL report recipe declares a managed view source; run the recipe's declaration in the test (FR-044)
buildReportViews skips a report whose source.rdb is @Unmanaged, so the recipe's
@Unmanaged: true declaration returned no SQL. The recipe now declares @kind: view only
(meta migrate never targets MySQL, so nothing manages the view either way) and says that an
unmanaged source is skipped. The MySQL test now reads the recipe's own fenced declaration,
runs the recipe's loadDirectory + buildReportViews shape, and asserts one view, and none
once @Unmanaged is added. Tables and views are created in beforeAll after dropping stale
ones, so tests run alone (-t) and rerun against a persistent server.
* fix(runtime-ts): the report read model reads the same source the lowering names (FR-044)
* test(persistence-conformance): six report scenarios (FR-044)
Six shared read scenarios over the view-backed reports in the canonical model:
grouped measures, totals, totals over an empty table, time grains, hour and
week buckets, and a relative-date filter. List and count only; no get, no write.
The TypeScript runner discovers them from queries/ with no list to update and
passes all 33 query scenarios. Persistence corpus count 33 -> 39 (27 -> 33 query)
in docs/CONFORMANCE.md; README gains a Report scenarios subsection. Other ports'
persistence lanes are red on these until their own tasks.
* feat(csharp): report shape and generated keyless row for a view-backed report (FR-044)
* feat(python): report shape and ObjectManager read of a view-backed report (FR-044)
* fix(csharp): refuse a report whose derived field name equals its row class name (FR-044)
* feat(java): report shape and OMDB read of a view-backed report (FR-044)
* docs(reporting): teach reports in the authoring skill; document the lowering (FR-044)
* fix(docs): keep the agent schema page byte-identical for a model with no report view; correct reporting doc details (FR-044)
* feat(kotlin): Exposed table for a view-backed report (FR-044)
KotlinExposedTableGenerator emits the read-only Exposed table of an
object.report that declares a source.rdb @kind: view, with one column per
derived field (contract Table B) taken from the JVM ReportShape. A report
with no view, or over a kind the lowering skips, still generates nothing,
and every other Kotlin generator still skips reports.
A report table binds by literal (no names artifact is emitted for a
report), types an enum column by the enum of the entity the dimension
reads, and reads a derived decimal with no declared precision at 38,18 so
Exposed does not round a ratio to four places. A derived field named after
a Kotlin keyword, or two that land on one column property, is a generation
error naming the report and the item.
Also reserves schemaName as an Exposed Table member in safeColumnProperty:
a column property of that name did not compile.
Six hand-written reference tables put the Kotlin persistence lane on the
six shared report scenarios.
* fix(kotlin): document report table generation and its refusals; reserve Exposed 1.x table members (FR-044)
* fix(reporting): resolve report references as the loader does; refuse what cannot be lowered (FR-044)
The lowering and the read shape disagreed with validateReporting about what a
loadable model means. Each case loaded clean and then failed, or was silently
wrong, at migrate or at read.
- @Of and @via resolve in the package of the entity that DECLARES the dimension
or measure, not the @from entity's. A member inherited from a base in another
package now resolves, and a same-named entity in the report's package can no
longer capture the reference and mistype the column.
- Without @via the field is read from @from, and a @via walk starts at @from,
as the loader's does.
- A dotted @measures item (Sale.total, loader rule R3) names the measure by its
last segment. One canonical report now uses the dotted form; schema.postgres.sql
and report-shapes.json are byte-identical.
- A report is classified (skip / @SQL / derive) by the same source its view is
named by and the runtime reads: primary, else first. A replica declared first
no longer decides it. The report-shapes generator uses the same selector.
- A derived report @from a TPH subtype is refused: the subtype shares its base's
table, so the view aggregated every subtype's rows. An @SQL or @Unmanaged
report is the author's body and is not refused.
- A @via hop with no identity.reference behind it says which hop and what it
needs. An empty in list, a time dimension without a grain and a grain outside
the closed set are refused by name instead of reaching the DDL.
* test(reporting): engine tests read real views and stop touching what they do not own (FR-044)
- MySQL: drop only the views and tables this file creates, by name, instead of
every view in the database (a shared test database lost unrelated views).
- Postgres: changing a report runs a plain migrate with no dropView allowance,
so a broken drop-and-create pairing would fail the test.
- The NULL-component tuple count is read through a lowered view over nullable
columns on Postgres and SQLite, not through hand-written SQL.
- A decimal sum and a double / float sum are created and read on Postgres, with
the view's column types asserted.
- Every inline model asserts the loader returned no errors.
- The SQLite inet residue is narrowed to the two inet columns.
* docs(reporting): a @via hop needs a declared foreign key; state the known limits (FR-044)
- The documented @via example could not be lowered: its relationship had no
identity.reference behind it. The example now declares one, and the feature doc,
authoring skill rule 2 and the skill reference say a hop needs it.
- Known limits: a derived report from a TPH subtype is refused (declare it from
the base with a filter on the discriminator); an abstract view-backed report
and non-view source kinds get no C# row or Kotlin table; a dimension over a
field.object is not supported across ports.
- Which source decides when a report declares several (primary, else first), the
dotted @measures form, and that count counts non-null @Of.
- CHANGELOG [Unreleased] and the agent-context goldens follow.
* fix(java): resolve report references as the loader does; refuse a report over a field.object (FR-044)
The report shape resolved a bare @Of in the @from entity's package. The loader
resolves it in the package of the entity that DECLARES the dimension or measure,
so a member inherited from a base in another package either failed to resolve or
was typed from a same-named decoy. The shape now follows the loader: declaring
package, the named entity must be @from or an ancestor, and without @via the
field is read from @from. A dotted @measures item (Sale.total) names the measure
by its last segment. A time dimension item with no grain, or one outside the
closed set, does not resolve.
OMDB: a derived field over a field.object is refused by name when the read model
is built (it was a NullPointerException on read). OQL with a report result class
builds rows from the read model. getObjectRef leaves an object with no metadata
to the base method. A projection whose view is named by @view now has a read
mapping: the view name is the source's physical name, one rule for projections
and reports.
* fix(kotlin): report table uses the shape's own resolution; refuse a report over a field.object (FR-044)
The enum class of a report column is taken from ReportShape.ofEntity, so the
generator restates nothing about packages or @via. gen fails, naming the report
and the dimension or measure, when a derived field reads a field.object. Tests
cover a member inherited across packages (with and without a same-named decoy),
a dotted @measures item, and an abstract view-backed report (generates nothing).
* fix(csharp): resolve report references as the loader does; refuse a report over a field.object (FR-044)
ReportShapes resolves @Of in the declaring entity's package, checks the named
entity is @from or an ancestor, and reads the field from @from when there is no
@via. A dotted @measures item names the measure by its last segment. A report
row with a dimension over a field.object silently lost that property; gen now
refuses it by name. An abstract view-backed report generates nothing (tested).
* fix(python): resolve report references as the loader does; test the Table B rows and carry rules (FR-044)
report_shape resolves @Of in the declaring entity's package, checks the named
entity is @from or an ancestor, and reads the field from @from when there is no
@via. A dotted @measures item names the measure by its last segment. The shapes
test helper names the view by the read model's rule (primary, else first). New
direct tests for sum/avg/min/max by @Of subtype and for what a derived field
carries from its type source. ADR-0039 comments on the own-only reads.
* docs(changelog): report reference fixes in the four ports, the field.object refusal, OMDB @view projections (FR-044)
* docs(reporting): state the field.object limit per port (FR-044)
Java OMDB (on read), Kotlin gen and C# gen now refuse a report whose column is
typed by a field.object, naming the report and the item; the TypeScript and
Python runtimes read it as parsed JSON. The Known limits entry and the skill
reference said the other ports were not gated for it.
* fix(codegen-ts): a @via report lowers in a package whose name contains a dot; document the redeclared-field limit (FR-044)
The @via walk was handed the @from entity's resolution key as its head, and the
walk splits on every dot, so a report in a package such as com.acme resolved no
hop and was refused with a false 'no foreign key' message. The head is now the
entity's short name, resolved in its own package, which is still that entity
when another package has one of the same name.
Known limits gains the quiet form of the @via rule: when the reached subtype
redeclares the field and @Of names the base, the view reads the base's column.
* no-mistakes(document): Fix stale FR-044 roadmap status and C# port codegen inventory for report lowering1 parent c8640ce commit e2456aa
132 files changed
Lines changed: 13892 additions & 344 deletions
File tree
- .claude/rules
- agent-context/skills
- metaobjects-authoring
- references
- metaobjects-codegen/references
- docs
- features
- ports
- recipes
- superpowers/plans
- fixtures
- agent-context-conformance
- java-kotlin-react-tanstack/expected/.claude/skills/metaobjects-authoring
- references
- java-react/expected/.claude/skills/metaobjects-authoring
- references
- python/expected/.claude/skills/metaobjects-authoring
- references
- ts-react-tanstack/expected/.claude/skills
- metaobjects-authoring
- references
- metaobjects-codegen/references
- ts-requirements/expected/.claude/skills
- metaobjects-authoring
- references
- metaobjects-codegen/references
- codegen-noop/reporting
- generator-registry-conformance
- persistence-conformance
- canonical
- queries
- server
- csharp
- MetaObjects.Codegen.Tests
- MetaObjects.Codegen
- Generators
- MetaObjects.Conformance.Tests
- MetaObjects.IntegrationTests/Generated
- MetaObjects
- Core/Reporting
- Loader
- java
- codegen-base/src/main/java/com/metaobjects/generator/util
- codegen-kotlin-exposed1x-check/src/test/kotlin/com/metaobjects/generator/kotlin/exposed1x
- codegen-kotlin/src
- main/kotlin/com/metaobjects/generator/kotlin
- test/kotlin/com/metaobjects/generator/kotlin
- integration-tests-kotlin/src/test/kotlin/com/metaobjects/integration/kotlin
- tables
- integration-tests/src/test/java/com/metaobjects/integration
- metadata/src
- main/java/com/metaobjects
- loader
- object
- reporting
- test/java/com/metaobjects/reporting
- omdb/src
- main/java/com/metaobjects/manager/db
- test
- java/com/metaobjects/manager/db
- resources
- python
- src/metaobjects
- meta/core/reporting
- runtime
- tests
- runtime
- typescript/packages
- cli/test/unit
- codegen-ts
- src
- generators
- projection
- test
- projection
- docs-site/src
- integration-tests
- src
- test
- metadata
- src
- core/reporting
- test
- runtime-ts
- src
- test
- spec
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
17 | 17 | | |
18 | 18 | | |
19 | 19 | | |
20 | | - | |
| 20 | + | |
21 | 21 | | |
22 | 22 | | |
23 | 23 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
82 | 82 | | |
83 | 83 | | |
84 | 84 | | |
85 | | - | |
| 85 | + | |
86 | 86 | | |
87 | 87 | | |
88 | 88 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
27 | 27 | | |
28 | 28 | | |
29 | 29 | | |
30 | | - | |
31 | | - | |
32 | | - | |
33 | | - | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
34 | 64 | | |
35 | 65 | | |
36 | 66 | | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
37 | 77 | | |
38 | 78 | | |
39 | 79 | | |
| |||
0 commit comments