-
Notifications
You must be signed in to change notification settings - Fork 107
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
include synonyms in odbcListObjects()
output
#773
base: main
Are you sure you want to change the base?
Conversation
R/driver-sql-server.R
Outdated
INNER JOIN sys.databases AS DB | ||
ON Sch.principal_id = DB.database_id | ||
WHERE DB.name = ? AND Sch.name = ? | ||
AND OBJECTPROPERTY(Object_ID(Syn.base_object_name), 'IsTable') = 1;", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For now, I've opted to only include synonyms whose base object is a table, and refer to that synonym as a "table" in the output. This means that synonyms (that are tables) will be previewable through the Connections pane, but also feels a bit hacky. From what I can tell, the drop-down based in the pane based on odbcListFields()
also doesn't work (but doesn't cause errors/crashes).
Instead, we could opt not to include this table and return the type
as synonym
. This means that there would be entries in the Connections pane but no previews.
@simonpcouch |
@ThomasSoeiro A separate issue is good! :) |
Hmmm, I think I'd be inclined to include these in |
Synonyms are now supported with With `main`library(DBI)
library(odbc)
con <- dbConnect(odbc(), dsn = "MicrosoftSQLServer", uid = "SA",
pwd = Sys.getenv("sqlServerPass"))
dbExecute(con, "create schema odbc")
#> [1] 0
dbExecute(con, "create table odbc.test (x int)")
#> [1] 0
# confirm that we can find the table:
odbcListObjects(con, catalog = "master", schema = "odbc")
#> name type
#> 1 test table
dbListTables(con, catalog = "master", schema = "odbc")
#> [1] "test"
# make a synonym and show that it can't be found:
dbExecute(con, "create synonym odbc.test2 for odbc.test")
#> [1] 0
odbcListObjects(con, catalog = "master", schema = "odbc")
#> name type
#> 1 test table
dbListTables(con, catalog = "master", schema = "odbc")
#> [1] "test" With this PRWith this PR: library(DBI)
library(odbc)
con <- dbConnect(odbc(), dsn = "MicrosoftSQLServer", uid = "SA",
pwd = Sys.getenv("sqlServerPass"))
dbExecute(con, "create schema odbc")
#> [1] 0
dbExecute(con, "create table odbc.test (x int)")
#> [1] 0
# confirm that we can find the table:
odbcListObjects(con, catalog = "master", schema = "odbc")
#> name type
#> 1 test table
dbListTables(con, catalog = "master", schema = "odbc")
#> [1] "test"
# make a synonym and show that it CAN be found:
dbExecute(con, "create synonym odbc.test2 for odbc.test")
#> [1] 0
odbcListObjects(con, catalog = "master", schema = "odbc")
#> name type
#> 1 test table
#> 2 test2 table
dbListTables(con, catalog = "master", schema = "odbc")
#> [1] "test" "test2" My biggest concern at this point is performance, esp. in cases when databases have many synonyms. Should this feature be gated behind an argument? |
had issues passing these as parameters using something like `WHERE (? IS NULL OR DB.name = ?) AND (? IS NULL OR Sch.name = ?)`
@@ -73,11 +73,11 @@ setMethod("dbExistsTable", c("Microsoft SQL Server", "character"), | |||
stopifnot(length(name) == 1) | |||
if (isTempTable(conn, name, ...)) { | |||
query <- paste0("SELECT OBJECT_ID('tempdb..", name, "')") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if it's worth checking to see if this works for synonyms too? (without the tempdb..
). If so, that would substantially simplify this function, and maybe make it simpler?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@simonpcouch Did you see this comment?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did! Just haven't made a moment to come back to this PR to address this comment and run some quick benchmarks to see how it affects performance. Will re-request review then, likely tomorrow!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh sorry, I though you had re-requested a review, but I must've been misreading my notifications.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Was almost able to make this work with:
dots <- list(...)
name_loc <- paste0(
c(dots[["catalog_name"]], dots[["schema_name"]], name),
collapse = "."
)
return(!is.na(dbGetQuery(conn, paste0("SELECT OBJECT_ID('", name_loc, "')"))[[1]]))
The only issue is that OBJECT_ID()
seems to only be able to work with fully qualified names. i.e.
library(DBI)
library(odbc)
con <- dbConnect(odbc(), dsn = "MicrosoftSQLServer", uid = "SA",
pwd = Sys.getenv("sqlServerPass"))
dbExecute(con, "create schema odbc")
#> [1] 0
dbExecute(con, "create table odbc.test (x int)")
#> [1] 0
dbExecute(con, "create synonym odbc.test2 for odbc.test")
#> [1] 0
# when fully qualified, works fine:
dbExistsTable(con, SQL("master.odbc.test2"))
#> [1] TRUE
# but not without schema/catalog specified
"test2" %in% dbListTables(con)
#> [1] TRUE
dbExistsTable(con, "test2")
#> [1] FALSE
Created on 2024-04-10 with reprex v2.1.0
Specifically, this triggered this DBItest test.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmmmm, is dbListTables()
correct here? i.e. SELECT * FROM test2
won't work (IIUC) because it's not in the current schema. So should it actually be listed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
:thinkies: The DBI docs for the generic read:
dbListTables() returns a character vector that enumerates all tables and views in the database.
so... I think it's correct that dbListTables()
would list test2
.
Some statements:
- DBI docs: dbListTables() returns a character vector that enumerates all tables and views in the database.
- DBItest, linked above: For all tables listed by
dbListTables()
,dbExistsTable()
should returnTRUE
. - Us, on slack: maybe the principle is that if
SELECT * FROM foo
works thendbExistsTable(con, "foo")
should return TRUE?
So dbListTables()
and dbExistsTable()
should agree, and dbListTables()
seems to be doing the right thing. The connection to SELECT * FROM foo
doesn't seem to hold up, at least for SQL Server.
FWIW, with this PR:
library(DBI)
library(odbc)
con <- dbConnect(odbc(), dsn = "MicrosoftSQLServer", uid = "SA",
pwd = Sys.getenv("sqlServerPass"))
"test" %in% dbListTables(con)
#> [1] TRUE
dbExistsTable(con, "test")
#> [1] TRUE
dbGetQuery(con, "SELECT * FROM test")
#> Error in eval(expr, envir, enclos): nanodbc/nanodbc.cpp:1711: 00000
#> [Microsoft][ODBC Driver 18 for SQL Server][SQL Server]Invalid object name 'test'.
"test2" %in% dbListTables(con)
#> [1] TRUE
dbExistsTable(con, "test2")
#> [1] TRUE
dbGetQuery(con, "SELECT * FROM test2")
#> Error in eval(expr, envir, enclos): nanodbc/nanodbc.cpp:1711: 00000
#> [Microsoft][ODBC Driver 18 for SQL Server][SQL Server]Invalid object name 'test2'.
Created on 2024-04-10 with reprex v2.1.0
With CRAN, both "test2"
calls are FALSE
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm pretty sure that the DBI docs are underspecified here — I think they really should read a "character vector that enumerates all tables and views in the database for the active schema/catalog.".
So maybe we're blowing out the scope of this PR, but I think we should narrow down this behaviour because I think that we should be returning FALSE
for the test
calls too.
But might be easiest to discuss this live on a call with some examples?
R/driver-sql-server.R
Outdated
if (!has_catalog & !has_schema) { | ||
return(paste0(res, filter_is_table)) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (!has_catalog & !has_schema) { | |
return(paste0(res, filter_is_table)) | |
} |
I think you can just remove this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, went ahead and removed this at the time but just ran into why it was there. If !has_catalog & !has_schema
then the filter has to start with WHERE
rather than AND
. I'll go ahead and add a test there so it's more obvious in the future. da86cc4.
edits to `dbExistsTable()` should resolve possible slowdown when the non-synonym table does exist
R/driver-sql-server.R
Outdated
catalog = DB.name, | ||
[schema] = Sch.name, | ||
name = Syn.name |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You might be able to simplify this query by using the DB_ID()
and SCHEMA_ID()
functions to convert schema/database names directly to IDs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Was able to rework using SCHEMA_NAME()
and respecting synonyms(catalog_name)
, so no more joins and a good bit simpler now.
@@ -73,11 +73,11 @@ setMethod("dbExistsTable", c("Microsoft SQL Server", "character"), | |||
stopifnot(length(name) == 1) | |||
if (isTempTable(conn, name, ...)) { | |||
query <- paste0("SELECT OBJECT_ID('tempdb..", name, "')") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmmmm, is dbListTables()
correct here? i.e. SELECT * FROM test2
won't work (IIUC) because it's not in the current schema. So should it actually be listed?
since each `sys.synonyms` references only the synonyms that live inside of the database/catalog it's inside of, only query the (fully qualified, if not the one from the active database) `sys.synonyms` in the `catalog_name`. if not `catalog_name` is specified, respect the active database.
Let's table this PR until at least after the upcoming release. Will mark as draft for now. |
Closes #221.
This PR modifies/adds SQL Server methods for
dbListTables()
,dbExistsTable()
, andodbcListObjects()
that include synonyms in output. This also means that synonyms will show up in the Connections pane.Note that this PR makes no changes todbListTables()
, which is noted in the original issue.[EDITs: update PR scope]