From a7396bb2d0ab6c783db58cf6229247ecec5974db Mon Sep 17 00:00:00 2001 From: Josh Taillon Date: Wed, 21 Oct 2026 20:29:20 -0600 Subject: [PATCH 1/2] fix(r): read a Snowflake semantic view's base table names as SQL identifiers The view's YAML keeps a quoted name's quotes, so a base table created as "orders" was recorded as a table literally named with quotes, and scoping a source to the view's dependencies failed. Quoted names are now unquoted, with a doubled quote standing for one, and unquoted names fold to upper case. Closes #446 --- pkg-r/NEWS.md | 2 ++ pkg-r/R/catalog-snowflake.R | 16 ++++++++++++--- pkg-r/tests/testthat/test-catalog-snowflake.R | 20 +++++++++++++++++++ 3 files changed, 35 insertions(+), 3 deletions(-) diff --git a/pkg-r/NEWS.md b/pkg-r/NEWS.md index e5f5213fc..02d1e891f 100644 --- a/pkg-r/NEWS.md +++ b/pkg-r/NEWS.md @@ -1,5 +1,7 @@ # commons (development version) +* A Snowflake semantic view whose base tables are named with quoted identifiers, such as `"orders"`, now scopes to those tables. The quotes were kept as part of each name, so `data_source()` could not find them (#446). + * A definition that adds `NULL` to a date, such as `created + NULL`, now compiles to SQL that Snowflake accepts. It compiles to a typed `NULL` timestamp (#441). * A definition that divides by a compound expression, such as `amount / (ratio * 2)`, now compiles to the correct Snowflake and Databricks SQL. The parentheses around the divisor were dropped, so the query divided by `ratio` alone (#439). diff --git a/pkg-r/R/catalog-snowflake.R b/pkg-r/R/catalog-snowflake.R index a52b89f0d..9d2fdcb06 100644 --- a/pkg-r/R/catalog-snowflake.R +++ b/pkg-r/R/catalog-snowflake.R @@ -692,9 +692,9 @@ snowflake_semantic_dependencies <- function(id, specification) { next } ids[[length(ids) + 1L]] <- DBI::Id( - catalog = catalog, - schema = schema, - table = table_name + catalog = snowflake_yaml_identifier(catalog), + schema = snowflake_yaml_identifier(schema), + table = snowflake_yaml_identifier(table_name) ) } keys <- vapply( @@ -706,6 +706,16 @@ snowflake_semantic_dependencies <- function(id, specification) { list(ids = ids[!duplicated(keys)], complete = complete && length(ids) > 0L) } +# A name in a semantic view's YAML follows SQL identifier rules: a quoted one +# is exact, with a doubled quote standing for one, and an unquoted one folds to +# upper case. +snowflake_yaml_identifier <- function(name) { + if (grepl('^".*"$', name)) { + return(gsub('""', '"', substr(name, 2L, nchar(name) - 1L), fixed = TRUE)) + } + toupper(name) +} + snowflake_semantic_context <- function( specification, dimensions, diff --git a/pkg-r/tests/testthat/test-catalog-snowflake.R b/pkg-r/tests/testthat/test-catalog-snowflake.R index cdb8456fa..1aeec2082 100644 --- a/pkg-r/tests/testthat/test-catalog-snowflake.R +++ b/pkg-r/tests/testthat/test-catalog-snowflake.R @@ -93,6 +93,26 @@ test_that("Snowflake semantic YAML imports only public dimensions and metrics", expect_equal(model$metrics[[1]]$parent, "orders") }) +test_that("Snowflake semantic dependencies read base tables as SQL identifiers", { + # The YAML keeps a quoted name's quotes, and folds an unquoted one to upper + # case the way the SQL that created it would. + specification <- list(tables = list( + list(base_table = list( + database = '"Commons"', + schema = "live", + table = '"order ""lines"""' + )) + )) + id <- DBI::Id(catalog = "COMMONS", schema = "LIVE", table = "MODEL") + + model <- snowflake_semantic_model_from_spec(id, specification) + + expect_identical( + model$dependencies[[1]], + DBI::Id(catalog = "Commons", schema = "LIVE", table = 'order "lines"') + ) +}) + test_that("Snowflake semantic scope retains dependencies and public context", { specification <- list( custom_instructions = "Use booked revenue for finance questions.", From 421986a5f2bbdedf32f67277b48083572d976124 Mon Sep 17 00:00:00 2001 From: Josh Taillon Date: Wed, 21 Oct 2026 20:34:33 -0600 Subject: [PATCH 2/2] fix(r): fold only the base table names a semantic view's YAML gives A namespace the YAML leaves out comes from the view's own id, which Snowflake already reports exactly, so folding it to upper case broke a view in a quoted mixed-case database or schema. --- pkg-r/R/catalog-snowflake.R | 18 ++++++++++++------ pkg-r/tests/testthat/test-catalog-snowflake.R | 10 ++++++++++ 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/pkg-r/R/catalog-snowflake.R b/pkg-r/R/catalog-snowflake.R index 9d2fdcb06..3f2a44bfc 100644 --- a/pkg-r/R/catalog-snowflake.R +++ b/pkg-r/R/catalog-snowflake.R @@ -680,9 +680,12 @@ snowflake_semantic_dependencies <- function(id, specification) { complete <- TRUE for (table in semantic_spec_entries(specification$tables)) { base <- table$base_table %||% list() - table_name <- base$table - catalog <- base$database %||% base$catalog %||% model[["catalog"]] - schema <- base$schema %||% model[["schema"]] + # A name the YAML gives follows SQL identifier rules; one it leaves out + # comes from the view's own id, which is already exact. + table_name <- snowflake_yaml_identifier(base$table) + catalog <- snowflake_yaml_identifier(base$database %||% base$catalog) %||% + model[["catalog"]] + schema <- snowflake_yaml_identifier(base$schema) %||% model[["schema"]] if ( !rlang::is_string(catalog) || !rlang::is_string(schema) || @@ -692,9 +695,9 @@ snowflake_semantic_dependencies <- function(id, specification) { next } ids[[length(ids) + 1L]] <- DBI::Id( - catalog = snowflake_yaml_identifier(catalog), - schema = snowflake_yaml_identifier(schema), - table = snowflake_yaml_identifier(table_name) + catalog = catalog, + schema = schema, + table = table_name ) } keys <- vapply( @@ -710,6 +713,9 @@ snowflake_semantic_dependencies <- function(id, specification) { # is exact, with a doubled quote standing for one, and an unquoted one folds to # upper case. snowflake_yaml_identifier <- function(name) { + if (!rlang::is_string(name)) { + return(NULL) + } if (grepl('^".*"$', name)) { return(gsub('""', '"', substr(name, 2L, nchar(name) - 1L), fixed = TRUE)) } diff --git a/pkg-r/tests/testthat/test-catalog-snowflake.R b/pkg-r/tests/testthat/test-catalog-snowflake.R index 1aeec2082..fb2184be7 100644 --- a/pkg-r/tests/testthat/test-catalog-snowflake.R +++ b/pkg-r/tests/testthat/test-catalog-snowflake.R @@ -111,6 +111,16 @@ test_that("Snowflake semantic dependencies read base tables as SQL identifiers", model$dependencies[[1]], DBI::Id(catalog = "Commons", schema = "LIVE", table = 'order "lines"') ) + + # A namespace the YAML leaves out is the view's own, which is exact. + inherited <- snowflake_semantic_model_from_spec( + DBI::Id(catalog = "MyDb", schema = "MySchema", table = "MODEL"), + list(tables = list(list(base_table = list(table = "orders")))) + ) + expect_identical( + inherited$dependencies[[1]], + DBI::Id(catalog = "MyDb", schema = "MySchema", table = "ORDERS") + ) }) test_that("Snowflake semantic scope retains dependencies and public context", {