-
Notifications
You must be signed in to change notification settings - Fork 280
fix(orionbelt): carry field and metric datatype in both directions #410
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -91,3 +91,124 @@ | |
| "timestamp": "timestamp", | ||
| "boolean": "boolean", | ||
| } | ||
|
|
||
| # ─── Ossie DataType ⇄ OBML ────────────────────────────────────────────────── | ||
| # Ossie `datatype` on Field/Metric is a *logical* type backed by the capitalised | ||
| # `DataType` enum in core-spec/ossie-schema.json - the same layer as OBML's column | ||
| # `abstractType` - so this is the field/dimension mapping. | ||
| # | ||
| # `Decimal` has no logical-layer equivalent in OBML: OBML models exact decimal at | ||
| # the physical/result layer (`sqlType`/`sqlPrecision`/`sqlScale`, measure/metric | ||
| # `dataType` via `decimal(p, s)`), not as a coarse `abstractType`. So `Decimal` | ||
| # narrows to `float` for fields, but is recovered exactly for metrics via the | ||
| # physical `dataType` map below (`OSSIE_DATATYPE_TO_OBML_PHYSICAL`). | ||
| # | ||
| # `Opaque` is Ossie's "known type outside the portable vocabulary" marker and is | ||
| # intentionally absent so it falls back to the name heuristic on import. | ||
| OSSIE_DATATYPE_TO_OBML_ABSTRACT = { | ||
| "String": "string", | ||
| "Integer": "int", | ||
| "Float": "float", | ||
| "Decimal": "float", | ||
| "Boolean": "boolean", | ||
| "Date": "date", | ||
| "Time": "time", | ||
| "DateTime": "timestamp", | ||
| "DateTimeTz": "timestamp_tz", | ||
| } | ||
|
|
||
| # OBML column `abstractType` -> Ossie `DataType`, for the export direction. | ||
| OBML_ABSTRACT_TO_OSSIE_DATATYPE = { | ||
| "string": "String", | ||
| "json": "Opaque", | ||
| "int": "Integer", | ||
| "float": "Float", | ||
| "date": "Date", | ||
| "time": "Time", | ||
| "time_tz": "Time", | ||
| "timestamp": "DateTime", | ||
| "timestamp_tz": "DateTimeTz", | ||
| "boolean": "Boolean", | ||
| } | ||
|
|
||
| # Metric/measure `datatype`. Unlike fields, OBML measures/metrics carry an exact | ||
| # `dataType` (physical vocabulary: `integer`/`double`/`decimal(p, s)`/...), which | ||
| # is where `Decimal` genuinely belongs. So Ossie metric `datatype` maps to that | ||
| # field, not the coarse `abstractType`. | ||
| OBML_DECIMAL_DEFAULT = "decimal(18, 2)" # mirrors OrionBelt's built-in default | ||
|
|
||
| # Ossie `DataType` -> OBML physical `dataType` (import direction). `Opaque` is | ||
| # omitted (non-portable). `DateTimeTz` has no tz-aware physical form, so it | ||
| # narrows to `timestamp`. | ||
| OSSIE_DATATYPE_TO_OBML_PHYSICAL = { | ||
| "String": "string", | ||
| "Integer": "integer", | ||
| "Float": "double", | ||
| "Decimal": OBML_DECIMAL_DEFAULT, | ||
| "Boolean": "boolean", | ||
| "Date": "date", | ||
| "Time": "time", | ||
| "DateTime": "timestamp", | ||
| "DateTimeTz": "timestamp", | ||
| } | ||
|
|
||
| # OBML physical `dataType` -> Ossie `DataType` (export direction). `decimal(p, s)` | ||
| # is handled by ``obml_datatype_to_ossie`` since it is parametrised. | ||
| OBML_PHYSICAL_TO_OSSIE_DATATYPE = { | ||
| "string": "String", | ||
| "integer": "Integer", | ||
| "bigint": "Integer", | ||
| "double": "Float", | ||
| "boolean": "Boolean", | ||
| "date": "Date", | ||
| "time": "Time", | ||
| "timestamp": "DateTime", | ||
| } | ||
|
|
||
|
|
||
| def obml_datatype_to_ossie(data_type: object) -> str | None: | ||
| """Map an explicit OBML measure/metric ``dataType`` to an Ossie ``DataType``. | ||
|
|
||
| Returns ``None`` when there is no mapping, so the caller emits nothing rather | ||
| than an unknown type. ``decimal(p, s)`` maps to ``Decimal``. A hand-authored | ||
| document may carry a non-string ``dataType`` (``123``) that no schema check | ||
| has rejected yet; that has no mapping either, rather than aborting the whole | ||
| conversion. | ||
| """ | ||
| if not isinstance(data_type, str): | ||
| return None | ||
| normalized = data_type.strip().lower() | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I think it's worth guarding with
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good catch, fixed in 7375c7e. |
||
| if not normalized: | ||
| return None | ||
| if normalized.startswith("decimal"): | ||
| return "Decimal" | ||
| return OBML_PHYSICAL_TO_OSSIE_DATATYPE.get(normalized) | ||
|
|
||
|
|
||
| def obml_decimal_default(settings: object) -> str: | ||
| """The ``dataType`` an Ossie ``Decimal`` metric becomes in this model. | ||
|
|
||
| OBML lets a model set ``settings.defaultNumericDataType`` (always a | ||
| ``decimal(p, s)``, which OrionBelt enforces), and a model configured for | ||
| ``decimal(20, 6)`` should not have its metrics written as the built-in | ||
| ``decimal(18, 2)``. Anything other than a decimal string there falls back to | ||
| the built-in default. | ||
| """ | ||
| if isinstance(settings, dict): | ||
| configured = settings.get("defaultNumericDataType") | ||
| if isinstance(configured, str) and configured.strip().lower().startswith("decimal"): | ||
| return configured | ||
| return OBML_DECIMAL_DEFAULT | ||
|
|
||
|
|
||
| def ossie_metric_datatype_to_obml(ossie_datatype: object, decimal_default: str) -> str | None: | ||
| """Map an Ossie metric ``datatype`` to the OBML measure/metric ``dataType``. | ||
|
|
||
| ``Decimal`` takes the model's numeric default; ``Opaque``, an unknown value | ||
| or a non-string has no mapping. | ||
| """ | ||
| if not isinstance(ossie_datatype, str): | ||
| return None | ||
| if ossie_datatype == "Decimal": | ||
| return decimal_default | ||
| return OSSIE_DATATYPE_TO_OBML_PHYSICAL.get(ossie_datatype) | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OBML_DECIMAL_DEFAULT = "decimal(18, 2)"is applied unconditionally whenever an Ossie metric hasdatatype: "Decimal". But OBML models can override the default numeric type viasettings.defaultNumericDataType, this hardcodes past that override, so a model configured for e.g.decimal(20, 6)gets metrics silently emitted asdecimal(18, 2)instead.Should this read
obml_settings.defaultNumericDataType(when present) before falling back to the"decimal(18, 2)"constant?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed, fixed in 7375c7e. An Ossie
Decimalmetric now takessettings.defaultNumericDataTypewhen the model has one, and falls back todecimal(18, 2)otherwise. The settings only exist on an OBML-origin model, in the stashedobml_settings, and the importer restored them after converting metrics. So they are now read before the metrics, and the restore itself stays where it was. OrionBelt rejects adefaultNumericDataTypethat is not adecimal(p, s), so any other value falls back to the built-in default here too. Covered byTestDecimalDefaultFromSettings.