Nothing
###############################################################################@
## ClickHouseHTTPConnection----
#' ClickHouseHTTPConnection class.
#'
#' @export
#'
setClass(
"ClickHouseHTTPConnection",
contains = "DBIConnection",
slots = list(
host = "character",
port = "integer",
user = "character",
password = "function",
https = "logical",
ssl_verifypeer = "logical",
host_path = "character",
session = "character",
convert_uint = "logical",
extended_headers = "list",
reset_handle = "logical",
settings = "character"
)
)
###############################################################################@
## dbIsValid ----
##
setMethod(
"dbIsValid",
"ClickHouseHTTPConnection",
function(dbObj, ...) {
toRet <- try(.check_db_session(dbObj), silent = TRUE)
if (inherits(toRet, "try-error")) {
v <- FALSE
attr(v, "m") <- as.character(toRet)
toRet <- v
}
if (!toRet) {
warning(attr(toRet, "m"))
toRet <- as.logical(toRet)
}
return(toRet)
}
)
###############################################################################@
## dbDisconnect ----
##
setMethod(
"dbDisconnect",
"ClickHouseHTTPConnection",
function(conn, ...) {
xn <- deparse(substitute(conn))
conn@session <- paste0(conn@session, "off")
assign(xn, conn, envir = parent.frame(n = 1))
return(invisible(TRUE))
}
)
###############################################################################@
## dbSendQuery ----
#' Send SQL query to ClickHouse
#'
#' @param conn a ClickHouseHTTPConnection object created
#' with [`dbConnect()`][ClickHouseHTTPDriver-class]
#' @param statement the SQL query statement
#' @param format the format used by ClickHouse to send the results.
#' Two formats are supported:
#' "Arrow" (default) and "TabSeparatedWithNamesAndTypes"
#' @param file a path to a file to send along the query (default: NA)
#' @param ... Other parameters passed on to methods
#'
#' @return A ClickHouseHTTPResult object
#'
#' @details Both format have their pros and cons:
#'
#' - **Arrow** (default):
#' - fast for long tables but slow for wide tables
#' - fast with Array columns
#' - Date and DateTime columns are returned as UInt16 and UInt32
#' respectively: by default, ClickHouseHTTP interpret them as Date and
#' POSIXct columns but cannot make the difference with actual UInt16 and
#' UInt32
#'
#' - **TabSeparatedWithNamesAndTypes**:
#' - in general faster than Arrow
#' - fast for wide tables but slow for long tables
#' - slow with Array columns
#' - Special characters are not well interpreted. In such cases, the function
#' below can be useful but can also take time.
#'
#' \preformatted{
#' .sp_ch_recov <- function(x){
#' stringi::stri_replace_all_regex(
#' x,
#' c(
#' "\\\\n", "\\\\t", "\\\\r", "\\\\b",
#' "\\\\a", "\\\\f", "\\\\'", "\\\\\\\\"
#' ),
#' c("\n", "\t", "\r", "\b", "\a", "\f", "'", "\\\\"),
#' vectorize_all=FALSE
#' )
#' }
#' }
#'
#' @example supp/examples/global-example.R
#'
#' @seealso [ClickHouseHTTPResult-class]
#'
#' @rdname ClickHouseHTTPConnection-class
#'
#' @export
#'
setMethod(
"dbSendQuery",
c("ClickHouseHTTPConnection", "character"),
function(
conn,
statement,
format = c("Arrow", "TabSeparatedWithNamesAndTypes"),
file = NA,
...
) {
format <- match.arg(format)
query <- statement
if (
length(grep(
"^[[:space:]]*(SELECT|DESC|DESCRIBE|SHOW|WITH|EXISTS)[[:space:]]+",
statement,
value = T,
ignore.case = TRUE
)) ==
1
) {
query = paste(query, "FORMAT", format)
}
r <- .send_query(
dbc = conn,
query = query,
file = file
)
v <- .query_success(r)
if (!v) {
m <- attr(v, "m")
if (length(grep("Arrow data format", m)) > 0) {
m <- paste0(
m,
'\n\n Issue with Arrow data format: ',
'try using format="TabSeparatedWithNamesAndTypes", ',
'or converting unsupported data type in the query ',
'(e.g. "SELECT toString(uuidColName) as uuidColName")'
)
}
stop(m)
}
resEnv <- new.env(parent = emptyenv())
resEnv$content <- if (httr2::resp_has_body(r)) {
httr2::resp_body_raw(r)
} else {
raw(0)
}
resEnv$ch_summary <- lapply(
jsonlite::fromJSON(httr2::resp_header(r, "x-clickhouse-summary")),
as.numeric
)
resEnv$fetched <- FALSE
return(new(
"ClickHouseHTTPResult",
sql = statement,
env = resEnv,
conn = conn,
format = format
))
}
)
###############################################################################@
## show ----
##
setMethod("show", "ClickHouseHTTPConnection", function(object) {
cat("<ClickHouseHTTPConnection>\n")
if (DBI::dbIsValid(object)) {
cat(sprintf(
" %s://%s@%s:%s\n",
ifelse(object@https, "https", "http"),
object@user,
object@host,
object@port
))
} else {
cat(" DISCONNECTED\n")
}
})
###############################################################################@
## dbGetInfo ----
#' Information about the ClickHouse database
#'
#' @param dbObj a ClickHouseHTTPConnection object
#'
#' @return A list with the following elements:
#' - name: "ClickHouseHTTPConnection"
#' - db.version: the version of ClickHouse
#' - uptime: ClickHouse uptime
#' - dbname: the default database
#' - username: user name
#' - host: ClickHouse host
#' - port: ClickHouse port
#' - https: Is the connection using HTTPS protocol instead of HTTP
#'
#' @rdname ClickHouseHTTPConnection-class
#'
#' @export
#'
setMethod(
"dbGetInfo",
"ClickHouseHTTPConnection",
function(dbObj, ...) {
chinfo <- DBI::dbGetQuery(
dbObj,
paste(
"SELECT version() as version, uptime() as uptime",
", currentDatabase() as database"
)
)
return(list(
name = "ClickHouseHTTPConnection",
db.version = chinfo$version,
uptime = chinfo$uptime,
dbname = chinfo$database,
username = dbObj@user,
host = dbObj@host,
port = dbObj@port,
https = dbObj@https
))
}
)
###############################################################################@
## dbListTables ----
#' List tables in ClickHouse
#'
#' @param conn a ClickHouseHTTPConnection object created
#' with [`dbConnect()`][ClickHouseHTTPDriver-class]
#' @param database the database to consider. If NA (default), the default
#' database or the one in use in the session (if a session is defined).
#' @param ... Other parameters passed on to methods
#'
#' @return A vector of character with table names.
#'
#' @rdname ClickHouseHTTPConnection-class
#'
setMethod(
"dbListTables",
"ClickHouseHTTPConnection",
function(conn, database = NA, ...) {
if (is.na(database)) {
qdb <- ""
} else {
qdb <- paste(" FROM", DBI::dbQuoteIdentifier(conn, database))
}
return(
as.character(DBI::dbGetQuery(
conn,
sprintf(
"SHOW TABLES%s",
qdb
)
)[[1]])
)
}
)
###############################################################################@
## dbDataType ----
##
setMethod(
"dbDataType",
"ClickHouseHTTPConnection",
function(dbObj, obj, ...) {
return(DBI::dbDataType(ClickHouseHTTP(), obj))
},
valueClass = "character"
)
###############################################################################@
## dbQuoteIdentifier.character ----
##
setMethod(
"dbQuoteIdentifier",
c("ClickHouseHTTPConnection", "character"),
function(conn, x, ...) {
if (anyNA(x)) {
stop("Input to dbQuoteIdentifier must not contain NA.")
} else {
x <- gsub('\\', '\\\\', x, fixed = TRUE)
x <- gsub('`', '\\`', x, fixed = TRUE)
return(DBI::SQL(paste0('`', x, '`')))
}
}
)
###############################################################################@
## dbQuoteIdentifier.SQL ----
##
setMethod(
"dbQuoteIdentifier",
c("ClickHouseHTTPConnection", "SQL"),
function(conn, x, ...) {
return(x)
}
)
###############################################################################@
## dbQuoteString.character ----
##
setMethod(
"dbQuoteString",
c("ClickHouseHTTPConnection", "character"),
function(conn, x, ...) {
x <- gsub('\\', '\\\\', x, fixed = TRUE)
x <- gsub("'", "\\'", x, fixed = TRUE)
return(DBI::SQL(ifelse(is.na(x), "NULL", paste0("'", x, "'"))))
}
)
###############################################################################@
## dbQuoteString.SQL ----
##
setMethod(
"dbQuoteString",
c("ClickHouseHTTPConnection", "SQL"),
function(conn, x, ...) {
return(x)
}
)
###############################################################################@
## dbExistsTable ----
#' Does a table exist?
#'
#' @param conn a ClickHouseHTTPConnection object created
#' with [`dbConnect()`][ClickHouseHTTPDriver-class]
#' @param name the table name
#' @param database the database to consider. If NA (default), the default
#' database or the one in use in the session (if a session is defined).
#' @param ... Other parameters passed on to methods
#'
#' @return A logical.
#'
#' @rdname ClickHouseHTTPConnection-class
#'
setMethod(
"dbExistsTable",
c("ClickHouseHTTPConnection", "character"),
function(conn, name, database = NA, ...) {
qname <- DBI::dbQuoteIdentifier(conn, name)
if (is.na(database)) {
qdb <- ""
} else {
qdb <- paste0(DBI::dbQuoteIdentifier(conn, database), ".")
}
return(DBI::dbGetQuery(
conn,
sprintf(
"EXISTS %s%s",
qdb,
qname
)
)[[1]])
}
)
###############################################################################@
## dbReadTable ----
#' Read database tables as data frames
#'
#' @param conn a ClickHouseHTTPConnection object created
#' with [`dbConnect()`][ClickHouseHTTPDriver-class]
#' @param name the table name
#' @param database the database to consider. If NA (default), the default
#' database or the one in use in the session (if a session is defined).
#' @param ... Other parameters passed on to methods
#'
#' @return A data.frame.
#'
#' @rdname ClickHouseHTTPConnection-class
#'
setMethod(
"dbReadTable",
c("ClickHouseHTTPConnection", "character"),
function(conn, name, database = NA, ...) {
qname <- DBI::dbQuoteIdentifier(conn, name)
if (is.na(database)) {
qdb <- ""
} else {
qdb <- paste0(DBI::dbQuoteIdentifier(conn, database), ".")
}
## Identify UUID columns for converting them in String
coltype <- DBI::dbGetQuery(
conn,
paste0("DESCRIBE TABLE ", qdb, qname)
)[, c("name", "type")]
if (any(coltype$type == "UUID")) {
query <- paste(
"SELECT",
paste(
ifelse(
coltype$type == "UUID",
sprintf(
"toString(%s) as %s",
DBI::dbQuoteIdentifier(conn, coltype$name),
DBI::dbQuoteIdentifier(conn, coltype$name)
),
DBI::dbQuoteIdentifier(conn, coltype$name)
),
collapse = ", "
)
)
} else {
query <- "SELECT *"
}
return(DBI::dbGetQuery(
conn,
paste(query, "FROM ", qdb, qname),
...
))
}
)
###############################################################################@
## dbListFields ----
#' List field names of a table
#'
#' @param conn a ClickHouseHTTPConnection object created
#' with [`dbConnect()`][ClickHouseHTTPDriver-class]
#' @param name the table name
#' @param database the database to consider. If NA (default), the default
#' database or the one in use in the session (if a session is defined).
#' @param ... Other parameters passed on to methods
#'
#' @return A vector of character with column names.
#'
#' @rdname ClickHouseHTTPConnection-class
#'
setMethod(
"dbListFields",
c("ClickHouseHTTPConnection", "character"),
function(conn, name, database = NA, ...) {
qname <- DBI::dbQuoteIdentifier(conn, name)
if (is.na(database)) {
qdb <- ""
} else {
qdb <- paste0(DBI::dbQuoteIdentifier(conn, database), ".")
}
return(DBI::dbGetQuery(conn, paste0("DESCRIBE TABLE ", qdb, qname))$name)
}
)
###############################################################################@
## dbRemoveTable ----
#' Remove a table from the database
#'
#' @param conn a ClickHouseHTTPConnection object created
#' with [`dbConnect()`][ClickHouseHTTPDriver-class]
#' @param name the table name
#' @param database the database to consider. If NA (default), the default
#' database or the one in use in the session (if a session is defined).
#' @param ... Other parameters passed on to methods
#'
#' @return `invisible(TRUE)`
#'
#' @rdname ClickHouseHTTPConnection-class
#'
setMethod(
"dbRemoveTable",
"ClickHouseHTTPConnection",
function(conn, name, database = NA, ...) {
qname <- DBI::dbQuoteIdentifier(conn, name)
if (is.na(database)) {
qdb <- ""
} else {
qdb <- paste0(DBI::dbQuoteIdentifier(conn, database), ".")
}
DBI::dbSendQuery(conn, paste0("DROP TABLE ", qdb, qname))
return(invisible(TRUE))
}
)
###############################################################################@
## dbCreateTable ----
#' Create a table in ClickHouse
#'
#' @param conn a ClickHouseHTTPConnection object created
#' with [`dbConnect()`][ClickHouseHTTPDriver-class]
#' @param name the name of the table to create
#' @param fields a character vector with the name of the fields and their
#' ClickHouse type
#' (e.g.
#' `c("text_col String", "num_col Nullable(Float64)", "nul_col Array(Int32)")`
#' )
#' @param database the database to consider. If NA (default), the default
#' database or the one in use in the session (if a session is defined).
#' @param engine the ClickHouse table engine as described in ClickHouse
#' [documentation](https://clickhouse.com/docs/en/engines/table-engines/).
#' Examples:
#' - `"TinyLog"` (default)
#' - `"MergeTree() ORDER BY (expr)"`
#' (expr generally correspond to fields separated by ",")
#' @param overwrite if TRUE and if a table with the same name exists,
#' then it is deleted before creating the new one (default: FALSE)
#' @param row.names unsupported parameter (add for compatibility reason)
#' @param temporary unsupported parameter (add for compatibility reason)
#' @param ... for compatibility purpose; not used
#'
#' @return dbCreateTable() returns TRUE, invisibly.
#'
#' @example supp/examples/global-example.R
#'
#' @rdname ClickHouseHTTPConnection-class
#'
#' @export
#'
setMethod(
"dbCreateTable",
"ClickHouseHTTPConnection",
function(
conn,
name,
fields,
database = NA,
engine = "TinyLog",
overwrite = FALSE,
...,
row.names = NULL,
temporary = FALSE
) {
qname <- DBI::dbQuoteIdentifier(conn, name)
if (overwrite && DBI::dbExistsTable(conn, qname)) {
DBI::dbRemoveTable(conn, qname, database = database)
}
if (is.na(database)) {
qdb <- ""
} else {
qdb <- paste0(DBI::dbQuoteIdentifier(conn, database), ".")
}
query <- paste(
sprintf(
"CREATE TABLE %s (",
paste0(qdb, qname)
),
paste(fields, collapse = ",\n"),
") ENGINE =",
engine
)
DBI::dbSendQuery(conn, query)
return(invisible(TRUE))
}
)
###############################################################################@
## dbAppendTable ----
#' Insert rows into a table
#'
#' @param conn a ClickHouseHTTPConnection object created
#' with [`dbConnect()`][ClickHouseHTTPDriver-class]
#' @param name the table name
#' @param value a data.frame
#' @param database the database to consider. If NA (default), the default
#' database or the one in use in the session (if a session is defined).
#' @param row.names unsupported parameter (add for compatibility reason)
#' @param ... Other parameters passed on to methods
#'
#' @return The number of rows written, invisibly.
#'
#' @rdname ClickHouseHTTPConnection-class
#'
setMethod(
"dbAppendTable",
"ClickHouseHTTPConnection",
function(
conn,
name,
value,
database = NA,
...,
row.names = NULL
) {
qname <- DBI::dbQuoteIdentifier(conn, name)
if (is.na(database)) {
qdb <- ""
} else {
qdb <- paste0(DBI::dbQuoteIdentifier(conn, database), ".")
}
tmpf <- tempfile()
on.exit(file.remove(tmpf))
arrow::write_feather(value, tmpf)
query <- sprintf("INSERT INTO %s FORMAT Arrow", paste0(qdb, qname))
res <- DBI::dbSendQuery(conn = conn, statement = query, file = tmpf)
return(invisible(DBI::dbGetRowsAffected(res)))
}
)
###############################################################################@
## dbWriteTable ----
#' Write a table in ClickHouse
#'
#' @param conn a ClickHouseHTTPConnection object created
#' with [`dbConnect()`][ClickHouseHTTPDriver-class]
#' @param name the name of the table to create
#' @param value a data.frame
#' @param database the database to consider. If NA (default), the default
#' database or the one in use in the session (if a session is defined).
#' @param overwrite if TRUE and if a table with the same name exists,
#' then it is deleted before creating the new one (default: FALSE)
#' @param append if TRUE, the values are added to the database table if
#' it exists (default: FALSE).
#' @param engine the ClickHouse table engine as described in ClickHouse
#' [documentation](https://clickhouse.com/docs/en/engines/table-engines/).
#' Examples:
#' - `"TinyLog"` (default)
#' - `"MergeTree() ORDER BY (expr)"`
#' (expr generally correspond to fields separated by ",")
#' @param ... Other parameters passed on to methods
#'
#' @return TRUE; called for side effects
#'
#' @example supp/examples/global-example.R
#'
#' @rdname ClickHouseHTTPConnection-class
#'
#' @export
#'
setMethod(
"dbWriteTable",
"ClickHouseHTTPConnection",
function(
conn,
name,
value,
database = NA,
overwrite = FALSE,
append = FALSE,
engine = "TinyLog",
...
) {
if (overwrite && append) {
stop("overwrite and append cannot be both TRUE")
}
if (!append) {
cdef <- unlist(lapply(
value,
function(x) {
type <- DBI::dbDataType(conn, x)
if (any(is.na(x))) {
type <- sprintf("Nullable(%s)", type)
}
return(type)
}
))
fields <- paste(DBI::dbQuoteIdentifier(conn, names(cdef)), cdef)
DBI::dbCreateTable(
conn = conn,
name = name,
database = database,
fields = fields,
engine = engine,
overwrite = overwrite
)
}
toRet <- DBI::dbAppendTable(
conn = conn,
name = name,
database = database,
value = value
)
return(invisible(toRet))
}
)
###############################################################################@
## Helpers ----
.build_http_req <- function(
host,
port,
https,
host_path,
session,
session_timeout = NA,
settings,
query = ""
) {
use_session <- !is.na(session)
if (use_session) {
to_ret <- sprintf(
"%s://%s:%s/%s?session_id=%s&session_check=%s%s%s%s",
ifelse(https, "https", "http"),
host,
port,
ifelse(is.na(host_path), "", paste0(host_path, "/")),
session,
as.integer(is.na(session_timeout)),
ifelse(
is.na(session_timeout),
"",
sprintf("&session_timeout=%s", as.integer(session_timeout))
),
ifelse(
settings == "",
"",
paste0("&", settings)
),
ifelse(
is.na(query) || query == "",
"",
sprintf("&query=%s", utils::URLencode(query))
)
)
} else {
to_ret <- sprintf(
"%s://%s:%s/%s%s%s",
ifelse(https, "https", "http"),
host,
port,
ifelse(is.na(host_path), "", paste0(host_path, "/")),
ifelse(
settings == "",
"",
paste0("?", settings)
),
ifelse(
is.na(query) || query == "",
"",
sprintf(
"%squery=%s",
ifelse(
settings == "",
"?",
"&"
),
utils::URLencode(query)
)
)
)
}
return(to_ret)
}
.send_query <- function(
dbc,
query,
file = NA,
session_timeout = NA
) {
if (is.na(file)) {
qbody <- query
query <- ""
} else {
query <- utils::URLencode(query)
}
## Add authentication headers ----
qheaders <- list(
'X-ClickHouse-User' = dbc@user,
'X-ClickHouse-Key' = dbc@password()
)
## Add other headers ----
qheaders <- c(qheaders, dbc@extended_headers)
url <- .build_http_req(
host = dbc@host,
port = dbc@port,
https = dbc@https,
host_path = dbc@host_path,
session = dbc@session,
session_timeout = session_timeout,
settings = dbc@settings,
query = query
)
req <- do.call(
httr2::req_headers,
c(list(httr2::request(url)), qheaders)
) |>
httr2::req_options(ssl_verifypeer = as.integer(dbc@ssl_verifypeer)) |>
httr2::req_error(is_error = \(r) FALSE)
if (!is.na(file)) {
req <- httr2::req_body_file(req, path = file)
} else {
req <- httr2::req_body_raw(req, body = qbody)
}
if (dbc@reset_handle) {
req <- httr2::req_options(req, fresh_connect = TRUE)
}
httr2::req_perform(req)
}
.query_success <- function(r) {
exc <- httr2::resp_header(r, "x-clickhouse-exception-code")
if (httr2::resp_status(r) >= 300 || !is.null(exc)) {
if (!is.null(exc)) {
m <- rawToChar(httr2::resp_body_raw(r))
} else {
m <- sprintf(
"Connection error. Status code: %s",
httr2::resp_status(r)
)
}
toRet <- FALSE
attr(toRet, "m") <- m
} else {
toRet <- TRUE
}
return(toRet)
}
.check_db_session <- function(
dbc,
session_timeout = NA
) {
r <- .send_query(
dbc = dbc,
query = "SELECT currentUser() as user",
session_timeout = session_timeout
)
toRet <- .query_success(r)
if (toRet) {
attr(toRet, "user") <- sub("\n$", "", rawToChar(httr2::resp_body_raw(r)))
}
return(toRet)
}
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.