Repository navigation
Commit c0c43e1
authored
* docs(plan): FR-044 zero rows from a dimension's entity and a default for an empty measure
Design for two additions to the 1.1 reporting vocabulary, in all five ports:
@spine on object.report (the report's rows come from a dimension's entity, so
a tuple with no fact rows still has a row) and @default on measure.aggregate
and measure.ratio (the integer a measure reads when it would be null).
The plan settles where each attribute lives and what was rejected, the loader
rules (R8, R9, M7, M8) and their error codes, the view SQL per dialect, what a
measure reads in a row with no facts, the derived shape's nullability and the
row type in every port, and filter and sort on the derived fields. The SQL
shapes were executed on Postgres 16, SQLite and MySQL 8.4.
The spec gains the ADR-0023 register amendment (3.2), requirements R8 and R9,
two mapping rows, one acceptance bullet and the parked list. metamodelVersion
stays 1.1. No code changes in this commit.
* feat(metamodel): register @spine on object.report and @default on measures (TypeScript)
* feat(metadata): validate @spine and a measure @default at load (TypeScript)
* fix(metadata): clearer @spine messages and one error per mistake (TypeScript)
* test(conformance): @spine and measure @default fixtures (2 positive, 8 error cases)
Two positive fixtures (reporting-spine-and-default, reporting-spine-inherited)
and the eight error fixtures of Table B, each the positive input with one
change and one expected error: R8 (to-many hop, owner not @from), R9 (no
dimensions, a time dimension over a fact column, a dimension through the
reference's name for the spine's join), M7 (@default on a count), M8
(@default on a max of a timestamp) and the type check (@default: 0.5).
attr.int and attr.long now refuse a fractional number in TypeScript: a
fractional number in any int/long attribute fails to load with
ERR_BAD_ATTR_VALUE (input with no valid meaning; it used to pass the
attribute type check unchanged). attr.double is unchanged. No fixture,
example or test model carried such a value.
Metamodel corpus count 364 -> 374. The registry coverage snapshot is
regenerated: spine and default are now exercised, and the stale `sortable`
entries on field.double and field.float are dropped (already exercised by the
api-contract projection fixtures). CAPABILITIES.json regenerates unchanged.
The C#, Java, Kotlin and Python conformance lanes are red on the new fixtures
until their own tasks.
* fix(metadata): refuse a fractional measure @default without changing attr.int (TypeScript)
Reverts the attr.int/attr.long tightening from the previous commit. It also
refused a fraction in validator @min/@max, which are registered attr.int but
documented as numeric values, so `validator.numeric @min: 0.5` over a double
field stopped loading. That changed released vocabulary, which this plan must
not do. A fractional number in an int/long attribute loads again as before.
A measure's @default is now checked in validateReporting instead: a number
that is not an integer on a measure.aggregate or measure.ratio is
ERR_BAD_ATTR_VALUE on the measure node, with the text "@default '<value>' is
not an integer. A measure's @default is a whole number (for example 0)." The
check runs first; when it fires, M7/M8 are skipped. A non-number stays the
attribute type check's error alone. error-measure-default-not-integer is
unchanged and still yields exactly one error.
The registry coverage snapshot gets back the two `sortable` entries
(field.decimal, field.float) that the regeneration had dropped, so the file
no longer differs from main.
* feat(metadata): a spine key and a defaulted measure are not nullable in a report's shape
* feat(codegen-ts): lower a report @spine and a measure @default to view SQL
FR-044 Table D in TypeScript (Postgres, SQLite/D1, MySQL). A @spine report
reads FROM the spine entity and walks the spine's hops back to @from, every
join LEFT OUTER, with the report scope ANDed onto the ON of the join that
introduces @from (no WHERE). Aliases are those of the same report without
@spine. A measure or ratio @default wraps its full expression in COALESCE
(a REAL literal on SQLite for a real measure). The extractor refuses a
spine that reaches an entity with no table or a TPH subtype, reports a hop
with no identity.reference as @spine's hop error, and refuses a dimension
not reached through the spine. A model declaring neither attribute emits
byte-identical SQL.
* test(persistence): canonical @spine and @default reports, value and idempotence tests
* test(persistence): @spine and @default read scenarios; describe them in meta docs
* test(api-contract): a @spine report with a defaulted measure in the report/ sub-corpus
* feat(csharp): @spine and measure @default
FR-044 zero rows and measure defaults, the C# port of the TypeScript
reference (Tables A, B and C).
- Register @spine (string) on object.report and @default (int) on
measure.aggregate and measure.ratio; byte-copy the two spec files.
MetaMeasure.DefaultValue() and ReportAccessors.ReportSpine().
- Loader rules R8 (the spine is D2's to-one walk, started at @from), R9
(every listed dimension is reached through the spine, hop names compared
as written), M7 (@default on a count) and M8 (@default on min/max of a
non-numeric field), with the TypeScript message texts. WalkToOneVia takes
a ToOneWalk (D2Walk / SpineWalk); every D2 message is unchanged. A
fractional @default is already refused by this port's generic attr.int
type check (ERR_BAD_ATTR_VALUE on the measure), so no second error is
added; it only skips M7/M8.
- ReportShape Table C: under @spine a dimension on the spine is required
when its @Of is @required or a primary-key column; a defaulted measure is
required. New ReportingViaHops / ReportSpineHops.
- No generator change: the keyless row types a defaulted measure as long /
decimal and the spine key as non-nullable. Regenerated the integration
fixtures (three new rows, AppDbContext additions).
- The report REST lane seeds every base table of seed.json in file order
and expects ProductRevenue (and the Product / Sale entities) routed.
Corpus counts: nine canonical reports, four inert reports.
* feat(java): @spine and measure @default
FR-044 zero rows and measure defaults, the Java port of the TypeScript
reference (Tables A, B and C). Kotlin rides on the loader and ReportShape.
- Register @spine (string) on object.report and @default (int) on
measure.aggregate and measure.ratio. MetaMeasure.getDefaultValue() and
ReportAccessors.reportSpine(). The registry manifest's null value type
for an attr named default is narrowed to field registrations, so a
measure's default prints int.
- Loader rules R8 (the spine is D2's to-one walk, started at @from), R9
(every listed dimension is reached through the spine, hop names compared
as written), M7 (@default on a count) and M8 (@default on min/max of a
non-numeric field), with the TypeScript message texts. walkToOneVia takes
a ToOneWalk (d2Walk / spineWalk); every D2 message is unchanged. A
fractional @default is already refused by this port's generic attr.int
parse (ERR_BAD_ATTR_VALUE on the measure, and the load stops there), so
no second error is added; a non-integer value only skips M7/M8.
- ReportShape Table C: under @spine a dimension on the spine is required
when its @Of is @required or a primary-key column; a defaulted measure is
required. New reportingViaHops / reportSpineHops.
- No generator change: the report DTO marks the spine key and a defaulted
measure @NotNull, as it does a count. OMDB reads a @spine view's empty
row.
- The report REST seam lane serves ProductRevenue; corpus counts: nine
canonical reports, four inert reports, sixteen REST scenarios.
* test(kotlin): @spine and measure @default through Exposed
FR-044 zero rows and measure defaults, the Kotlin side. Kotlin reads the
loader and ReportShape from Java, so no Kotlin generator changes: a spine
key and a defaulted measure are required in the shape, and a required
field is already a non-null Exposed column and data class property.
- KotlinReportTableGeneratorTest pins Table C/F: ProgramRoster's table
text; a defaulted sum, min and ratio are non-null in the table and the
row while the same measure without @default stays nullable; under
@spine the spine entity's key is non-null and without @spine the same
dimension is nullable.
- Three hand-written reference tables (ProgramRoster, ProgramLongWeeks,
FitnessTotalsFilled) for the persistence lane, which now runs the three
new report scenarios. KotlinCodegenMatchesReferenceTest also checks every
report reference table against the same expectations (view, columns in
order, nullability), so a reference cannot drift from the generator.
- The report/ REST seam lane serves ProductRevenue (sixteen scenarios);
the inert test counts four reports, ProgramCatalogue sourceless.
* feat(python): @spine and measure @default
FR-044 zero rows and measure defaults, the Python port of the TypeScript
reference (Tables A, B and C).
- Register @spine (string) on object.report and @default (int) on
measure.aggregate and measure.ratio; byte-copy the two spec files.
MetaMeasure.default_value() (refuses bool) and report_spine().
- Loader rules R8 (the spine is D2's to-one walk, started at @from), R9
(every listed dimension is reached through the spine, hop names compared
as written), M7 (@default on a count) and M8 (@default on min/max of a
non-numeric field), with the TypeScript message texts. _walk_to_one_via
takes a _ToOneWalk (_d2_walk / _spine_walk); every D2 message is
unchanged. A fractional @default is already refused by this port's
generic attr.int type check (ERR_BAD_ATTR_VALUE on the measure), so no
second error is added; it only skips M7/M8.
- report_shape Table C: under @spine a dimension on the spine is required
when its @Of is @required or a primary-key column; a defaulted measure is
required. New reporting_via_hops / report_spine_hops.
- No generator or runtime change: the Pydantic row model types the spine
key and a defaulted measure without "| None", as it does a count, and the
ObjectManager reads a @spine view's empty row.
- The report REST seam lane serves ProductRevenue; corpus counts: nine
canonical reports, four inert reports, sixteen REST scenarios. A new
unit file ports the TypeScript exact-text assertions for R8/R9/M7/M8.
* docs(reporting): @spine and measure @default
* fix(reporting): review fixes — null-group wording, @SQL shape note, fail-closed spine check
Without @spine a fact row with a null or unmatched reference still counts: it
falls in a null group through a nullable (LEFT OUTER) reference and is dropped
through a required (INNER) one. Corrected in the CHANGELOG, reporting.md and
the spec's R8. reporting.md also notes that an @SQL or @Unmanaged report keeps
the derived read shape, so a defaulted measure and a @spine key are non-null in
every port. extractReportSpec now throws on an unresolvable spine-chain entity
instead of skipping the no-table and TPH refusals. Comment-only tidy-ups in the
Kotlin harness, the C# reporting passes and the Python validator.
* no-mistakes(document): Updated AGENTS.md FR-044 description to reflect shipped @spine and @default attributes.
1 parent 4afad53 commit c0c43e1
195 files changed
Lines changed: 12773 additions & 649 deletions
File tree
- .claude/rules
- agent-context/skills
- metaobjects-authoring
- references
- metaobjects-codegen/references
- docs
- features
- ports
- recipes
- superpowers/specs
- examples/showcase
- 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
- api-contract-conformance
- report
- scenarios
- codegen-noop/reporting
- with
- conformance
- error-measure-default-non-numeric
- input
- error-measure-default-not-integer
- input
- error-measure-default-on-count
- input
- error-report-spine-dimension-off-spine
- input
- error-report-spine-dimension-other-path
- input
- error-report-spine-no-dimensions
- input
- error-report-spine-not-from
- input
- error-report-spine-to-many
- input
- reporting-spine-and-default
- input
- reporting-spine-inherited
- input
- metamodel-docs/expected
- types
- persistence-conformance
- canonical
- queries
- registry-conformance
- server
- csharp
- MetaObjects.Codegen.Tests
- MetaObjects.Conformance.Tests
- MetaObjects.IntegrationTests
- Api
- Generated
- MetaObjects
- Core
- Object
- Reporting
- Loader
- Meta
- SpecMetamodel
- java
- codegen-kotlin/src/test/kotlin/com/metaobjects/generator/kotlin
- codegen-spring/src/test/java/com/metaobjects/generator/spring
- integration-tests-kotlin/src/test/kotlin/com/metaobjects/integration/kotlin
- api/report
- generated
- tables
- integration-tests/src/test/java/com/metaobjects/integration/api
- metadata/src
- main/java/com/metaobjects
- loader
- object
- registry
- reporting
- test/java/com/metaobjects
- loader
- reporting
- omdb/src/test
- java/com/metaobjects/manager/db
- resources
- python
- src/metaobjects
- loader
- meta/core
- object
- reporting
- spec_metamodel
- tests
- codegen
- integration
- runtime
- unit
- typescript/packages
- cli/test/unit
- codegen-ts
- src
- generators
- projection
- test
- projection
- docs-site
- src/builders
- templates
- test
- integration-tests
- src
- test
- metadata
- src
- core
- object
- reporting
- loader
- test
- site-reference
- types
- spec/metamodel
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 | | |
| |||
0 commit comments