Skip to content

Commit 49b70ea

Browse files
authored
prevent application of inline markup within rethrown messages (#870)
* prevent application of inline markup when rethrown messages fail to parse * handle case when `parse_database_error()` succeeds
1 parent deb390b commit 49b70ea

File tree

4 files changed

+47
-1
lines changed

4 files changed

+47
-1
lines changed

NEWS.md

+3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# odbc (development version)
22

3+
* Addressed issue where error messages rethrown from some drivers would be
4+
garbled when the raw error message contained curly brackets
5+
(#859 by @simonpcouch).
36

47
* snowflake: Runtime driver configuration checks on `MacOS` (#857).
58

R/utils.R

+16-1
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ random_name <- function(prefix = "") {
162162
rethrow_database_error <- function(msg, call = trace_back()$call[[1]]) {
163163
tryCatch(
164164
res <- parse_database_error(msg),
165-
error = function(e) cli::cli_abort(msg, call = call)
165+
error = function(e) cli::cli_abort("{msg}", call = call)
166166
)
167167

168168
cli::cli_abort(
@@ -232,6 +232,8 @@ set_database_error_names <- function(cnd_body) {
232232
return(cnd_body)
233233
}
234234

235+
cnd_body <- escape_curly_brackets(cnd_body)
236+
235237
if (is.null(names(cnd_body))) {
236238
return(
237239
set_names(cnd_body, nm = c("x", rep("*", length(cnd_body) - 1)))
@@ -247,6 +249,19 @@ set_database_error_names <- function(cnd_body) {
247249
)
248250
}
249251

252+
# Escape curly brackets before formatting with cli (#859). Do so only to
253+
# unnamed elements so that formatting is preserved for context
254+
# added with `contextualize_database_error()`.
255+
escape_curly_brackets <- function(cnd_body) {
256+
if (is.null(names(cnd_body))) {
257+
return(gsub("\\{", "{{", gsub("\\}", "}}", cnd_body)))
258+
}
259+
260+
unnamed <- names(cnd_body) == ""
261+
cnd_body[unnamed] <- gsub("\\{", "{{", gsub("\\}", "}}", cnd_body[unnamed]))
262+
cnd_body
263+
}
264+
250265
# check helpers for common odbc arguments --------------------------------------
251266
check_row.names <- function(row.names, call = caller_env()) {
252267
if (is.null(row.names) || is_scalar_logical(row.names) || is_string(row.names)) {

tests/testthat/_snaps/utils.md

+19
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,25 @@
102102
* A network-related or instance-specific error has occurred while establishing a connection to 127.0.0.1. Server is not found or not accessible. Check if instance name is correct and if SQL Server is configured to allow remote connections. For more information see SQL Server Books Online.
103103
i From 'nanodbc/nanodbc.cpp:1147'.
104104

105+
---
106+
107+
Code
108+
rethrow_database_error(msg, call = NULL)
109+
Condition
110+
Error:
111+
! ODBC failed with error S1000 from [SAP AG][LIBODBCHDB DLL][HDBODBC][SAP AG][LIBODBCHDB SO][HDBODBC][89013].
112+
x General error;403 internal error: Error opening the cursor for the remote database <***.***> Connection not open;-10807 Connection down: Socket closed by peer {***.**.*.**:***** -> ***.**.***.**:***** TenantName:(none) SiteVolumeID:1:3 SiteType:PRIMARY ConnectionID:****** SessionID:************}
113+
* <SQL> 'SELECT DISTINCT "po_id", ***CENSORED***'
114+
i From 'nanodbc/nanodbc.cpp:1722'.
115+
116+
---
117+
118+
Code
119+
rethrow_database_error(msg, call = NULL)
120+
Condition
121+
Error:
122+
! `parse_database_error()` will not {be able to parse this}, but it should still be successfully rethrown as-is.
123+
105124
# check_row.names()
106125

107126
Code

tests/testthat/test-utils.R

+9
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,15 @@ test_that("parse_database_error() works with messages from the wild", {
8181
[Microsoft][ODBC Driver 18 for SQL Server]TCP Provider: Error code 0x2726
8282
[Microsoft][ODBC Driver 18 for SQL Server]A network-related or instance-specific error has occurred while establishing a connection to 127.0.0.1. Server is not found or not accessible. Check if instance name is correct and if SQL Server is configured to allow remote connections. For more information see SQL Server Books Online. "
8383
expect_snapshot(error = TRUE, rethrow_database_error(msg, call = NULL))
84+
85+
# message contains brackets that may be interpreted as inline markup (#859)
86+
msg <- "nanodbc/nanodbc.cpp:1722: S1000
87+
[SAP AG][LIBODBCHDB DLL][HDBODBC] General error;403 internal error: Error opening the cursor for the remote database <***.***> [SAP AG][LIBODBCHDB SO][HDBODBC] Connection not open;-10807 Connection down: [89013] Socket closed by peer {***.**.*.**:***** -> ***.**.***.**:***** TenantName:(none) SiteVolumeID:1:3 SiteType:PRIMARY ConnectionID:****** SessionID:************}
88+
<SQL> 'SELECT DISTINCT \"po_id\", ***CENSORED***'"
89+
expect_snapshot(error = TRUE, rethrow_database_error(msg, call = NULL))
90+
91+
msg <- "`parse_database_error()` will not {be able to parse this}, but it should still be successfully rethrown as-is."
92+
expect_snapshot(error = TRUE, rethrow_database_error(msg, call = NULL))
8493
})
8594

8695
test_that("set_database_error_names() works (#840)", {

0 commit comments

Comments
 (0)