diff --git a/.classpath b/.classpath index 48775fc5..d4c4be3b 100644 --- a/.classpath +++ b/.classpath @@ -1,6 +1,6 @@ - + diff --git a/DESCRIPTION b/DESCRIPTION index 3728bd59..ecd6d242 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,8 +1,8 @@ Package: SqlRender Type: Package Title: Rendering Parameterized SQL and Translation to Dialects -Version: 1.6.9 -Date: 2020-09-26 +Version: 1.7.0 +Date: 2020-10-21 Authors@R: c( person("Martijn", "Schuemie", , "schuemie@ohdsi.org", role = c("aut", "cre")), person("Marc", "Suchard", role = c("aut")) @@ -16,7 +16,8 @@ VignetteBuilder: knitr URL: https://ohdsi.github.io/SqlRender, https://github.com/OHDSI/SqlRender BugReports: https://github.com/OHDSI/SqlRender/issues Imports: - rJava + rJava, + rlang Suggests: testthat, knitr, diff --git a/NEWS.md b/NEWS.md index 4d05d65e..0d36124b 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,6 +1,10 @@ -SqlRender 1.6.9 +SqlRender 1.7.0 =============== +Changes: + +1. Deprecating `oracleTempSchema` argument in various functions in favor of `tempEmulationSchema` schema, which can also be set globally using the `sqlRenderTempEmulationSchema` option. + Bugfixes: 1. Fixed translation of CTE without FROM or UNION in BigQuery. diff --git a/R/HelperFunctions.R b/R/HelperFunctions.R index 0c57e203..f9623e02 100644 --- a/R/HelperFunctions.R +++ b/R/HelperFunctions.R @@ -111,9 +111,12 @@ renderSqlFile <- function(sourceFile, targetFile, warnOnMissingParameters = TRUE #' #' @param sourceFile The source SQL file #' @param targetFile The target SQL file -#' @param targetDialect The target dialect. Currently 'oracle', 'postgresql', and 'redshift' are -#' supported -#' @param oracleTempSchema A schema that can be used to create temp tables in when using Oracle. +#' @param targetDialect The target dialect. Currently "oracle", "postgresql", "pdw", "impala", "sqlite", "netezza", "bigquery", and +#' "redshift" are supported. +#' @param oracleTempSchema DEPRECATED: use \code{tempEmulationSchema} instead. +#' @param tempEmulationSchema Some database platforms like Oracle and Impala do not truly support temp tables. To +#' emulate temp tables, provide a schema with write privileges where temp tables +#' can be created. #' #' @examples #' \dontrun{ @@ -125,9 +128,14 @@ renderSqlFile <- function(sourceFile, targetFile, warnOnMissingParameters = TRUE translateSqlFile <- function(sourceFile, targetFile, targetDialect, + tempEmulationSchema = getOption("sqlRenderTempEmulationSchema"), oracleTempSchema = NULL) { + if (!is.null(oracleTempSchema) && oracleTempSchema != "") { + rlang::warn("The 'oracleTempSchema' argument is deprecated. Use 'tempEmulationSchema' instead.", .frequency = "regularly", .frequency_id = "oracleTempSchema") + tempEmulationSchema <- oracleTempSchema + } sql <- readSql(sourceFile) - sql <- translate(sql = sql, targetDialect = targetDialect, oracleTempSchema = oracleTempSchema) + sql <- translate(sql = sql, targetDialect = targetDialect, tempEmulationSchema = tempEmulationSchema) writeSql(sql, targetFile) } @@ -150,7 +158,10 @@ translateSqlFile <- function(sourceFile, #' @param dbms The target dialect. Currently 'sql server', 'oracle', 'postgres', and #' 'redshift' are supported #' @param ... Parameter values used for \code{render} -#' @param oracleTempSchema A schema that can be used to create temp tables in when using Oracle. +#' @param oracleTempSchema DEPRECATED: use \code{tempEmulationSchema} instead. +#' @param tempEmulationSchema Some database platforms like Oracle and Impala do not truly support temp tables. To +#' emulate temp tables, provide a schema with write privileges where temp tables +#' can be created. #' @param warnOnMissingParameters Should a warning be raised when parameters provided to this function #' do not appear in the parameterized SQL that is being rendered? By default, this is TRUE. #' @@ -168,8 +179,13 @@ loadRenderTranslateSql <- function(sqlFilename, packageName, dbms = "sql server", ..., + tempEmulationSchema = getOption("sqlRenderTempEmulationSchema"), oracleTempSchema = NULL, warnOnMissingParameters = TRUE) { + if (!is.null(oracleTempSchema) && oracleTempSchema != "") { + rlang::warn("The 'oracleTempSchema' argument is deprecated. Use 'tempEmulationSchema' instead.", .frequency = "regularly", .frequency_id = "oracleTempSchema") + tempEmulationSchema <- oracleTempSchema + } pathToSql <- system.file(paste("sql/", gsub(" ", "_", dbms), sep = ""), sqlFilename, package = packageName) @@ -188,7 +204,7 @@ loadRenderTranslateSql <- function(sqlFilename, renderedSql <- render(sql = parameterizedSql[1], warnOnMissingParameters = warnOnMissingParameters, ...) if (mustTranslate) - renderedSql <- translate(sql = renderedSql, targetDialect = dbms, oracleTempSchema = oracleTempSchema) + renderedSql <- translate(sql = renderedSql, targetDialect = dbms, tempEmulationSchema = tempEmulationSchema) renderedSql } @@ -354,7 +370,7 @@ createRWrapperForSql <- function(sqlFilename, "#' @param connectionDetails\t\tAn R object of type \\code{ConnectionDetails} created using the function \\code{createConnectionDetails} in the \\code{DatabaseConnector} package.") if (hasTempTables) lines <- c(lines, - "#' @param oracleTempSchema\t\tA schema where temp tables can be created in Oracle.") + "#' @param tempEmulationSchema\t\tSome database platforms like Oracle and Impala do not truly support temp tables. To emulate temp tables, provide a schema with write privileges where temp tables can be created.") for (i in 1:nrow(functionDefinitions)) { lines <- c(lines, paste("#' @param", functionDefinitions$rParameter[i], "\t\t")) } @@ -364,7 +380,7 @@ createRWrapperForSql <- function(sqlFilename, lines <- c(lines, paste(gsub(".sql", "", sqlFilename), " <- function(connectionDetails,", sep = "")) if (hasTempTables) - lines <- c(lines, " oracleTempSchema = NULL,") + lines <- c(lines, " tempEmulationSchema = getOption(\"sqlRenderTempEmulationSchema\"),") for (i in 1:nrow(functionDefinitions)) { if (i == nrow(functionDefinitions)) end <- ") {" else end <- "," @@ -389,7 +405,7 @@ createRWrapperForSql <- function(sqlFilename, lines <- c(lines, paste(" packageName = \"", packageName, "\",", sep = "")) lines <- c(lines, " dbms = connectionDetails$dbms,") if (hasTempTables) - lines <- c(lines, " oracleTempSchema = oracleTempSchema,") + lines <- c(lines, " tempEmulationSchema = tempEmulationSchema,") for (i in 1:nrow(definitions)) { if (i == nrow(definitions)) end <- ")" else end <- "," diff --git a/R/RenderSql.R b/R/RenderSql.R index 941b3180..68c7fd97 100644 --- a/R/RenderSql.R +++ b/R/RenderSql.R @@ -119,9 +119,12 @@ renderSql <- function(sql = "", warnOnMissingParameters = TRUE, ...) { #' \code{\link{splitSql}}. #' #' @param sql The SQL to be translated -#' @param targetDialect The target dialect. Currently "oracle", "postgresql", "pdw", "impala", "netezza", "bigquery", and -#' "redshift" are supported -#' @param oracleTempSchema A schema that can be used to create temp tables in when using Oracle or Impala. +#' @param targetDialect The target dialect. Currently "oracle", "postgresql", "pdw", "impala", "sqlite", "netezza", "bigquery", and +#' "redshift" are supported. +#' @param oracleTempSchema DEPRECATED: use \code{tempEmulationSchema} instead. +#' @param tempEmulationSchema Some database platforms like Oracle and Impala do not truly support temp tables. To +#' emulate temp tables, provide a schema with write privileges where temp tables +#' can be created. #' @return #' A character string containing the translated SQL. #' @@ -131,12 +134,17 @@ renderSql <- function(sql = "", warnOnMissingParameters = TRUE, ...) { #' @export translate <- function(sql = "", targetDialect, + tempEmulationSchema = getOption("sqlRenderTempEmulationSchema"), oracleTempSchema = NULL) { + if (!is.null(oracleTempSchema) && oracleTempSchema != "") { + rlang::warn("The 'oracleTempSchema' argument is deprecated. Use 'tempEmulationSchema' instead.", .frequency = "regularly", .frequency_id = "oracleTempSchema") + tempEmulationSchema <- oracleTempSchema + } pathToReplacementPatterns <- system.file("csv", "replacementPatterns.csv", package = "SqlRender") - if (missing(oracleTempSchema) || is.null(oracleTempSchema)) { - oracleTempSchema <- rJava::.jnull() + if (missing(tempEmulationSchema) || is.null(tempEmulationSchema)) { + tempEmulationSchema <- rJava::.jnull() } else { - oracleTempSchema <- as.character(oracleTempSchema) + tempEmulationSchema <- as.character(tempEmulationSchema) } messages <- rJava::J("org.ohdsi.sql.SqlTranslate")$check(as.character(sql), as.character(targetDialect)) @@ -146,7 +154,7 @@ translate <- function(sql = "", translatedSql <- rJava::J("org.ohdsi.sql.SqlTranslate")$translateSqlWithPath(as.character(sql), as.character(targetDialect), rJava::.jnull(), - oracleTempSchema, + tempEmulationSchema, as.character(pathToReplacementPatterns)) return(translatedSql) } @@ -191,9 +199,12 @@ translateSql <- function(sql = "", #' in the SQL. #' #' @param sql The SQL to be translated -#' @param targetDialect The target dialect. Currently "oracle", "postgresql", "pdw", "impala", "netezza", "bigquery", and -#' "redshift" are supported -#' @param oracleTempSchema A schema that can be used to create temp tables in when using Oracle or Impala. +#' @param targetDialect The target dialect. Currently "oracle", "postgresql", "pdw", "impala", "sqlite", "netezza", "bigquery", and +#' "redshift" are supported. +#' @param oracleTempSchema DEPRECATED: use \code{tempEmulationSchema} instead. +#' @param tempEmulationSchema Some database platforms like Oracle and Impala do not truly support temp tables. To +#' emulate temp tables, provide a schema with write privileges where temp tables +#' can be created. #' @return #' A character vector with the translated SQL. #' @examples @@ -202,12 +213,17 @@ translateSql <- function(sql = "", #' @export translateSingleStatement <- function(sql = "", targetDialect, + tempEmulationSchema = getOption("sqlRenderTempEmulationSchema"), oracleTempSchema = NULL) { + if (!is.null(oracleTempSchema) && oracleTempSchema != "") { + rlang::warn("The 'oracleTempSchema' argument is deprecated. Use 'tempEmulationSchema' instead.", .frequency = "regularly", .frequency_id = "oracleTempSchema") + tempEmulationSchema <- oracleTempSchema + } pathToReplacementPatterns <- system.file("csv", "replacementPatterns.csv", package = "SqlRender") - if (missing(oracleTempSchema) || is.null(oracleTempSchema)) { - oracleTempSchema <- rJava::.jnull() + if (missing(tempEmulationSchema) || is.null(tempEmulationSchema)) { + tempEmulationSchema <- rJava::.jnull() } else { - oracleTempSchema <- as.character(oracleTempSchema) + tempEmulationSchema <- as.character(tempEmulationSchema) } messages <- rJava::J("org.ohdsi.sql.SqlTranslate")$check(as.character(sql), as.character(targetDialect)) @@ -217,7 +233,7 @@ translateSingleStatement <- function(sql = "", translatedSql <- rJava::J("org.ohdsi.sql.SqlTranslate")$translateSingleStatementSqlWithPath(as.character(sql), as.character(targetDialect), rJava::.jnull(), - oracleTempSchema, + tempEmulationSchema, as.character(pathToReplacementPatterns)) return(translatedSql) } diff --git a/inst/java/SqlRender.jar b/inst/java/SqlRender.jar index 3690f825..60ee020a 100644 Binary files a/inst/java/SqlRender.jar and b/inst/java/SqlRender.jar differ diff --git a/inst/shinyApps/SqlDeveloper/server.R b/inst/shinyApps/SqlDeveloper/server.R index e27b2f7c..bcd4fa5b 100644 --- a/inst/shinyApps/SqlDeveloper/server.R +++ b/inst/shinyApps/SqlDeveloper/server.R @@ -16,32 +16,25 @@ shinyServer(function(input, output, session) { }) output$target <- renderText({ - # if (!input$continuous && (input$renderTranslate == cache$clicks)) { - # return(cache$target) - # } else { - # print(paste("continuous: ", input$continuous, ", renderTranslate: ", input$renderTranslate)) - parameterValues <- list() - for (param in parameters()) { - value <- input[[param]] - if (!is.null(value)) { - parameterValues[[param]] <- value - } - } - sql <- do.call("render", append(input$source, parameterValues)) - warningString <- c() - handleWarning <- function(e) { - output$warnings <- e$message + parameterValues <- list() + for (param in parameters()) { + value <- input[[param]] + if (!is.null(value)) { + parameterValues[[param]] <- value } - oracleTempSchema <- input$oracleTempSchema - if (oracleTempSchema == "") - oracleTempSchema <- NULL - sql <- withCallingHandlers(suppressWarnings(translate(sql, targetDialect = tolower(input$dialect), oracleTempSchema = oracleTempSchema)), warning = handleWarning) - if (!is.null(warningString)) - output$warnings <- warningString - # cache$target <- sql - # cache$clicks <- input$renderTranslate - return(sql) - # } + } + sql <- do.call("render", append(input$source, parameterValues)) + warningString <- c() + handleWarning <- function(e) { + output$warnings <- e$message + } + tempEmulationSchema <- input$tempEmulationSchema + if (tempEmulationSchema == "") + tempEmulationSchema <- NULL + sql <- withCallingHandlers(suppressWarnings(translate(sql, targetDialect = tolower(input$dialect), tempEmulationSchema = tempEmulationSchema)), warning = handleWarning) + if (!is.null(warningString)) + output$warnings <- warningString + return(sql) }) output$parameterInputs <- renderUI({ diff --git a/inst/shinyApps/SqlDeveloper/ui.R b/inst/shinyApps/SqlDeveloper/ui.R index 729c3252..a896a47d 100644 --- a/inst/shinyApps/SqlDeveloper/ui.R +++ b/inst/shinyApps/SqlDeveloper/ui.R @@ -33,8 +33,8 @@ dashboardPage( box(background = "light-blue", h4("Target dialect"), width = NULL, selectInput("dialect", NULL, choices = c("BigQuery", "Impala", "Netezza", "Oracle", "PDW", "PostgreSQL", "RedShift", "SQL Server", "SQLite", "Hive"), selected = "SQL Server"), - h4("Oracle temp schema"), - textInput("oracleTempSchema", NULL), + h4("Temp emulation schema"), + textInput("tempEmulationSchema", NULL), h4("Parameters"), uiOutput("parameterInputs"), textOutput("warnings") diff --git a/java/org/ohdsi/sql/MainClass.java b/java/org/ohdsi/sql/MainClass.java index 2cbe8a46..9f865004 100644 --- a/java/org/ohdsi/sql/MainClass.java +++ b/java/org/ohdsi/sql/MainClass.java @@ -43,7 +43,7 @@ public static void main(String[] args) { List parameters = new ArrayList(); List values = new ArrayList(); for (int j = i + 1; j < args.length - 1; j += 2) { - if (args[j].equals("-translate") || args[j].equals("-oracle_temp_schema") || args[j].equals("-session_id")) + if (args[j].equals("-translate") || args[j].equals("-temp_emulation_schema") || args[j].equals("-session_id")) break; parameters.add(args[j]); values.add(args[j + 1]); @@ -52,10 +52,10 @@ public static void main(String[] args) { } // Translate - String oracleTempSchema = null; + String tempEmulationSchema = null; for (int i = 2; i < args.length - 1; i++) - if (args[i].equals("-oracle_temp_schema")) { - oracleTempSchema = args[i + 1]; + if (args[i].equals("-temp_emulation_schema")) { + tempEmulationSchema = args[i + 1]; break; } String sessionId = null; @@ -68,7 +68,7 @@ public static void main(String[] args) { for (int i = 2; i < args.length - 1; i++) if (args[i].equals("-translate")) { String targetDialect = args[i + 1]; - sql = SqlTranslate.translateSql(sql, targetDialect, sessionId, oracleTempSchema); + sql = SqlTranslate.translateSql(sql, targetDialect, sessionId, tempEmulationSchema); } writeFile(sql, args[1]); } @@ -83,7 +83,7 @@ private static void printUsage() { System.out.println("Options"); System.out.println(" -render { } ... Render the SQL with a list of parameter name-value pairs"); System.out.println(" -translate Translate the input SQL to the target dialect"); - System.out.println(" -oracle_temp_schema When translating to Oracle SQL, use this schema to emulate temp tables"); + System.out.println(" -temp_emulation_schema When translating to platforms that don't support tempt table, use this schema to emulate temp tables"); System.out.println(" -session_id When translating to Oracle SQL, use this ID to make emulated temp table names unique. Should be 8 chars long"); System.out.println(""); System.out.println("Examples"); diff --git a/java/org/ohdsi/sql/SqlTranslate.java b/java/org/ohdsi/sql/SqlTranslate.java index ea1611bd..0417fbf4 100644 --- a/java/org/ohdsi/sql/SqlTranslate.java +++ b/java/org/ohdsi/sql/SqlTranslate.java @@ -34,19 +34,19 @@ import java.util.regex.Pattern; public class SqlTranslate { - public static int SESSION_ID_LENGTH = 8; - public static int MAX_ORACLE_TABLE_NAME_LENGTH = 30; - private static Map> targetToReplacementPatterns = null; - private static ReentrantLock lock = new ReentrantLock(); - private static Random random = new Random(); - private static String globalSessionId = null; - private static String SOURCE_DIALECT = "sql server"; - private static String BIG_QUERY = "bigquery"; - private static String IMPALA = "impala"; + public static int SESSION_ID_LENGTH = 8; + public static int MAX_ORACLE_TABLE_NAME_LENGTH = 30; + private static Map> targetToReplacementPatterns = null; + private static ReentrantLock lock = new ReentrantLock(); + private static Random random = new Random(); + private static String globalSessionId = null; + private static String SOURCE_DIALECT = "sql server"; + private static String BIG_QUERY = "bigquery"; + private static String IMPALA = "impala"; protected static class Block extends StringUtils.Token { - public boolean isVariable; - public String regEx; + public boolean isVariable; + public String regEx; public Block(StringUtils.Token other) { super(other); @@ -55,10 +55,10 @@ public Block(StringUtils.Token other) { } protected static class MatchedPattern { - public int start; - public int end; - public int startToken; - public Map variableToValue = new HashMap(); + public int start; + public int end; + public int startToken; + public Map variableToValue = new HashMap(); } protected static List parseSearchPattern(String pattern) { @@ -95,7 +95,8 @@ else if (!escape && tokens.get(j).text.equals(")")) { } if ((blocks.get(0).isVariable && blocks.get(0).regEx == null) || (blocks.get(blocks.size() - 1).isVariable && blocks.get(blocks.size() - 1).regEx == null)) { - throw new RuntimeException("Error in search pattern: pattern cannot start or end with a non-regex variable: " + pattern); + throw new RuntimeException( + "Error in search pattern: pattern cannot start or end with a non-regex variable: " + pattern); } return blocks; } @@ -111,16 +112,19 @@ protected static MatchedPattern search(String sql, List parsedPattern, in for (int cursor = startToken; cursor < tokens.size(); cursor++) { StringUtils.Token token = tokens.get(cursor); if (parsedPattern.get(matchCount).isVariable) { - if (parsedPattern.get(matchCount).regEx != null && (matchCount == parsedPattern.size() - 1 || parsedPattern.get(matchCount + 1).isVariable)) { + if (parsedPattern.get(matchCount).regEx != null + && (matchCount == parsedPattern.size() - 1 || parsedPattern.get(matchCount + 1).isVariable)) { // Regex variable at end of pattern, or has another variable following it - Pattern pattern = Pattern.compile(parsedPattern.get(matchCount).regEx, Pattern.CASE_INSENSITIVE | Pattern.DOTALL | Pattern.MULTILINE); + Pattern pattern = Pattern.compile(parsedPattern.get(matchCount).regEx, + Pattern.CASE_INSENSITIVE | Pattern.DOTALL | Pattern.MULTILINE); Matcher matcher = pattern.matcher(sql.substring(token.start)); if (matcher.find() && matcher.start() == 0) { if (matchCount == 0) { matchedPattern.start = token.start; matchedPattern.startToken = cursor; } - matchedPattern.variableToValue.put(parsedPattern.get(matchCount).text, sql.substring(token.start, token.start + matcher.end())); + matchedPattern.variableToValue.put(parsedPattern.get(matchCount).text, + sql.substring(token.start, token.start + matcher.end())); matchCount++; if (matchCount == parsedPattern.size()) { matchedPattern.end = token.start + matcher.end(); @@ -135,15 +139,18 @@ protected static MatchedPattern search(String sql, List parsedPattern, in } else { matchCount = 0; } - } else if (nestStack.size() == 0 && matchCount < parsedPattern.size() - 1 && token.text.equals(parsedPattern.get(matchCount + 1).text)) { + } else if (nestStack.size() == 0 && matchCount < parsedPattern.size() - 1 + && token.text.equals(parsedPattern.get(matchCount + 1).text)) { // Found the token after the variable - if (parsedPattern.get(matchCount).regEx != null && !matches(parsedPattern.get(matchCount).regEx, sql.substring(varStart, token.start))) { + if (parsedPattern.get(matchCount).regEx != null + && !matches(parsedPattern.get(matchCount).regEx, sql.substring(varStart, token.start))) { // Content didn't match regex matchCount = 0; cursor = matchedPattern.startToken; } else { // No regex or matched regex - matchedPattern.variableToValue.put(parsedPattern.get(matchCount).text, sql.substring(varStart, token.start)); + matchedPattern.variableToValue.put(parsedPattern.get(matchCount).text, + sql.substring(varStart, token.start)); matchCount += 2; if (matchCount == parsedPattern.size()) { matchedPattern.end = token.end; @@ -154,12 +161,15 @@ protected static MatchedPattern search(String sql, List parsedPattern, in if (token.text.equals("'") || token.text.equals("'")) inPatternQuote = !inPatternQuote; } - } else if (nestStack.size() == 0 && !inPatternQuote && (token.text.equals(";") || token.text.equals(")"))) { // Not allowed to span multiple SQL + } else if (nestStack.size() == 0 && !inPatternQuote + && (token.text.equals(";") || token.text.equals(")"))) { // Not allowed to span multiple SQL // statements or outside of nesting matchCount = 0; cursor = matchedPattern.startToken; } else { - if (nestStack.size() != 0 && (nestStack.peek().equals("\"") || nestStack.peek().equals("'"))) { // inside quoted string + if (nestStack.size() != 0 && (nestStack.peek().equals("\"") || nestStack.peek().equals("'"))) { // inside + // quoted + // string if (token.text.equals(nestStack.peek())) nestStack.pop(); } else { @@ -167,13 +177,15 @@ protected static MatchedPattern search(String sql, List parsedPattern, in nestStack.push(token.text); } else if (!inPatternQuote && token.text.equals("(")) { nestStack.push(token.text); - } else if (!inPatternQuote && nestStack.size() != 0 && token.text.equals(")") && nestStack.peek().equals("(")) { + } else if (!inPatternQuote && nestStack.size() != 0 && token.text.equals(")") + && nestStack.peek().equals("(")) { nestStack.pop(); } } } } else { - // Check if token matches current part of pattern. But first part cannot be within quotes: + // Check if token matches current part of pattern. But first part cannot be + // within quotes: if (token.text.equals(parsedPattern.get(matchCount).text) && (matchCount != 0 || !token.inQuotes)) { if (matchCount == 0) { matchedPattern.start = token.start; @@ -193,7 +205,8 @@ protected static MatchedPattern search(String sql, List parsedPattern, in cursor = matchedPattern.startToken; } } - if (matchCount != 0 && cursor == tokens.size() - 1) { // If at end of sql and still didn't finish pattern, we're not going to finish it + if (matchCount != 0 && cursor == tokens.size() - 1) { // If at end of sql and still didn't finish pattern, + // we're not going to finish it matchCount = 0; cursor = matchedPattern.startToken; } @@ -214,21 +227,25 @@ private static String searchAndReplace(String sql, List parsedPattern, St String replacement = replacePattern; for (Map.Entry pair : matchedPattern.variableToValue.entrySet()) replacement = StringUtils.replaceAll(replacement, pair.getKey(), pair.getValue()); - sql = sql.substring(0, matchedPattern.start) + replacement + sql.substring(matchedPattern.end, sql.length()); + sql = sql.substring(0, matchedPattern.start) + replacement + + sql.substring(matchedPattern.end, sql.length()); // System.out.println(sql); int delta = 1; if (StringUtils.tokenizeSql(replacement).size() == 0) delta = 0; - // Special situation: if replacement pattern starts with variable, and variable content starts with start of search pattern, don't + // Special situation: if replacement pattern starts with variable, and variable + // content starts with start of search pattern, don't // skip first token: - if (delta > 0 && replacePattern.startsWith("@@") && replacement.toLowerCase().trim().startsWith(parsedPattern.get(0).text)) + if (delta > 0 && replacePattern.startsWith("@@") + && replacement.toLowerCase().trim().startsWith(parsedPattern.get(0).text)) delta = 0; matchedPattern = search(sql, parsedPattern, matchedPattern.startToken + delta); } return sql; } - private static String translateSql(String sql, List replacementPatterns, String sessionId, String oracleTempPrefix) { + private static String translateSql(String sql, List replacementPatterns, String sessionId, + String oracleTempPrefix) { for (int i = 0; i < replacementPatterns.size(); i++) { String[] pair = replacementPatterns.get(i).clone(); pair[1] = pair[1].replace("%session_id%", sessionId); @@ -240,14 +257,14 @@ private static String translateSql(String sql, List replacementPattern } /** - * This function takes SQL in one dialect and translates it into another. It uses simple pattern replacement, so its functionality is limited. + * This function takes SQL in one dialect and translates it into another. It + * uses simple pattern replacement, so its functionality is limited. * - * @param sql - * The SQL to be translated - * @param sourceDialect - * The source dialect. Currently, only "sql server" for Microsoft SQL Server is supported - * @param targetDialect - * The target dialect. Currently "oracle", "postgresql", and "redshift" are supported + * @param sql The SQL to be translated + * @param sourceDialect The source dialect. Currently, only "sql server" for + * Microsoft SQL Server is supported + * @param targetDialect The target dialect. Currently "oracle", "postgresql", + * and "redshift" are supported * @return The translated SQL */ @Deprecated @@ -256,14 +273,15 @@ public static String translateSql(String sql, String sourceDialect, String targe } /** - * This function takes SQL in one dialect and translates it into another. It uses simple pattern replacement, so its functionality is limited.The source - * dialect is always SQL Server. Note that trailing semicolons are not removed for Oracle, which is required before sending a statement through JDBC. This + * This function takes SQL in one dialect and translates it into another. It + * uses simple pattern replacement, so its functionality is limited.The source + * dialect is always SQL Server. Note that trailing semicolons are not removed + * for Oracle, which is required before sending a statement through JDBC. This * will be done by splitSql. * - * @param sql - * The SQL to be translated - * @param targetDialect - * The target dialect. Currently "oracle", "postgresql", and "redshift" are supported + * @param sql The SQL to be translated + * @param targetDialect The target dialect. Currently "oracle", "postgresql", + * and "redshift" are supported * @return The translated SQL */ public static String translateSql(String sql, String targetDialect) { @@ -271,135 +289,172 @@ public static String translateSql(String sql, String targetDialect) { } /** - * This function takes SQL in one dialect and translates it into another. It uses simple pattern replacement, so its functionality is limited. + * This function takes SQL in one dialect and translates it into another. It + * uses simple pattern replacement, so its functionality is limited. * - * @param sql - * The SQL to be translated - * @param sourceDialect - * The source dialect. Currently, only "sql server" for Microsoft SQL Server is supported - * @param targetDialect - * The target dialect. Currently "oracle", "postgresql", and "redshift" are supported - * @param sessionId - * An alphanumeric string to be used when generating unique table names (specifically for Oracle temp tables). This ID should preferably be - * generated using the SqlTranslate.generateSessionId() function. If null, a global session ID will be generated and used for all subsequent - * calls to translateSql. - * @param oracleTempSchema - * The name of a schema where temp tables can be created in Oracle. When null, the current schema is assumed to be the temp schema (ie. no schema - * name is prefixed to the temp table name). + * @param sql The SQL to be translated + * @param sourceDialect The source dialect. Currently, only "sql server" for + * Microsoft SQL Server is supported + * @param targetDialect The target dialect. Currently "oracle", "postgresql", + * and "redshift" are supported + * @param sessionId An alphanumeric string to be used when generating + * unique table names (specifically for Oracle temp + * tables). This ID should preferably be generated using + * the SqlTranslate.generateSessionId() function. If + * null, a global session ID will be generated and used + * for all subsequent calls to translateSql. + * @param oracleTempSchema The name of a schema where temp tables can be created + * in platforms that do not natively support temp + * tables. When null, the current schema is assumed to + * be the temp schema (ie. no schema name is prefixed to + * the temp table name). * @return The translated SQL */ @Deprecated - public static String translateSql(String sql, String sourceDialect, String targetDialect, String sessionId, String oracleTempSchema) { + public static String translateSql(String sql, String sourceDialect, String targetDialect, String sessionId, + String oracleTempSchema) { return translateSql(sql, targetDialect, sessionId, oracleTempSchema); } /** - * This function takes SQL in one dialect and translates it into another. It uses simple pattern replacement, so its functionality is limited. The source - * dialect is always SQL Server. Note that trailing semicolons are not removed for Oracle, which is required before sending a statement through JDBC. This + * This function takes SQL in one dialect and translates it into another. It + * uses simple pattern replacement, so its functionality is limited. The source + * dialect is always SQL Server. Note that trailing semicolons are not removed + * for Oracle, which is required before sending a statement through JDBC. This * will be done by splitSql. * - * @param sql - * The SQL to be translated - * @param targetDialect - * The target dialect. Currently "oracle", "postgresql", and "redshift" are supported - * @param sessionId - * An alphanumeric string to be used when generating unique table names (specifically for Oracle temp tables). This ID should preferably be - * generated using the SqlTranslate.generateSessionId() function. If null, a global session ID will be generated and used for all subsequent - * calls to translateSql. - * @param oracleTempSchema - * The name of a schema where temp tables can be created in Oracle. When null, the current schema is assumed to be the temp schema (ie. no schema - * name is prefixed to the temp table name). + * @param sql The SQL to be translated + * @param targetDialect The target dialect. Currently "oracle", + * "postgresql", and "redshift" are supported + * @param sessionId An alphanumeric string to be used when generating + * unique table names (specifically for Oracle temp + * tables). This ID should preferably be generated + * using the SqlTranslate.generateSessionId() + * function. If null, a global session ID will be + * generated and used for all subsequent calls to + * translateSql. + * @param tempEmulationSchema The name of a schema where temp tables can be + * created in Oracle. When null, the current schema + * is assumed to be the temp schema (ie. no schema + * name is prefixed to the temp table name). * @return The translated SQL */ - public static String translateSql(String sql, String targetDialect, String sessionId, String oracleTempSchema) { - return translateSqlWithPath(sql, targetDialect, sessionId, oracleTempSchema, null); + public static String translateSql(String sql, String targetDialect, String sessionId, String tempEmulationSchema) { + return translateSqlWithPath(sql, targetDialect, sessionId, tempEmulationSchema, null); } - + /** - * This function takes SQL in one dialect and translates it into another. It uses simple pattern replacement, so its functionality is limited. The source - * dialect is always SQL Server. This removes any trailing semicolon as required by Oracle when sending through JDBC. A runtime exception is thrown if - * more than one statement is encountered in the SQL. + * This function takes SQL in one dialect and translates it into another. It + * uses simple pattern replacement, so its functionality is limited. The source + * dialect is always SQL Server. This removes any trailing semicolon as required + * by Oracle when sending through JDBC. A runtime exception is thrown if more + * than one statement is encountered in the SQL. * - * @param sql - * The SQL to be translated - * @param targetDialect - * The target dialect. Currently "oracle", "postgresql", and "redshift" are supported + * @param sql The SQL to be translated + * @param targetDialect The target dialect. Currently "oracle", "postgresql", + * and "redshift" are supported * @return The translated SQL */ public static String translateSingleStatementSql(String sql, String targetDialect) { return translateSingleStatementSql(sql, targetDialect, null, null); } - + /** - * This function takes SQL in one dialect and translates it into another. It uses simple pattern replacement, so its functionality is limited.The source - * dialect is always SQL Server. This removes any trailing semicolon as required by Oracle when sending through JDBC. A runtime exception is thrown if - * more than one statement is encountered in the SQL. + * This function takes SQL in one dialect and translates it into another. It + * uses simple pattern replacement, so its functionality is limited.The source + * dialect is always SQL Server. This removes any trailing semicolon as required + * by Oracle when sending through JDBC. A runtime exception is thrown if more + * than one statement is encountered in the SQL. * - * @param sql - * The SQL to be translated - * @param targetDialect - * The target dialect. Currently "oracle", "postgresql", and "redshift" are supported - * @param sessionId - * An alphanumeric string to be used when generating unique table names (specifically for Oracle temp tables). This ID should preferably be - * generated using the SqlTranslate.generateSessionId() function. If null, a global session ID will be generated and used for all subsequent - * calls to translateSql. - * @param oracleTempSchema - * The name of a schema where temp tables can be created in Oracle. When null, the current schema is assumed to be the temp schema (ie. no schema - * name is prefixed to the temp table name). + * @param sql The SQL to be translated + * @param targetDialect The target dialect. Currently "oracle", + * "postgresql", and "redshift" are supported + * @param sessionId An alphanumeric string to be used when generating + * unique table names (specifically for Oracle temp + * tables). This ID should preferably be generated + * using the SqlTranslate.generateSessionId() + * function. If null, a global session ID will be + * generated and used for all subsequent calls to + * translateSql. + * @param tempEmulationSchema The name of a schema where temp tables can be + * created for those platforms that don't support + * temp tables natively. When null, the current + * schema is assumed to be the temp schema (ie. no + * schema name is prefixed to the temp table name). * @return The translated SQL */ - public static String translateSingleStatementSql(String sql, String targetDialect, String sessionId, String oracleTempSchema) { - return translateSingleStatementSqlWithPath(sql, targetDialect, sessionId, oracleTempSchema, null); + public static String translateSingleStatementSql(String sql, String targetDialect, String sessionId, + String tempEmulationSchema) { + return translateSingleStatementSqlWithPath(sql, targetDialect, sessionId, tempEmulationSchema, null); } - + /** - * This function takes SQL in one dialect and translates it into another. It uses simple pattern replacement, so its functionality is limited.The source - * dialect is always SQL Server. This removes any trailing semicolon as required by Oracle when sending through JDBC. A runtime exception is thrown if - * more than one statement is encountered in the SQL. + * This function takes SQL in one dialect and translates it into another. It + * uses simple pattern replacement, so its functionality is limited.The source + * dialect is always SQL Server. This removes any trailing semicolon as required + * by Oracle when sending through JDBC. A runtime exception is thrown if more + * than one statement is encountered in the SQL. * - * @param sql - * The SQL to be translated - * @param targetDialect - * The target dialect. Currently "oracle", "postgresql", and "redshift" are supported - * @param sessionId - * An alphanumeric string to be used when generating unique table names (specifically for Oracle temp tables). This ID should preferably be - * generated using the SqlTranslate.generateSessionId() function. If null, a global session ID will be generated and used for all subsequent - * calls to translateSql. - * @param oracleTempSchema - * The name of a schema where temp tables can be created in Oracle. When null, the current schema is assumed to be the temp schema (ie. no schema - * name is prefixed to the temp table name). - * @param pathToReplacementPatterns - * The absolute path of the csv file containing the replacement patterns. If null, the csv file inside the jar is used. + * @param sql The SQL to be translated + * @param targetDialect The target dialect. Currently "oracle", + * "postgresql", and "redshift" are supported + * @param sessionId An alphanumeric string to be used when + * generating unique table names (specifically + * for Oracle temp tables). This ID should + * preferably be generated using the + * SqlTranslate.generateSessionId() function. + * If null, a global session ID will be + * generated and used for all subsequent calls + * to translateSql. + * @param tempEmulationSchema The name of a schema where temp tables can + * be created for those platforms that don't + * support temp tables natively. When null, the + * current schema is assumed to be the temp + * schema (ie. no schema name is prefixed to + * the temp table name). + * @param pathToReplacementPatterns The absolute path of the csv file containing + * the replacement patterns. If null, the csv + * file inside the jar is used. * @return The translated SQL */ - public static String translateSingleStatementSqlWithPath(String sql, String targetDialect, String sessionId, String oracleTempSchema, String pathToReplacementPatterns) { - sql = translateSqlWithPath(sql, targetDialect, sessionId, oracleTempSchema, pathToReplacementPatterns); + public static String translateSingleStatementSqlWithPath(String sql, String targetDialect, String sessionId, + String tempEmulationSchema, String pathToReplacementPatterns) { + sql = translateSqlWithPath(sql, targetDialect, sessionId, tempEmulationSchema, pathToReplacementPatterns); String[] sqlStatements = SqlSplit.splitSql(sql); if (sqlStatements.length > 1) throw new RuntimeException("SQL contains more than one statement: " + sql); return sqlStatements[0]; } - + /** - * This function takes SQL in one dialect and translates it into another. It uses simple pattern replacement, so its functionality is limited.The source + * This function takes SQL in one dialect and translates it into another. It + * uses simple pattern replacement, so its functionality is limited.The source * dialect is always SQL Server. * - * @param sql - * The SQL to be translated - * @param targetDialect - * The target dialect. Currently "oracle", "postgresql", and "redshift" are supported - * @param sessionId - * An alphanumeric string to be used when generating unique table names (specifically for Oracle temp tables). This ID should preferably be - * generated using the SqlTranslate.generateSessionId() function. If null, a global session ID will be generated and used for all subsequent - * calls to translateSql. - * @param oracleTempSchema - * The name of a schema where temp tables can be created in Oracle. When null, the current schema is assumed to be the temp schema (ie. no schema - * name is prefixed to the temp table name). - * @param pathToReplacementPatterns - * The absolute path of the csv file containing the replacement patterns. If null, the csv file inside the jar is used. + * @param sql The SQL to be translated + * @param targetDialect The target dialect. Currently "oracle", + * "postgresql", and "redshift" are supported + * @param sessionId An alphanumeric string to be used when + * generating unique table names (specifically + * for Oracle temp tables). This ID should + * preferably be generated using the + * SqlTranslate.generateSessionId() function. + * If null, a global session ID will be + * generated and used for all subsequent calls + * to translateSql. + * @param tempEmulationSchema The name of a schema where temp tables can + * be created for those platforms that don't + * support temp tables natively. When null, the + * current schema is assumed to be the temp + * schema (ie. no schema name is prefixed to + * the temp table name). + * @param pathToReplacementPatterns The absolute path of the csv file containing + * the replacement patterns. If null, the csv + * file inside the jar is used. * @return The translated SQL */ - public static String translateSqlWithPath(String sql, String targetDialect, String sessionId, String oracleTempSchema, String pathToReplacementPatterns) { + public static String translateSqlWithPath(String sql, String targetDialect, String sessionId, + String tempEmulationSchema, String pathToReplacementPatterns) { ensurePatternsAreLoaded(pathToReplacementPatterns); if (sessionId == null) { if (globalSessionId == null) @@ -408,10 +463,10 @@ public static String translateSqlWithPath(String sql, String targetDialect, Stri } else validateSessionId(sessionId); String oracleTempPrefix; - if (oracleTempSchema == null) + if (tempEmulationSchema == null) oracleTempPrefix = ""; else - oracleTempPrefix = oracleTempSchema + "."; + oracleTempPrefix = tempEmulationSchema + "."; List replacementPatterns = targetToReplacementPatterns.get(targetDialect); if (replacementPatterns == null) { @@ -423,8 +478,8 @@ public static String translateSqlWithPath(String sql, String targetDialect, Stri for (String sourceTarget : targetToReplacementPatterns.keySet()) if (sourceTarget.split("\t")[0].equals(SOURCE_DIALECT)) allowedDialects.add(sourceTarget.split("\t")[1]); - throw new RuntimeException("Don't know how to translate from " + SOURCE_DIALECT + " to " + targetDialect + ". Valid target dialects are " - + StringUtils.join(allowedDialects, ", ")); + throw new RuntimeException("Don't know how to translate from " + SOURCE_DIALECT + " to " + targetDialect + + ". Valid target dialects are " + StringUtils.join(allowedDialects, ", ")); } } else if (targetDialect.equalsIgnoreCase(BIG_QUERY)) { sql = BigQueryTranslate.translatebigQuery(sql); @@ -438,7 +493,8 @@ public static String translateSqlWithPath(String sql, String targetDialect, Stri private static void validateSessionId(String sessionId) { if (sessionId.length() != SESSION_ID_LENGTH) - throw new RuntimeException("Session ID has length " + sessionId.length() + ", should be " + SESSION_ID_LENGTH); + throw new RuntimeException( + "Session ID has length " + sessionId.length() + ", should be " + SESSION_ID_LENGTH); if (!Character.isLetter(sessionId.charAt(0))) throw new RuntimeException("Session ID does not start with a letter"); for (int i = 1; i < sessionId.length(); i++) @@ -503,7 +559,8 @@ private static void ensurePatternsAreLoaded(String pathToReplacementPatterns) { replacementPatterns = new ArrayList(); targetToReplacementPatterns.put(target, replacementPatterns); } - replacementPatterns.add(new String[] { row.get(1).replaceAll("@", "@@"), row.get(2).replaceAll("@", "@@") }); + replacementPatterns.add( + new String[] { row.get(1).replaceAll("@", "@@"), row.get(2).replaceAll("@", "@@") }); } } catch (UnsupportedEncodingException e) { System.err.println("Computer does not support UTF-8 encoding"); @@ -529,7 +586,8 @@ public static String[] check(String sql, String targetDialect) { for (String longName : longTempNames) warnings.add("Temp table name '" + longName + "' is too long. Temp table names should be shorter than " - + (MAX_ORACLE_TABLE_NAME_LENGTH - SESSION_ID_LENGTH) + " characters to prevent Oracle from crashing."); + + (MAX_ORACLE_TABLE_NAME_LENGTH - SESSION_ID_LENGTH) + + " characters to prevent Oracle from crashing."); // normal table names: pattern = Pattern.compile("(create|drop|truncate)\\s+table +[0-9a-zA-Z_]+"); @@ -541,17 +599,19 @@ public static String[] check(String sql, String targetDialect) { longNames.add(name); } for (String longName : longNames) - warnings.add("Table name '" + longName + "' is too long. Table names should be shorter than " + MAX_ORACLE_TABLE_NAME_LENGTH - + " characters to prevent Oracle from crashing."); + warnings.add("Table name '" + longName + "' is too long. Table names should be shorter than " + + MAX_ORACLE_TABLE_NAME_LENGTH + " characters to prevent Oracle from crashing."); return warnings.toArray(new String[warnings.size()]); } /** - * Forces the replacement patterns to be loaded from the specified path. Useful for debugging. + * Forces the replacement patterns to be loaded from the specified path. Useful + * for debugging. * - * @param pathToReplacementPatterns - * The absolute path of the csv file containing the replacement patterns. If null, the csv file inside the jar is used. + * @param pathToReplacementPatterns The absolute path of the csv file containing + * the replacement patterns. If null, the csv + * file inside the jar is used. */ public static void setReplacementPatterns(String pathToReplacementPatterns) { targetToReplacementPatterns = null; diff --git a/man/loadRenderTranslateSql.Rd b/man/loadRenderTranslateSql.Rd index 2cc2cd58..db1ce5d9 100644 --- a/man/loadRenderTranslateSql.Rd +++ b/man/loadRenderTranslateSql.Rd @@ -9,6 +9,7 @@ loadRenderTranslateSql( packageName, dbms = "sql server", ..., + tempEmulationSchema = getOption("sqlRenderTempEmulationSchema"), oracleTempSchema = NULL, warnOnMissingParameters = TRUE ) @@ -23,7 +24,11 @@ loadRenderTranslateSql( \item{...}{Parameter values used for \code{render}} -\item{oracleTempSchema}{A schema that can be used to create temp tables in when using Oracle.} +\item{tempEmulationSchema}{Some database platforms like Oracle and Impala do not truly support temp tables. To +emulate temp tables, provide a schema with write privileges where temp tables +can be created.} + +\item{oracleTempSchema}{DEPRECATED: use \code{tempEmulationSchema} instead.} \item{warnOnMissingParameters}{Should a warning be raised when parameters provided to this function do not appear in the parameterized SQL that is being rendered? By default, this is TRUE.} diff --git a/man/translate.Rd b/man/translate.Rd index 111cb8ff..12c2d704 100644 --- a/man/translate.Rd +++ b/man/translate.Rd @@ -4,15 +4,24 @@ \alias{translate} \title{Translates SQL from one dialect to another} \usage{ -translate(sql = "", targetDialect, oracleTempSchema = NULL) +translate( + sql = "", + targetDialect, + tempEmulationSchema = getOption("sqlRenderTempEmulationSchema"), + oracleTempSchema = NULL +) } \arguments{ \item{sql}{The SQL to be translated} -\item{targetDialect}{The target dialect. Currently "oracle", "postgresql", "pdw", "impala", "netezza", "bigquery", and -"redshift" are supported} +\item{targetDialect}{The target dialect. Currently "oracle", "postgresql", "pdw", "impala", "sqlite", "netezza", "bigquery", and +"redshift" are supported.} -\item{oracleTempSchema}{A schema that can be used to create temp tables in when using Oracle or Impala.} +\item{tempEmulationSchema}{Some database platforms like Oracle and Impala do not truly support temp tables. To +emulate temp tables, provide a schema with write privileges where temp tables +can be created.} + +\item{oracleTempSchema}{DEPRECATED: use \code{tempEmulationSchema} instead.} } \value{ A character string containing the translated SQL. diff --git a/man/translateSingleStatement.Rd b/man/translateSingleStatement.Rd index ffaa2c39..868767d1 100644 --- a/man/translateSingleStatement.Rd +++ b/man/translateSingleStatement.Rd @@ -4,15 +4,24 @@ \alias{translateSingleStatement} \title{Translates a single SQL statement from one dialect to another} \usage{ -translateSingleStatement(sql = "", targetDialect, oracleTempSchema = NULL) +translateSingleStatement( + sql = "", + targetDialect, + tempEmulationSchema = getOption("sqlRenderTempEmulationSchema"), + oracleTempSchema = NULL +) } \arguments{ \item{sql}{The SQL to be translated} -\item{targetDialect}{The target dialect. Currently "oracle", "postgresql", "pdw", "impala", "netezza", "bigquery", and -"redshift" are supported} +\item{targetDialect}{The target dialect. Currently "oracle", "postgresql", "pdw", "impala", "sqlite", "netezza", "bigquery", and +"redshift" are supported.} -\item{oracleTempSchema}{A schema that can be used to create temp tables in when using Oracle or Impala.} +\item{tempEmulationSchema}{Some database platforms like Oracle and Impala do not truly support temp tables. To +emulate temp tables, provide a schema with write privileges where temp tables +can be created.} + +\item{oracleTempSchema}{DEPRECATED: use \code{tempEmulationSchema} instead.} } \value{ A character vector with the translated SQL. diff --git a/man/translateSqlFile.Rd b/man/translateSqlFile.Rd index 405d5295..87e07ead 100644 --- a/man/translateSqlFile.Rd +++ b/man/translateSqlFile.Rd @@ -8,6 +8,7 @@ translateSqlFile( sourceFile, targetFile, targetDialect, + tempEmulationSchema = getOption("sqlRenderTempEmulationSchema"), oracleTempSchema = NULL ) } @@ -16,10 +17,14 @@ translateSqlFile( \item{targetFile}{The target SQL file} -\item{targetDialect}{The target dialect. Currently 'oracle', 'postgresql', and 'redshift' are -supported} +\item{targetDialect}{The target dialect. Currently "oracle", "postgresql", "pdw", "impala", "sqlite", "netezza", "bigquery", and +"redshift" are supported.} -\item{oracleTempSchema}{A schema that can be used to create temp tables in when using Oracle.} +\item{tempEmulationSchema}{Some database platforms like Oracle and Impala do not truly support temp tables. To +emulate temp tables, provide a schema with write privileges where temp tables +can be created.} + +\item{oracleTempSchema}{DEPRECATED: use \code{tempEmulationSchema} instead.} } \description{ This function takes SQL and translates it to a different dialect. diff --git a/pom.xml b/pom.xml index 6dff3f0a..717c7f94 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ org.ohdsi.sql SqlRender jar - 1.6.9-SNAPSHOT + 1.7.0-SNAPSHOT SqlRender scm:git:https://github.com/OHDSI/SqlRender diff --git a/vignettes/UsingSqlRender.Rmd b/vignettes/UsingSqlRender.Rmd index 872cf25d..0e00d446 100644 --- a/vignettes/UsingSqlRender.Rmd +++ b/vignettes/UsingSqlRender.Rmd @@ -16,11 +16,6 @@ vignette: > ```{r, echo = FALSE, message = FALSE} library(SqlRender) -knitr::opts_chunk$set( - cache = FALSE, - comment = "#>", - error = FALSE, - tidy = FALSE) ``` # Introduction @@ -33,37 +28,37 @@ One of the main functions of the package is to support parameterization of SQL. ## Substituting parameter values The `@` character can be used to indicate parameter names that need to be exchange for actual parameter values when rendering. In the following example, a variable called `a` is mentioned in the SQL. In the call to the render function the value of this parameter is defined: -```{r tidy=TRUE,echo=TRUE} +```{r echo=TRUE} sql <- "SELECT * FROM table WHERE id = @a;" -render(sql, a=123) +render(sql, a = 123) ``` Note that, unlike the parameterization offered by most database management systems, it is just as easy to parameterize table or field names as values: -```{r tidy=TRUE,echo=TRUE} +```{r echo=TRUE} sql <- "SELECT * FROM @x WHERE id = @a;" -render(sql, x="my_table", a=123) +render(sql, x = "my_table", a = 123) ``` The parameter values can be numbers, strings, booleans, as well as vectors, which are converted to comma-delimited lists: -```{r tidy=TRUE,echo=TRUE} +```{r echo=TRUE} sql <- "SELECT * FROM table WHERE id IN (@a);" -render(sql,a = c(1,2,3)) +render(sql, a = c(1,2,3)) ``` ## Default parameter values For some or all parameters, it might make sense to define default values that will be used unless the user specifies another value. This can be done using the `{DEFAULT @parameter = value}` syntax: -```{r tidy=TRUE,echo=TRUE} +```{r echo=TRUE} sql <- "{DEFAULT @a = 1} SELECT * FROM table WHERE id = @a;" render(sql) -render(sql,a=2) +render(sql, a = 2) ``` Defaults for multiple variables can be defined: -```{r tidy=TRUE,echo=TRUE} +```{r echo=TRUE} sql <- "{DEFAULT @a = 1} {DEFAULT @x = 'my_table'} SELECT * FROM @x WHERE id = @a;" render(sql) ``` @@ -71,34 +66,34 @@ render(sql) ## If-then-else Sometimes blocks of codes need to be turned on or off based on the values of one or more parameters. This is done using the `{Condition} ? {if true} : {if false}` syntax. If the *condition* evaluates to true or 1, the *if true* block is used, else the *if false* block is shown (if present). -```{r tidy=TRUE,echo=TRUE} +```{r echo=TRUE} sql <- "SELECT * FROM table {@x} ? {WHERE id = 1}" -render(sql,x = FALSE) -render(sql,x = TRUE) +render(sql, x = FALSE) +render(sql, x = TRUE) ``` Simple comparisons are also supported: -```{r tidy=TRUE,echo=TRUE} +```{r echo=TRUE} sql <- "SELECT * FROM table {@x == 1} ? {WHERE id = 1};" -render(sql,x = 1) -render(sql,x = 2) +render(sql, x = 1) +render(sql, x = 2) ``` As well as the `IN` operator: -```{r tidy=TRUE,echo=TRUE} +```{r echo=TRUE} sql <- "SELECT * FROM table {@x IN (1,2,3)} ? {WHERE id = 1};" -render(sql,x = 2) +render(sql, x = 2) ``` Clauses can combined with boolean operators: -```{r tidy=TRUE,echo=TRUE} +```{r echo=TRUE} sql <- "SELECT * FROM table {@x IN (1,2,3) | @y != 3} ? {WHERE id = @x AND value = @y};" -render(sql,x = 4, y = 4) +render(sql, x = 4, y = 4) sql <- "SELECT * FROM table {(@x == 1 | @x == 3) & @y != 3} ? {WHERE id = @x AND val = @y};" -render(sql,x = 3, y = 4) +render(sql, x = 3, y = 4) ``` @@ -116,7 +111,7 @@ A last limitation is that not all functions supported in one dialect have an equ Below an example: -```{r tidy=TRUE,echo=TRUE} +```{r echo=TRUE} sql <- "SELECT DATEDIFF(dd,a,b) FROM table; " translate(sql,targetDialect = "oracle") ``` @@ -218,22 +213,26 @@ However, Oracle will throw an error when the `AS` keyword is used. In the above ## Temp tables -Temp tables can be very useful to store intermediate results, and when used correctly can be used to dramatically improve performance of queries. In Postgres, PDW, RedShift and SQL Server temp tables have very nice properties: they're only visible to the current user, are automatically dropped when the session ends, and can be created even when the user has no write access. Unfortunately, in Oracle temp tables are basically permanent tables, with the only difference that the data inside the table is only visible to the current user. This is why, in Oracle, SqlRender will try to emulate temp tables by +Temp tables can be very useful to store intermediate results, and when used correctly can be used to dramatically improve performance of queries. In platforms like Postgres, PDW, RedShift and SQL Server temp tables have very nice properties: they're only visible to the current user, are automatically dropped when the session ends, and can be created even when the user has no write access. Unfortunately, in Oracle temp tables are basically permanent tables, with the only difference that the data inside the table is only visible to the current user. Other platforms, like Impala, don't support temp tables at all. This is why for some platforms SqlRender will try to emulate temp tables by 1. Adding a random string to the table name so tables from different users will not conflict. 2. Allowing the user to specify the schema where the temp tables will be created. For example: -```{r tidy=TRUE,echo=TRUE} +```{r echo=TRUE} sql <- "SELECT * FROM #children;" -translate(sql, targetDialect = "oracle", oracleTempSchema = "temp_schema") +translate(sql, targetDialect = "oracle", tempEmulationSchema = "temp_schema") ``` -Note that the user will need to have write privileges on `temp_schema`. +Note that the user will need to have write privileges on `temp_schema`. You can also set the `tempEmulationSchema` globally by using + +```{r echo=TRUE, eval=FALSE} +options(sqlRenderTempEmulationSchema = "temp_schema") +``` Also note that because Oracle has a limit on table names of 30 characters, **temp table names are only allowed to be at most 22 characters long** because else the name will become too long after appending the session ID. -Furthermore, remember that temp tables are not automatically dropped on Oracle, so you will need to explicitly ```TRUNCATE``` and ```DROP``` all temp tables once you're done with them to prevent orphan tables accumulating in the Oracle temp schema. +Furthermore, remember that emulated temp tables are not automatically dropped, so you will need to explicitly ```TRUNCATE``` and ```DROP``` all temp tables once you're done with them to prevent orphan tables accumulating in the Oracle temp schema. If possible, try to avoid using temp tables altogether. Sometimes one could use Common Table Expressions (CTEs) when one would normally use a temp table. For example, instead of @@ -305,7 +304,7 @@ SELECT * FROM person we can set `database = "cdm_data"` and the other called `databaseSchema = "cdm_data.dbo"`. On platforms other than SQL Server, the two variables will hold the same value and only on SQL Server will they be different. Within an R function, it is even possible to derive one variable from the other, so the user of your function would need to specify only one value: -```{r tidy=TRUE,echo=TRUE} +```{r echo=TRUE} foo <- function(databaseSchema, dbms) { database <- strsplit(databaseSchema ,"\\.")[[1]][1] sql <- "SELECT * FROM @databaseSchema.person; USE @database; SELECT * FROM person;" @@ -358,7 +357,7 @@ Debugging parameterized SQL can be a bit complicated; Only the rendered SQL can A Shiny app is included in the SqlRender package for interactively editing source SQL and generating rendered and translated SQL. The app can be started using: -```{r tidy=TRUE,eval=FALSE} +```{r eval=FALSE} launchSqlRenderDeveloper() ``` @@ -366,7 +365,7 @@ Which will open the default browser with the app. In addition, two functions have been developed to aid the debugging process: `renderFile()` and `translateFile()`. These can be used to read SQL from file, render or translate it, and write it back to file. For example: -```{r tidy=TRUE,eval=FALSE} +```{r eval=FALSE} translateFile("parameterizedSql.txt","renderedSql.txt") ``` @@ -392,7 +391,7 @@ createRWrapperForSql(sqlFilename = "test.sql", would result in the file *test.R* being generated containing this R code: -```{r tidy=TRUE,eval=FALSE} +```{r eval=FALSE} #' Todo: add title #' #' @description