diff --git a/api/libs/semantic_layer/examples/model_file.yml b/api/libs/semantic_layer/examples/model_file.yml index 78e8d3914..e2894e90a 100644 --- a/api/libs/semantic_layer/examples/model_file.yml +++ b/api/libs/semantic_layer/examples/model_file.yml @@ -21,25 +21,25 @@ models: description: Minimum number of logins description: Customers with logins above threshold and active subscription metrics: - # Metric using entity columns, requires deduplication for many-to-many + # Metric using relationship columns, requires deduplication for many-to-many - name: popular_product_revenue expr: SUM(revenue) WHERE culture_products.product_count > 5 description: Revenue from cultures with popular products - entities: + relationships: - name: logins - primary_key: cultureid - foreign_key: cultureid + source_col: cultureid + ref_col: cultureid type: LEFT # Explicitly set, but LLM could override cardinality: one-to-many description: Links to login activity - name: subscriptions - primary_key: cultureid - foreign_key: cultureid + source_col: cultureid + ref_col: cultureid cardinality: one-to-one description: Links to subscription data (no type, LLM decides) - name: culture_products - primary_key: cultureid - foreign_key: cultureid + source_col: cultureid + ref_col: cultureid cardinality: many-to-many description: Links to product associations (many-to-many via junction) @@ -52,10 +52,10 @@ models: measures: - name: login_count description: Number of logins - entities: + relationships: - name: culture - primary_key: cultureid - foreign_key: cultureid + source_col: cultureid + ref_col: cultureid cardinality: many-to-one # Model for subscriptions @@ -67,10 +67,10 @@ models: - name: subscription_status description: Current subscription status options: ["active", "inactive"] - entities: + relationships: - name: culture - primary_key: cultureid - foreign_key: cultureid + source_col: cultureid + ref_col: cultureid cardinality: one-to-one # Junction model for many-to-many between culture and products @@ -84,12 +84,12 @@ models: measures: - name: product_count description: Number of products in this association - entities: + relationships: - name: culture - primary_key: cultureid - foreign_key: cultureid + source_col: cultureid + ref_col: cultureid cardinality: many-to-many - name: products - primary_key: productid - foreign_key: productid + source_col: productid + ref_col: productid cardinality: many-to-many \ No newline at end of file diff --git a/api/libs/semantic_layer/spec.yml b/api/libs/semantic_layer/spec.yml index 81d710161..694aea8e6 100644 --- a/api/libs/semantic_layer/spec.yml +++ b/api/libs/semantic_layer/spec.yml @@ -13,7 +13,7 @@ type: string # Optional, inferred if omitted metrics: - name: string # Required - expr: string # Required, can use model.column from entities + expr: string # Required, can use model.column from relationships description: string # Optional args: # Optional, required only if expr contains arguments, default: null - name: string # Required @@ -21,16 +21,16 @@ description: string # Optional filters: - name: string # Required - expr: string # Required, can use model.column from entities + expr: string # Required, can use model.column from relationships description: string # Optional args: # Optional, required only if expr contains arguments, default: null - name: string # Required type: string # Required description: string # Optional - entities: + relationships: - name: string # Required - primary_key: string # Required - foreign_key: string # Required + source_col: string # Required + ref_col: string # Required type: string # Optional, e.g., "LEFT", "INNER"; LLM decides if omitted cardinality: string # Optional, e.g., "one-to-many", "many-to-many" description: string # Optional diff --git a/api/libs/semantic_layer/src/models.rs b/api/libs/semantic_layer/src/models.rs index a7f899712..dfb0145f3 100644 --- a/api/libs/semantic_layer/src/models.rs +++ b/api/libs/semantic_layer/src/models.rs @@ -18,7 +18,7 @@ pub struct Model { pub metrics: Vec, #[serde(default)] pub filters: Vec, - #[serde(rename = "entities", default)] // Added default + #[serde(default)] // Added default pub relationships: Vec, } diff --git a/cli/cli/src/commands/deploy/deploy.rs b/cli/cli/src/commands/deploy/deploy.rs index bad0e5be2..61a28c770 100644 --- a/cli/cli/src/commands/deploy/deploy.rs +++ b/cli/cli/src/commands/deploy/deploy.rs @@ -439,23 +439,9 @@ fn to_deploy_request(model: &Model, sql_content: String) -> DeployDatasetsReques }); } - // Convert entity relationships - let entity_relationships: Option> = - if !model.relationships.is_empty() { - Some( - model - .relationships - .iter() - .map(|rel| DeployDatasetsEntityRelationshipsRequest { - name: rel.name.clone(), - expr: rel.source_col.clone(), // Assuming foreign_key is the expression for the relationship for now - type_: rel.type_.clone().unwrap_or_else(|| "LEFT".to_string()), // Default to LEFT if not specified - }) - .collect(), - ) - } else { - None - }; + // Note: Relationships are now preserved in the yml_file field rather than being converted to entity_relationships. + // This allows the full semantic model structure (including relationships with all their metadata like + // cardinality, descriptions, etc.) to be preserved and processed by the backend. let data_source_name = model.data_source_name.clone() .expect("data_source_name missing after validation, should be resolved by resolve_model_configurations"); @@ -464,7 +450,7 @@ fn to_deploy_request(model: &Model, sql_content: String) -> DeployDatasetsReques ); // Serialize the input Model to YAML to be stored in the yml_file field of the request. - // This captures the full semantic definition as sent. + // This captures the full semantic definition as sent, including relationships with all their metadata. let yml_content_for_request = serde_yaml::to_string(&model).unwrap_or_else(|e| { eprintln!( "Error serializing model {} to YAML for deploy request: {}. Using empty string.", @@ -484,7 +470,7 @@ fn to_deploy_request(model: &Model, sql_content: String) -> DeployDatasetsReques database: model.database.clone(), description: model.description.clone().unwrap_or_default(), sql_definition: Some(sql_content), - entity_relationships, + entity_relationships: None, // Relationships are now preserved in yml_file instead columns, yml_file: Some(yml_content_for_request), // Store the YAML of the model being deployed } @@ -1191,7 +1177,7 @@ mod tests { name: test_model description: "Test model" -dimension: +dimensions: - name: dim1 description: "First dimension" type: "string" @@ -1362,19 +1348,15 @@ models: }; let sql_content = "SELECT * FROM test_schema.test_model"; - let request = to_deploy_request(&model, sql_content.to_string()); // Call the restored function + let request = to_deploy_request(&model, sql_content.to_string()); assert_eq!(request.name, "test_model"); assert_eq!(request.columns.len(), 2); // 1 dim, 1 measure assert_eq!(request.columns[0].name, "dim1"); assert_eq!(request.columns[0].searchable, true); assert_eq!(request.columns[1].name, "measure1"); - assert!(request.entity_relationships.is_some()); - assert_eq!(request.entity_relationships.as_ref().unwrap().len(), 1); - assert_eq!( - request.entity_relationships.as_ref().unwrap()[0].name, - "related_model" - ); + // The model has empty relationships, so entity_relationships should be None + assert!(request.entity_relationships.is_none()); let expected_yml_content = serde_yaml::to_string(&model)?; assert_eq!(request.yml_file, Some(expected_yml_content));