Add datatype field to Field and Metric; reframe is_time as role marker - #12
Closed
jonmmease wants to merge 12 commits into
Closed
Add datatype field to Field and Metric; reframe is_time as role marker#12jonmmease wants to merge 12 commits into
jonmmease wants to merge 12 commits into
Conversation
Introduces a top-level `datatype` on Field and Metric with a closed logical enum: string, integer, number, boolean, date, time, timestamp, timestamp_tz, other. Addresses issue #84. `datatype` and `dimension.is_time` are independent and orthogonal: - `datatype` declares the field's logical data type (casting/serialization). - `is_time` is a temporal-role marker (time-series analysis, temporal filtering). A field with `is_time: true` may carry any `datatype` (e.g. integer for a year grain, string for a month name, date for a calendar date). When `is_time` is unset, it defaults to `true` for temporal datatypes (`date`, `time`, `timestamp`, `timestamp_tz`) and `false` otherwise. Explicit `is_time` always wins, so authors can set `is_time: false` on an audit `created_at` to keep it off the time axis. Taxonomy and type/role split were chosen after benchmarking 14 peer semantic layers and 5 portable type standards. Notable precedent: Snowflake Semantic Views' YAML authoring form has a `time_dimensions:` collection whose entries can carry any `data_type` (the published example annotates `order_year` with `data_type: NUMBER`); LookML's `dimension_group` accepts `date`, `datetime`, `timestamp`, `epoch`, and `yyyymmdd`. Snowflake converter updated: `_classify_field` honors explicit `is_time` first, then falls back to the temporal-datatype default. 9 new tests cover the datatype paths and the mixed-metadata cases (`d_year` with `datatype: integer` and `is_time: true`, audit timestamp opt-out, etc.). tpcds_semantic_model.yaml demonstrates three coexistence patterns: datatype-only, datatype + is_time, and is_time-only. Also added .gitignore for __pycache__ directories.
There was a problem hiding this comment.
Pull request overview
This PR extends the Ossie core spec and implementations to support an optional, portable logical datatype on both Field and Metric, while reframing dimension.is_time as an independent temporal-role marker whose effective value can default from temporal datatypes when unset. The change is carried through the JSON Schema, Python bindings, documentation/examples, and multiple converters (Snowflake, GoodData, Polaris, Salesforce, dbt MetricFlow).
Changes:
- Add
DataTypevocabulary and optionaldatatypefields to the core JSON Schema plus spec/docs, and update examples accordingly. - Update Python models with
OSIDataType, addOSIField.is_time_dimension()to implement the effective-role rule, and add tests to keep Python and schema enums synchronized. - Update converter implementations/tests/docs to map vendor-native types ↔ portable
datatype, while keepingdatatypeindependent from dimension/fact classification and fromdimension.is_time.
Reviewed changes
Copilot reviewed 42 out of 43 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| python/tests/test_models.py | Adds Python tests for datatype enum sync, serialization, validation, and effective time-role behavior. |
| python/src/ossie/models.py | Introduces OSIDataType, adds datatype to field/metric, and implements OSIField.is_time_dimension(). |
| python/src/ossie/init.py | Exports OSIDataType from the Python package. |
| examples/tpcds_semantic_model.yaml | Annotates many fields/metrics with datatype and demonstrates type vs. time-role combinations. |
| docs/index.md | Updates glossary entry for Field to mention optional datatype and its vocabulary. |
| core-spec/spec.yaml | Documents datatype and clarifies dimension.is_time semantics and defaulting behavior. |
| core-spec/spec.md | Adds DataType section, documents type-vs-role split, updates field/metric reference tables and examples. |
| core-spec/osi-schema.json | Adds $defs.DataType enum and datatype properties on Field and Metric; updates is_time description. |
| converters/snowflake/tests/test_osi_to_snowflake_yaml_converter.py | Adds tests for datatype→Snowflake mapping and time-dimension classification defaulting from datatype. |
| converters/snowflake/tests/example_converted_tpcds_semantic_model.yaml | Updates expected Snowflake YAML output to include emitted data_type for fields where applicable. |
| converters/snowflake/src/osi_to_snowflake_yaml_converter.py | Implements _convert_datatype and uses datatype for field data_type emission and time-dimension defaults. |
| converters/snowflake/README.md | Documents Snowflake datatype mapping and clarifies that metric result types remain inferred. |
| converters/salesforce/src/test/resources/examples/osiToSalesforce.yaml | Updates Salesforce converter example input to include datatype on fields and metrics. |
| converters/salesforce/src/test/java/org/apache/ossie/SalesforceToOsiConverterTest.java | Extends Salesforce→Ossie tests to assert imported field/metric datatype and time dimension typing. |
| converters/salesforce/src/test/java/org/apache/ossie/OsiToSalesforceConverterTest.java | Adds assertions ensuring exact Salesforce extension types override portable datatypes on export. |
| converters/salesforce/src/test/java/org/apache/ossie/converter/SalesforceDataTypeMapperTest.java | Adds unit tests for Salesforce↔Ossie type mapping, compatibility, and timezone-lossy cases. |
| converters/salesforce/src/main/java/org/apache/ossie/converter/SalesforceDataTypeMapper.java | Introduces centralized Salesforce↔Ossie datatype mapping and compatibility/timezone-lossy helpers. |
| converters/salesforce/src/main/java/org/apache/ossie/converter/MetricMappingHandler.java | Imports Salesforce metric dataType into Ossie metric datatype during Salesforce→Ossie conversion. |
| converters/salesforce/src/main/java/org/apache/ossie/converter/FieldMappingHandler.java | Uses mapper for temporal detection and adds portable datatype export logic with precedence/warnings. |
| converters/salesforce/src/main/java/org/apache/ossie/converter/ConverterConstants.java | Adds datatype constant and removes now-replaced hardcoded Salesforce date/datetime constants. |
| converters/salesforce/README.md | Updates capability claims and adds detailed datatype + role mapping reference and limitations. |
| converters/polaris/src/test/java/org/apache/ossie/converter/polaris/OsiPolarisConverterTest.java | Extends tests to cover datatype import/export precedence, extension round-trips, and legacy fallbacks. |
| converters/polaris/src/test/java/org/apache/ossie/converter/polaris/IcebergTypeMapperTest.java | Adds unit tests for Iceberg physical type ↔ Ossie datatype mapping and nested ID regeneration. |
| converters/polaris/src/main/java/org/apache/ossie/converter/polaris/PolarisImporter.java | Maps Iceberg types to portable datatypes and preserves exact Iceberg type JSON in POLARIS extensions. |
| converters/polaris/src/main/java/org/apache/ossie/converter/polaris/PolarisExporter.java | Exports using exact extension type first, then portable datatype defaults, then legacy heuristics. |
| converters/polaris/src/main/java/org/apache/ossie/converter/polaris/OsiYamlGenerator.java | Emits datatype and field-level custom_extensions in generated Ossie YAML. |
| converters/polaris/src/main/java/org/apache/ossie/converter/polaris/OsiModelParser.java | Parses field datatype and reuses custom-extension parsing for datasets/fields. |
| converters/polaris/src/main/java/org/apache/ossie/converter/polaris/model/OsiModel.java | Adds datatype and customExtensions to the Polaris converter’s internal Field model. |
| converters/polaris/src/main/java/org/apache/ossie/converter/polaris/IcebergTypeMapper.java | Adds shared Iceberg physical type utilities and datatype mapping + exact-type export preparation. |
| converters/polaris/README.md | Documents datatype and exact Iceberg type extension behavior and precedence order. |
| converters/index.md | Updates converter guidance to describe datatype vs dimension.is_time semantics and defaulting. |
| converters/gooddata/tests/test_roundtrip.py | Adds round-trip tests for GoodData sourceColumnDataType ↔ Ossie datatype including Opaque extension cases. |
| converters/gooddata/tests/test_osi_to_gooddata.py | Adds tests for datatype mapping, precedence of exact extensions, warnings, and legacy date-dataset heuristic. |
| converters/gooddata/tests/test_models.py | Updates model tests to assert omitted source types remain None and aren’t serialized spuriously. |
| converters/gooddata/tests/test_gooddata_to_osi.py | Adds tests for GoodData→Ossie datatype mapping, Opaque extension retention, and omission behavior. |
| converters/gooddata/src/ossie_gooddata/osi_to_gooddata.py | Uses shared mapping helper; preserves legacy explicit-time heuristic for date datasets. |
| converters/gooddata/src/ossie_gooddata/models.py | Makes source_column_data_type optional and adjusts (de)serialization to omit defaults when unset. |
| converters/gooddata/src/ossie_gooddata/gooddata_to_osi.py | Maps native GoodData types into portable Ossie datatype and stores unknowns via Opaque extension. |
| converters/gooddata/src/ossie_gooddata/datatype_mapping.py | Adds shared GoodData↔Ossie datatype mapping with precedence and warning semantics. |
| converters/gooddata/README.md | Documents datatype mapping, precedence rules, and limitations (Float/Time/Opaque behaviors). |
| converters/dbt/tests/test_osi_to_msi.py | Adds tests ensuring MetricFlow time-dimension classification respects effective time-role defaulting from datatype. |
| converters/dbt/src/ossie_dbt/osi_to_msi.py | Switches time-dimension classification to field.is_time_dimension() (effective-role logic). |
| .gitignore | Ignores compiled Python artifacts (*.py[cod]). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Author
|
superseded by apache/ossie#113 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
AI Disclosure: I drove the original PR and this updated port iteratively with Claude Code and Codex, and have reviewed it fully.
Motivation
This ports and extends open-semantic-interchange/ossie#113 for the current Apache Ossie codebase.
The original proposal grew out of open-semantic-interchange/ossie#25, which became open-semantic-interchange/ossie#84, and is also related to open-semantic-interchange/ossie#17.
The issue originally framed this as “use
datatyperather thanis_time.” After looking at existing semantic-layer implementations, the cleaner model was to represent data type and temporal role independently:datatypesays what values a field or metric produces.dimension.is_timesays whether a field plays a temporal-dimension role.For example,
d_year,d_quarter_name, andd_month_namemay have different scalar types while all serving temporal roles. Snowflake Semantic Views and LookML similarly separate scalar representation from temporal classification.This PR therefore adds
datatypealongsideis_time, then carries that new metadata through the Python bindings and the existing converter paths that have native type information.What changes
Core specification
Adds an optional top-level
datatypeproperty to bothFieldandMetric, backed by a logical-type vocabulary:StringIntegerDecimalFloatBooleanDateTimeDateTimeDateTimeTzOpaqueThese types have been updated since the original PR to match those introduced by the ontology specification. So the prior
numberis split intoInteger/Float/Decimal. They also include three types that are not part of ontology type system, but I'd be happy to add them there as well for consistency:Time,DateTimeTz, andOpaque.These are logical exchange types, not SQL-physical types. In particular:
Decimalis exact base-10 with unspecified precision and scale;Floatis approximate.DateTimehas no timezone or offset;DateTimeTzidentifies an instant using offset or timezone context, without promising preservation of a named timezone.datatype.Opaquepluscustom_extensionswhen an exact vendor type needs to be retained.Temporal role semantics
dimension.is_timeremains valid and is documented as independent ofdatatype.For fields with a
dimensionblock:is_timevalue wins.is_timeis unset, it defaults totrueforDate,Time,DateTime, andDateTimeTz, and tofalseotherwise.A field without dimension metadata remains a fact regardless of its datatype. This supports combinations such as:
datatype: Integerplusis_time: truefor a year grain.datatype: DateTimeTzplusis_time: falsefor an audit timestamp that should not be exposed as a time dimension.is_time: truewithoutdatatypefor legacy models that know the role but do not declare a scalar type.Python bindings
OSIDataTypeand exports it from the package.OSIFieldandOSIMetric.OSIField.is_time_dimension()for the effective role rule above.POLARISto the supported custom-extension vendor constants so Polaris-imported models validate in Python.Existing converters
This PR makes targetted updates to existing import/export logic;
data_typefor portable mappings, includingDateTime→TIMESTAMP_NTZandDateTimeTz→TIMESTAMP_TZ; omitsOpaquewith a warning. Field classification also follows the explicit-role/temporal-default rule. Snowflake metric result types remain inferred from expressions.sourceColumnDataTypevalues to and from fielddatatype; omitted source types remain unspecified. Unknown GoodData types becomeOpaqueand retain their exact value in aGOODDATAextension. GoodData metrics remain outside the existing converter's scope.POLARISextension so integer width, decimal precision/scale, timestamp precision, and nested structures can round-trip. Nested Iceberg field IDs are regenerated on export. Polaris catalog conversion does not currently convert metrics.SALESFORCEextensions so distinctions such asEmail/TextandCurrency/Numbersurvive round trips. Exporting timezone-freeDateTimewarns because Salesforce's singleDateTimetype re-imports asDateTimeTz. Existing Ossie → Salesforce metric export remains unsupported.is_timerule when classifying MetricFlow time dimensions, including the temporal-datatype default and explicit opt-outs. MetricFlow → Ossie still cannot populate datatypes because the semantic manifest lacks column type metadata.When an exact vendor type in
custom_extensionsconflicts with a portable datatype, the GoodData, Polaris, and Salesforce exporters preserve the exact vendor value and warn. This prioritizes lossless round trips over a lossy default mapping.Examples and documentation
examples/tpcds_semantic_model.yaml: 29 of its 31 fields and all 5 metrics now declare datatypes. It retains examples of datatype without an explicit time role, datatype plusis_time, and legacyis_timewithout datatype.__pycache__directories.Compatibility and impact
The core schema change is additive:
datatypeis optional on fields and metrics.is_timecontinue to validate and preserve their temporal roles.is_time: falseopts a temporal-typed dimension out of time-dimension treatment.The dbt converter does not infer datatypes during MetricFlow → Ossie conversion. MetricFlow's semantic manifest does not provide the required column type metadata; supporting that direction would require a separate catalog or manifest input rather than guessing from semantic roles or expressions. Ossie → MetricFlow does honor the effective
is_timerule because the Ossie input already carries the necessary datatype and dimension metadata.To be conservative, this PR also does not add
TimeorDateTimeTzto the ontology's built-in value types, but I'm happy to add them for consistency if desired.Since this PR updates datatype handling across several existing converters, I wanted to specifically ping the people who've worked on those implementations: @sfc-gh-tgao for Snowflake, @jaceksan for GoodData, @jbonofre for Polaris, and @deutschn for Salesforce. Would welcome any level of review or feedback.