Skip to content

fix(geoservices): preserve imported calendar dates (#4833) - #4839

Draft
mikemcdougall wants to merge 9 commits into
trunkfrom
fix/migration-date-only-20260914
Draft

mikemcdougall wants to merge 9 commits into
trunkfrom
fix/migration-date-only-20260914

Conversation

@mikemcdougall

@mikemcdougall mikemcdougall commented Sep 14, 2026

Copy link
Copy Markdown
Collaborator

Pull Request

Issue Link

Closes #4833.
Subtype/dimension prerequisite #4830 is merged; this branch is updated against trunk.
Related SDK discovery defect: honua-io/honua-sdk-js#1718.

Summary

Importing an Esri date-only field stored it as text, so the helicopter sample's original DATE filter failed. Imports now store calendar dates as PostgreSQL DATE and publish date-only metadata and ISO values, while timestamp fields keep epoch-millisecond behavior. Grouped calendar statistics also return ISO dates consistently across server cultures, allowing the helicopter date selector to load.

Changes Made

  • Map esriFieldTypeDateOnly to DATE and reject invalid non-null calendar dates per row.
  • Share schema-aware temporal formatting across GeoServices query, raw point, related-record, identify/find and replication output; preserve DateOnly in JSON/PBF and Date32 in Arrow.
  • Add PostGIS and protocol regression coverage for leap-day boundaries, nulls, invalid input, calendar offsets and timestamp compatibility.
  • Format DATE grouping keys invariantly in both Postgres statistics readers; add six culture cases and an imported grouped DATE/count regression.
  • Document the canonical Date wire correction and fresh-import requirement for existing text columns.

Testing

  • Unit tests added/updated
  • Integration tests added/updated
  • Architecture tests pass
  • Manual testing performed

Earlier import/protocol verification passed 7 PostGIS tests and 97 protocol tests. The current statistics follow-up passes all 13 targeted PostGIS/calendar cases (zero failures or skips), including six culture cases across both readers, the real imported DATE/grouped-count case, two subtype cases and four Z/M cases. The isolated build initially exposed a test asset-path issue; a hash-verified private copy of the repository seed and migration files allowed the same compiled tests to pass with --no-build --no-restore in 16.677 seconds. Scoped dotnet format and git whitespace checks pass. Broad pre-pr/AOT/architecture checks were not repeated locally; That evidence is tied to ef7b55b; the new review follow-up is pending verification.

Fresh local job abccf487f1c5 imports 1,963 rows with zero row failures. Public metadata returns esriFieldTypeDateOnly; original-case and normalized DATE filters both return 1,963. Independent fresh source/target comparison finds no key, non-OID attribute, topology or dimension differences across 3,926 vertices (max XY delta 4.49e-10 degrees; Z 7.30e-12). Source PBF independently confirms enum 14 with ISO strings. Installed SDK 0.1.9-beta.0 reads all 1,963 imported rows with the original native DATE filter and zero temporal-value errors. The published SDK still reports calendar fields as unknown; the coordinated installed SDK candidate e77171323 from honua-io/honua-sdk-js#1720 now passes discovered-schema DATE queries for all 1,963 records against both original Esri and this fresh Honua import. JSON/PBF selected attributes agree in Honolulu, UTC and UTC+14, with no binary fallback. This is candidate evidence, not a registry release.

The job remains NeedsReview: internal reconciliation sends its filter through the legacy raw-WHERE parser, which rejects DATE literals. Public query filtering succeeds; source-aware reconciliation remains #4826. The local evidence uses a recorded managed component overlay on an older host, not a fresh full-host build. The four-app conversion remains incomplete.

Gate Impact

  • PR gates (build, test, governance)
  • Nightly gates (conformance, performance, security)
  • Release gates (packaging, publishing)
  • Deploy gates (promotion, post-apply validation)
  • None ? no gate impact

Docs or Contract Impact

  • OpenAPI spec changed
  • Protobuf/gRPC contract changed
  • Control plane SDK surface changed
  • Documentation updated
  • None ? no docs or contract impact

Release/Deploy Impact

  • Requires coordinated release across repos
  • Requires database migration
  • Requires infrastructure changes
  • Requires environment variable or secret changes
  • None ? standard merge-and-release flow

Coordinate release with SDK PR honua-io/honua-sdk-js#1720 for DateOnly discovery and PBF attribute decoding. SDK PR #1720 has merged; this server PR is ready for review with the additional grouped-statistics correction. No production deployment is requested. The merged #4830 prerequisite is now on trunk; resolving the test-file merge conflict retained the exact tested date regression.

Breaking Changes

Canonical Date fields now advertise esriFieldTypeDateOnly and emit yyyy-MM-dd strings instead of esriFieldTypeDate epochs. Arrow represents them as Date32 rather than timestamps. Canonical DateTime behavior is unchanged. Existing imported text columns require a fresh import; no in-place data rewrite occurs.

Pre-PR Checklist

  • Ran scripts/ci/pre-pr-check.sh and all checks passed
  • Commit messages follow conventional format: type: description (#issue)
  • PR title matches main commit message
  • Issue number linked above
  • Tests added for new functionality
  • If protocol/auth behavior changed: updated compatibility contract
  • If breaking admin/control-plane API changes: updated migration guide
  • OPENAPI_BREAKING_CHANGE_APPROVED ? intentional Admin API break acknowledged for this PR
  • If breaking gRPC/proto wire changes: confirmed with explicit review

Additional wire-path validation

The queryTopFeatures JSON regression passed in CI for all three temporal response paths, and its review thread was resolved with the TRX evidence. Grouped statistics subsequently exposed a separate culture-dependent DateOnly.ToString fallback. Both Postgres readers now emit yyyy-MM-dd with invariant culture. The protected candidate backend returns dateofflight=2026-01-11 and count=1963; the converted app's calendar and source-aligned chart rendered against it. This is limited UI evidence: whole-aircraft selection still depends on the separately tracked SDK transport and server geometry-budget fixes, and the full four-app migration remains incomplete.

Calendar metadata and joined identify follow-up

Commit 85b7e9e recognizes esriFieldTypeDateOnly during catalog reconciliation, normalizes defaults through the shared temporal converter for FeatureServer and MapServer metadata, and normalizes qualified right-side dates/timestamps in dynamic joined identify results. New regressions cover rejected String/DateTime substitutions, defaults in buffered/streaming/top-features output, and joined identify plus dynamic-layer metadata. Focused Core/protocol tests and scoped formatting are queued; PR remains draft and the review findings are not yet marked resolved. Prior ef7b55b PR Gate and affected shards passed. Its documentation gate found generated capability pages stale on the CI merge; regeneration/check on the branch passes with no changes, so that CI failure is not claimed fixed.

…nly-20260914

# Conflicts:
#	tests/dotnet/Honua.Db.Postgres.Tests/Features/Import/GeoservicesImportSubtypePersistenceTests.cs
@mikemcdougall
mikemcdougall marked this pull request as ready for review September 14, 2026 07:37
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 14, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-14T10:50:08.728261Z ef7b55b Draft marked ready
🔒 Security Review Completed 2026-09-14T10:45:43.534094Z ef7b55b Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 160910f2ac

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@claude

claude Bot commented Sep 14, 2026

Copy link
Copy Markdown

Claude Review: No major issues found.

Reviewed commit: 160910f2aca430f6d24ab19b7c2108aebacc4293

@mikemcdougall
mikemcdougall marked this pull request as draft September 14, 2026 09:16
@claude

claude Bot commented Sep 14, 2026

Copy link
Copy Markdown

Claude Review: No major issues found.

Reviewed commit: b8735bfd7f144c6da69bc923d83d4904fcf00086

@mikemcdougall
mikemcdougall marked this pull request as ready for review September 14, 2026 10:39

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ef7b55bd3d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

? DateTimeOffset.FromUnixTimeMilliseconds(element.GetInt64())
: null,

"ESRIFIELDTYPEDATEONLY" =>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Recognize date-only fields during catalog reconciliation

When an import is auto-published, BuildInventoryField preserves this source type as esriFieldTypeDateOnly, but MigrationCatalogReconciler.NormalizeFieldType only recognizes the stripped token date and returns Unknown for dateonly (MigrationCatalogReconciler.cs:680-692). CollectFieldTypeFinding then skips the comparison entirely, so the reconciliation report can classify the import as passing even if the published field regresses to String or DateTime, failing to verify the calendar-date preservation introduced here; map dateonly to MetadataV2FieldType.Date in the reconciler.

Useful? React with 👍 / 👎.

MetadataV2FieldType.Boolean => "esriFieldTypeSmallInteger",
MetadataV2FieldType.DateTime => "esriFieldTypeDate",
MetadataV2FieldType.Date => "esriFieldTypeDate",
MetadataV2FieldType.Date => "esriFieldTypeDateOnly",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Normalize date-only defaults with the advertised field type

For a canonical Date field whose DefaultValue uses the supported legacy epoch representation, this mapping now advertises esriFieldTypeDateOnly, but MapFieldInfo still copies the JsonElement through ConvertJsonElement unchanged at lines 637-650. Consequently the fields array in a normal /query response contains a numeric default for a field whose values are declared as ISO calendar-date strings; the MapServer mapper has the same raw-default behavior. Normalize date defaults through the calendar-date conversion when constructing field metadata.

Useful? React with 👍 / 👎.

// rows. JSONB stores dates as either ISO strings (seeds) or epoch-ms longs
// (applyEdits); coerce both via the shared GeoServices date convention (matches query).
var dateFieldNames = GeoServicesFieldConventions.ResolveDateFieldNames(layer.Resource);
var temporalFieldTypes = GeoServicesFieldConventions.ResolveTemporalFieldTypes(layer.Resource);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Normalize joined date-only attributes during identify

When identify processes a dynamic joinTable, right-side Date fields are now advertised as esriFieldTypeDateOnly by BuildJoinedFields, but this temporal map contains only the left resource's unqualified field names. ApplyDynamicJoinAttributes emits right-side values as <qualifier>.<field>, so a right-side canonical date using the supported legacy epoch representation never matches this map and is returned as a number instead of yyyy-MM-dd. Include the right resource's temporal fields under their qualified names before coercing joined attributes.

Useful? React with 👍 / 👎.

@mikemcdougall
mikemcdougall marked this pull request as draft September 14, 2026 11:17
@claude

claude Bot commented Sep 14, 2026

Copy link
Copy Markdown

Claude Review: No major issues found.

Reviewed commit: ef7b55bd3da7a3625c2adadbe73b9bdf27b3bcdb

Comment on lines +80 to +86
foreach (var result in results.EnumerateArray())
{
var attributes = result.GetProperty("attributes");
attributes.GetProperty("right_parcels.day").GetString().Should().Be("2024-02-29");
attributes.GetProperty("right_parcels.timestamp").GetInt64().Should().Be(epoch);
attributes.GetProperty("right_parcels.nullable_day").ValueKind.Should().Be(JsonValueKind.Null);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Preserve date-only fields through Esri import and public queries

1 participant