R/kql-escape.R

Defines functions kql_escape_ident_q kql_escape_logical kql_escape_ident kql_escape_string kql_quote build_kql kql_vector escape.list escape.kql escape.NULL escape.integer64 escape.integer escape.double escape.character escape.POSIXt escape.Date escape.factor escape.logical escape.ident_q escape.ident escape

Documented in build_kql escape kql_escape_ident kql_escape_ident_q kql_escape_logical kql_escape_string kql_quote kql_vector

#' Escape/quote a string.
#'
#' @param x An object to escape. Existing kql vectors will be left as is,
#'   character vectors are escaped with single quotes, numeric vectors have
#'   trailing `.0` added if they're whole numbers, identifiers are
#'   escaped with double quotes.
#' @param parens,collapse Controls behaviour when multiple values are supplied.
#'   `parens` should be a logical flag, or if `NA`, will wrap in
#'   parens if length > 1.
#'
#'   Default behaviour: lists are always wrapped in parens and separated by
#'   commas, identifiers are separated by commas and never wrapped,
#'   atomic vectors are separated by spaces and wrapped in parens if needed.
#' @export
escape <- function(x, parens = NA, collapse = " ")
{
    UseMethod("escape")
}

#' @export
escape.ident <- function(x, parens = FALSE, collapse = ", ")
{
    y <- kql_escape_ident(x)
    kql_vector(y, parens, collapse)
}

#' @export
escape.ident_q <- function(x, parens = FALSE, collapse = ", ")
{
    y <- kql_escape_ident_q(x)
    kql_vector(y, parens, collapse)
}

#' @export
escape.logical <- function(x, parens = NA, collapse = ", ")
{
    kql_vector(kql_escape_logical(x), parens, collapse)
}

#' @export
escape.factor <- function(x, parens = NA, collapse = ", ")
{
    escape(as.character(x), parens = parens, collapse = collapse)
}

#' @export
escape.Date <- function(x, parens = NA, collapse = ", ")
{
    escape(as.character(x), parens = parens, collapse = collapse)
}

#' @export
escape.POSIXt <- function(x, parens = NA, collapse = ", ")
{
    x <- strftime(x, "%Y-%m-%dT%H:%M:%OSZ", tz = "UTC")
    escape.character(x, parens = parens, collapse = collapse)
}

#' @export
escape.character <- function(x, parens = NA, collapse = ", ")
{
    # Kusto doesn't support null strings, instead use empty string
    out <- x
    out[is.na(x)] <- ""
    out[is.null(x)] <- ""
    kql_vector(kql_escape_string(out), parens, collapse)
}

#' @export
escape.double <- function(x, parens = NA, collapse = ", ")
{
    out <- as.character(x)
    out[is.na(x)] <- "real(null)"
    inf <- is.infinite(x)
    out[inf & x > 0] <- "'real(+inf)'"
    out[inf & x < 0] <- "'real(-inf)'"

    kql_vector(out, parens, collapse)
}

#' @export
escape.integer <- function(x, parens = NA, collapse = ", ")
{
    x[is.na(x)] <- "int(null)"
    kql_vector(x, parens, collapse)
}

#' @export
escape.integer64 <- function(x, parens = NA, collapse = ", ")
{
    x <- as.character(x)
    x[is.na(x)] <- "long(null)"
    kql_vector(x, parens, collapse)
}

#' @export
escape.NULL <- function(x, parens = NA, collapse = " ")
{
    kql("null")
}

#' @export
escape.kql <- function(x, parens = NULL, collapse = NULL)
{
    kql_vector(x, isTRUE(parens), collapse)
}

#' @export
escape.list <- function(x, parens = TRUE, collapse = ", ")
{
    pieces <- vapply(x, escape, character(1))
    kql_vector(pieces, parens, collapse)
}

#' @export
#' @rdname escape
kql_vector <- function(x, parens = NA, collapse = " ")
{
    if (is_empty(x))
    {
        if (!is.null(collapse))
            return(if (isTRUE(parens)) kql("()") else kql(""))
        else
            return(kql())
    }

    if (is.na(parens))
        parens <- length(x) > 1L

    x <- paste(x, collapse = collapse)

    if (parens)
        x <- paste0("(", x, ")")

    kql(x)
}

#' Build a KQL string.
#' @param ... input to convert to KQL. Use [kql()] to preserve
#'   user input as is (dangerous), and [ident()] to label user
#'   input as kql identifiers (safe)
#' @param .env the environment in which to evaluate the arguments. Should not
#'   be needed in typical use.
#' @export
build_kql <- function(..., .env = parent.frame())
{
    escape_expr <- function(x) {
        # If it's a string, leave it as is
        if (is.character(x)) return(x)

        val <- eval_bare(x, .env)
        # Skip nulls, so you can use if statements like in paste
        if (is.null(val)) return("")

        escape(val)
    }

    pieces <- vapply(dots(...), escape_expr, character(1))
    kql(paste0(pieces, collapse = ""))
}

#' Helper function for quoting kql elements.
#'
#' If the quote character is present in the string, it will be doubled.
#' `NA`s will be replaced with NULL.
#'
#' @export
#' @param x Character vector to escape.
#' @param quote Single quoting character.
#' @export
#' @keywords internal
#' @examples
#' kql_quote("abc", "'")
#' kql_quote("I've had a good day", "'")
#' kql_quote(c("abc", NA), "'")
kql_quote <- function(x, quote)
{
    if (is_empty(x)) return(x)

    y <- gsub(quote, paste0(quote, quote), x, fixed = TRUE)
    y <- paste0(quote, y, quote)
    y[is.na(x)] <- "NULL"
    names(y) <- names(x)
    y
}

#' Escape a Kusto string by single-quoting
#' @param x A string to escape
#' @export
kql_escape_string <- function(x)
{
    kql_quote(x, "'")
}

#' Escape a Kusto identifier with \[' '\]
#' @param x An identifier to escape
#' @export
kql_escape_ident <- function(x)
{
    if(!is_empty(x))
        paste0("[", kql_escape_string(x), "]")
    else x
}

#' Escape a Kusto logical value.
#' Converts TRUE/FALSE to true / false
#' @param x A logical value to escape
#' @export
kql_escape_logical <- function(x)
{
    y <- tolower(as.character(x))
    y[is.na(x)] <- "null"
    y
}

#' Pass through an already-escaped Kusto identifier
#' @param x An identifier to pass through
#' @export
kql_escape_ident_q <- function(x)
{
    x
}
cloudyr/AzureKusto documentation built on Oct. 15, 2023, 5:42 p.m.