Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/compatibility/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ Detailed compatibility documents describe field-level limitations where they exi
| `malloy` | — | `file_or_directory` | `.malloy` | Registered | Yes | 10 | [Detailed guide](malloy.md) |
| `metricflow` | `dbt`, `dbt-semantic-layer` | `file_or_directory` | `.yml`, `.yaml` | Registered | Yes | 6 | Registry summary only |
| `omni` | — | `file_or_directory` | `.yml`, `.yaml` | Registered | Yes | 4 | Registry summary only |
| `ossie` | `apache-ossie`, `osi`, `open-semantic-interchange` | `file_or_directory` | `.yml`, `.yaml`, `.json` | Registered | Yes | 4 | [Detailed guide](ossie.md) |
| `ossie` | `apache-ossie`, `osi`, `open-semantic-interchange` | `file_or_directory` | `.yml`, `.yaml`, `.json` | Registered | Yes | 5 | [Detailed guide](ossie.md) |
| `rill` | — | `file_or_directory` | `.yml`, `.yaml` | Registered | Yes | 8 | Registry summary only |
| `sidemantic` | `native` | `file` | `.yml`, `.yaml`, `.sql` | Registered | Yes | 1 | Registry summary only |
| `snowflake` | `cortex`, `snowflake-cortex` | `file_or_directory` | `.yml`, `.yaml` | Registered | Yes | 8 | Registry summary only |
Expand Down
21 changes: 21 additions & 0 deletions docs/compatibility/ossie.md
Original file line number Diff line number Diff line change
Expand Up @@ -238,3 +238,24 @@ sidemantic convert models/orders.yml \
```

Use `--force` only when intentionally replacing an existing output file.

## Rust forward-import status

The experimental Rust runtime now has a strict forward Ossie import subset in
its dedicated `ossie` adapter. It supports explicit consumer profiles,
including `ossie-core` (`0.1.1` and `0.2.0.dev0`) and `dbt-1.12` compatibility
profiles. It preserves separate semantic-model scopes and uses
exact-target-then-`ANSI_SQL` expression selection for its supported runtime
targets: `ANSI_SQL`, `DUCKDB`, `POSTGRES`, `SNOWFLAKE`, `DATABRICKS`, and
`BIGQUERY`. Its import gate also checks scalar SQL structure, identifiers,
declared primary and unique keys, relationship identity and endpoints, key
arity, and target-key uniqueness. Invalid or unsupported input fails closed;
the legacy `osi` adapter remains a separate compatibility surface.

This is a forward import subset, not full parity with the Python contract. Rust
does not yet provide the Python implementation's complete pinned JSON Schema
validation, preserved source-document and exact-byte model, ontology
preservation/reasoning boundary, permissive lowering mode, or Ossie export and
graph-synthesis path. Rust checks therefore establish strict structural import
coverage only; they are not live warehouse execution tests and do not claim
runtime coverage for every target database.
87 changes: 85 additions & 2 deletions sidemantic-rs/examples/parity_adapter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
use sidemantic::{
build_symmetric_aggregate_sql, config::SidemanticConfig, load_from_string, Aggregation,
DimensionType, Metric, Model, QueryRewriter, RelationshipType, SemanticGraph, SemanticQuery,
SqlDialect, SqlGenerator, SymmetricAggType, TableCalculation,
DimensionType, Metric, Model, OssieConsumerProfile, OssieForwardAdapter, OssieSerialization,
OssieTarget, QueryRewriter, RelationshipType, SemanticGraph, SemanticQuery, SqlDialect,
SqlGenerator, SymmetricAggType, TableCalculation,
};

#[derive(Debug, Deserialize)]
Expand Down Expand Up @@ -90,6 +91,21 @@ enum Request {
relationship: String,
is_base_model: bool,
},
OssieValidate {
content: String,
serialization: String,
#[serde(default = "default_ossie_consumer_profile")]
consumer_profile: String,
},
OssieSelectScope {
content: String,
serialization: String,
#[serde(default = "default_ossie_consumer_profile")]
consumer_profile: String,
#[serde(default = "default_ossie_target")]
target: String,
scope_id: Option<String>,
},
}

#[derive(Debug, Serialize)]
Expand Down Expand Up @@ -117,6 +133,8 @@ struct PathStep {
from_columns: Vec<String>,
to_columns: Vec<String>,
relationship: &'static str,
#[serde(skip_serializing_if = "Option::is_none")]
edge_id: Option<String>,
}

fn handle(request: Request) -> sidemantic::Result<Response> {
Expand Down Expand Up @@ -186,6 +204,7 @@ fn handle(request: Request) -> sidemantic::Result<Response> {
from_columns: step.from_keys,
to_columns: step.to_keys,
relationship: relationship_type_name(&step.relationship_type),
edge_id: step.edge_id,
})
.collect();
Ok(Response::Ok {
Expand Down Expand Up @@ -356,9 +375,73 @@ fn handle(request: Request) -> sidemantic::Result<Response> {
)
)),
}),
Request::OssieValidate {
content,
serialization,
consumer_profile,
} => {
let serialization = parse_ossie_serialization(&serialization)?;
let consumer = parse_ossie_consumer(&consumer_profile)?;
let status = OssieForwardAdapter.inspect(&content, serialization, consumer);
Ok(Response::Ok {
sql: None,
path: None,
catalog: None,
value: Some(
serde_json::to_value(status).map_err(|error| {
sidemantic::SidemanticError::Validation(error.to_string())
})?,
),
})
}
Request::OssieSelectScope {
content,
serialization,
consumer_profile,
target,
scope_id,
} => {
let scope = OssieForwardAdapter.select_scope(
&content,
parse_ossie_serialization(&serialization)?,
parse_ossie_consumer(&consumer_profile)?,
parse_ossie_target(&target)?,
scope_id.as_deref(),
)?;
Ok(Response::Ok {
sql: None,
path: None,
catalog: None,
value: Some(
serde_json::to_value(scope).map_err(|error| {
sidemantic::SidemanticError::Validation(error.to_string())
})?,
),
})
}
}
}

fn default_ossie_consumer_profile() -> String {
"ossie-core".to_string()
}

fn default_ossie_target() -> String {
"ANSI_SQL".to_string()
}

fn parse_ossie_serialization(value: &str) -> sidemantic::Result<OssieSerialization> {
OssieSerialization::parse(value).map_err(sidemantic::SidemanticError::Validation)
}

fn parse_ossie_consumer(value: &str) -> sidemantic::Result<OssieConsumerProfile> {
OssieConsumerProfile::parse(value).map_err(sidemantic::SidemanticError::Validation)
}

fn parse_ossie_target(value: &str) -> sidemantic::Result<OssieTarget> {
OssieTarget::parse(value).map_err(sidemantic::SidemanticError::Validation)
}

fn parse_symmetric_agg_type(agg_type: &str) -> sidemantic::Result<SymmetricAggType> {
match agg_type {
"sum" => Ok(SymmetricAggType::Sum),
Expand Down
3 changes: 3 additions & 0 deletions sidemantic-rs/src/adapters/cube.rs
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,8 @@ impl CubeDimension {
Dimension {
name: self.name,
r#type: dim_type,
logical_data_type: None,
declared_is_time: None,
sql,
granularity: None,
supported_granularities: None,
Expand Down Expand Up @@ -224,6 +226,7 @@ impl CubeMeasure {
Metric {
name: self.name,
extends: None,
logical_data_type: None,
r#type: metric_type,
agg,
sql,
Expand Down
5 changes: 5 additions & 0 deletions sidemantic-rs/src/adapters/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,14 @@ use crate::error::{Result, SidemanticError};

pub mod cube;
pub mod osi;
pub mod ossie;

pub use cube::CubeAdapter;
pub use osi::OsiAdapter;
pub use ossie::{
OssieCatalog, OssieCompiledScope, OssieConsumerProfile, OssieDiagnostic, OssieForwardAdapter,
OssieProfile, OssieSerialization, OssieStatus, OssieTarget,
};

/// Result of parsing a single external-format document.
#[derive(Debug, Default)]
Expand Down
3 changes: 3 additions & 0 deletions sidemantic-rs/src/adapters/osi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,8 @@ fn parse_field(field_def: &Json) -> Option<Dimension> {
} else {
DimensionType::Categorical
},
logical_data_type: None,
declared_is_time: None,
sql,
granularity: if is_time {
Some("day".to_string())
Expand Down Expand Up @@ -480,6 +482,7 @@ fn add_relationship_to_model(rel_def: &Json, models: &mut [Model]) {

let relationship = Relationship {
name: to_model.to_string(),
edge_id: None,
r#type: RelationshipType::ManyToOne,
foreign_key: foreign_key_columns.first().cloned(),
foreign_key_columns: Some(foreign_key_columns),
Expand Down
Loading
Loading