Skip to content
Draft
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: 2 additions & 0 deletions pkg-r/NEWS.md
Original file line number Diff line number Diff line change
@@ -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).
Expand Down
22 changes: 19 additions & 3 deletions pkg-r/R/catalog-snowflake.R
Original file line number Diff line number Diff line change
Expand Up @@ -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) ||
Expand All @@ -706,6 +709,19 @@ 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 (!rlang::is_string(name)) {
return(NULL)
}
if (grepl('^".*"$', name)) {
return(gsub('""', '"', substr(name, 2L, nchar(name) - 1L), fixed = TRUE))
}
toupper(name)
}

snowflake_semantic_context <- function(
specification,
dimensions,
Expand Down
30 changes: 30 additions & 0 deletions pkg-r/tests/testthat/test-catalog-snowflake.R
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,36 @@ 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"')
)

# 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", {
specification <- list(
custom_instructions = "Use booked revenue for finance questions.",
Expand Down
Loading