├── src ├── .gitignore ├── RcppExports.cpp ├── sqldelim.h └── sqldelim.cpp ├── .gitignore ├── .Rbuildignore ├── tests ├── testthat.R └── testthat │ ├── test-sql-df.R │ ├── test-interpolate.R │ └── test-rownames.R ├── README.md ├── man ├── ANSI.Rd ├── sqlTableDrop.Rd ├── Table.Rd ├── transactions.Rd ├── sqlTableInsertIntoTemplate.Rd ├── rownames.Rd ├── sqlData.Rd ├── sqlTableInsertInto.Rd ├── sqlInterpolate.Rd ├── sqlParseVariables.Rd └── sqlTableCreate.Rd ├── R ├── AnsiConnection.R ├── TableDrop.R ├── RcppExports.R ├── Table.R ├── Transactions.R ├── Data.R ├── TableInsertInto.R ├── TableCreate.R ├── RowNames.R └── Interpolate.R ├── SQL.Rproj ├── DESCRIPTION ├── .travis.yml └── NAMESPACE /src/.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.so 3 | *.dll 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .Rproj.user 2 | .Rhistory 3 | .RData 4 | -------------------------------------------------------------------------------- /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^.*\.Rproj$ 2 | ^\.Rproj\.user$ 3 | ^\.travis\.yml$ 4 | -------------------------------------------------------------------------------- /tests/testthat.R: -------------------------------------------------------------------------------- 1 | library(testthat) 2 | library(SQL) 3 | 4 | test_check("SQL") 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SQL 2 | 3 | This package is __deprecated__ and all functions have been moved into DBI. 4 | -------------------------------------------------------------------------------- /man/ANSI.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2 (4.1.0): do not edit by hand 2 | % Please edit documentation in R/AnsiConnection.R 3 | \name{ANSI} 4 | \alias{ANSI} 5 | \title{Create (dummy) ANSI connection} 6 | \description{ 7 | Create (dummy) ANSI connection 8 | } 9 | \keyword{internal} 10 | 11 | -------------------------------------------------------------------------------- /R/AnsiConnection.R: -------------------------------------------------------------------------------- 1 | #' @importClassesFrom DBI DBIConnection DBIObject 2 | #' @useDynLib SQL 3 | #' @importFrom Rcpp sourceCpp 4 | NULL 5 | 6 | # Moved into DBI 7 | 8 | 9 | #' Create (dummy) ANSI connection 10 | #' 11 | #' @keywords internal 12 | #' @importFrom DBI ANSI 13 | #' @export ANSI 14 | #' @name ANSI 15 | NULL 16 | -------------------------------------------------------------------------------- /tests/testthat/test-sql-df.R: -------------------------------------------------------------------------------- 1 | context("sqlData") 2 | 3 | test_that("NAs turn in NULLs", { 4 | df <- data.frame( 5 | x = c(1, NA), 6 | y = c("a", NA), 7 | stringsAsFactors = FALSE 8 | ) 9 | sql_df <- sqlData(ANSI(), df) 10 | 11 | expect_equal(sql_df$x, c("1", "NULL")) 12 | expect_equal(sql_df$y, c("'a'", "NULL")) 13 | }) 14 | -------------------------------------------------------------------------------- /SQL.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: No 4 | SaveWorkspace: No 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 2 10 | Encoding: UTF-8 11 | 12 | RnwWeave: Sweave 13 | LaTeX: pdfLaTeX 14 | 15 | AutoAppendNewline: Yes 16 | StripTrailingWhitespace: Yes 17 | 18 | BuildType: Package 19 | PackageUseDevtools: Yes 20 | PackageInstallArgs: --no-multiarch --with-keep.source 21 | PackageRoxygenize: rd,collate,namespace 22 | -------------------------------------------------------------------------------- /R/TableDrop.R: -------------------------------------------------------------------------------- 1 | #' Drop table. 2 | #' 3 | #' @inheritParams sqlTableCreate 4 | #' @export 5 | #' @examples 6 | #' sqlTableDrop(ANSI(), "mtcars") 7 | #' sqlTableDrop(ANSI(), "mt\nca\"rs") 8 | setGeneric("sqlTableDrop", function(con, table, ...) { 9 | standardGeneric("sqlTableDrop") 10 | }) 11 | 12 | #' @export 13 | #' @rdname sqlTableDrop 14 | setMethod("sqlTableDrop", "DBIConnection", function(con, table, ...) { 15 | table <- dbQuoteIdentifier(con, table) 16 | 17 | SQL(paste0("DROP TABLE ", table)) 18 | }) 19 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: SQL 2 | Title: Safely Generate Common SQL Strings 3 | Version: 0.0.0.9003 4 | Authors@R: c( 5 | person("Hadley", "Wickham", , "hadley@rstudio.com", role = c("aut", "cre")), 6 | person("RStudio", role = "cph") 7 | ) 8 | Description: The SQL package provides a number of useful functions for working 9 | with SQL strings. 10 | Depends: R (>= 3.1.0) 11 | License: GPL-3 12 | LazyData: true 13 | Imports: 14 | DBI (>= 0.3.1.9003), 15 | methods, 16 | Rcpp 17 | Suggests: testthat 18 | LinkingTo: Rcpp 19 | -------------------------------------------------------------------------------- /R/RcppExports.R: -------------------------------------------------------------------------------- 1 | # This file was generated by Rcpp::compileAttributes 2 | # Generator token: 10BE3573-1514-4C36-9D1C-5A225CD40393 3 | 4 | #' @export 5 | #' @rdname sqlParseVariables 6 | #' @param sql SQL to parse (a character vector of length 1) 7 | #' @param quotes A list of \code{QuoteSpec} calls defining the quoting 8 | #' specification. 9 | #' @param comments A list of \code{CommentSpec} calls defining the commenting 10 | #' specification. 11 | sqlParseVariablesImpl <- function(sql, quotes, comments) { 12 | .Call('SQL_sqlParseVariablesImpl', PACKAGE = 'SQL', sql, quotes, comments) 13 | } 14 | 15 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # Sample .travis.yml for R projects from https://github.com/craigcitro/r-travis 2 | 3 | language: c 4 | 5 | before_install: 6 | - curl -OL http://raw.github.com/craigcitro/r-travis/master/scripts/travis-tool.sh 7 | - chmod 755 ./travis-tool.sh 8 | - ./travis-tool.sh bootstrap 9 | 10 | install: 11 | - ./travis-tool.sh install_github rstats-db/DBI 12 | - ./travis-tool.sh install_deps 13 | 14 | script: ./travis-tool.sh run_tests 15 | 16 | after_failure: 17 | - ./travis-tool.sh dump_logs 18 | 19 | env: 20 | - WARNINGS_ARE_ERRORS=1 21 | 22 | notifications: 23 | email: 24 | on_success: change 25 | on_failure: change 26 | -------------------------------------------------------------------------------- /man/sqlTableDrop.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2 (4.1.0): do not edit by hand 2 | % Please edit documentation in R/TableDrop.R 3 | \docType{methods} 4 | \name{sqlTableDrop} 5 | \alias{sqlTableDrop} 6 | \alias{sqlTableDrop,DBIConnection-method} 7 | \title{Drop table.} 8 | \usage{ 9 | sqlTableDrop(con, table, ...) 10 | 11 | \S4method{sqlTableDrop}{DBIConnection}(con, table, ...) 12 | } 13 | \arguments{ 14 | \item{con}{A database connection.} 15 | 16 | \item{table}{Name of the table. Escaped with 17 | \code{\link[DBI]{dbQuoteIdentifier}}.} 18 | 19 | \item{...}{Other arguments used by individual methods.} 20 | } 21 | \description{ 22 | Drop table. 23 | } 24 | \examples{ 25 | sqlTableDrop(ANSI(), "mtcars") 26 | sqlTableDrop(ANSI(), "mt\\nca\\"rs") 27 | } 28 | 29 | -------------------------------------------------------------------------------- /tests/testthat/test-interpolate.R: -------------------------------------------------------------------------------- 1 | context("sqlInterpolate") 2 | 3 | test_that("parameter names matched", { 4 | expect_equal( 5 | sqlInterpolate(ANSI(), "?a ?b", a = 1, b = 2), 6 | SQL("1 2") 7 | ) 8 | expect_equal( 9 | sqlInterpolate(ANSI(), "?a ?b", b = 2, a = 1), 10 | SQL("1 2") 11 | ) 12 | }) 13 | 14 | test_that("parameters in strings are ignored", { 15 | expect_equal( 16 | sqlInterpolate(ANSI(), "'?a'"), 17 | SQL("'?a'") 18 | ) 19 | }) 20 | 21 | test_that("parameters in comments are ignored", { 22 | expect_equal( 23 | sqlInterpolate(ANSI(), "-- ?a"), 24 | SQL("-- ?a") 25 | ) 26 | }) 27 | 28 | test_that("strings are quoted", { 29 | expect_equal( 30 | sqlInterpolate(ANSI(), "?a", a = "abc"), 31 | SQL("'abc'") 32 | ) 33 | 34 | 35 | }) 36 | -------------------------------------------------------------------------------- /src/RcppExports.cpp: -------------------------------------------------------------------------------- 1 | // This file was generated by Rcpp::compileAttributes 2 | // Generator token: 10BE3573-1514-4C36-9D1C-5A225CD40393 3 | 4 | #include 5 | 6 | using namespace Rcpp; 7 | 8 | // sqlParseVariablesImpl 9 | List sqlParseVariablesImpl(std::string sql, ListOf quotes, ListOf comments); 10 | RcppExport SEXP SQL_sqlParseVariablesImpl(SEXP sqlSEXP, SEXP quotesSEXP, SEXP commentsSEXP) { 11 | BEGIN_RCPP 12 | Rcpp::RObject __result; 13 | Rcpp::RNGScope __rngScope; 14 | Rcpp::traits::input_parameter< std::string >::type sql(sqlSEXP); 15 | Rcpp::traits::input_parameter< ListOf >::type quotes(quotesSEXP); 16 | Rcpp::traits::input_parameter< ListOf >::type comments(commentsSEXP); 17 | __result = Rcpp::wrap(sqlParseVariablesImpl(sql, quotes, comments)); 18 | return __result; 19 | END_RCPP 20 | } 21 | -------------------------------------------------------------------------------- /man/Table.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2 (4.1.0): do not edit by hand 2 | % Please edit documentation in R/Table.R 3 | \docType{class} 4 | \name{Table-class} 5 | \alias{Table} 6 | \alias{Table-class} 7 | \alias{dbQuoteIdentifier,DBIConnection,Table-method} 8 | \alias{show,Table-method} 9 | \title{Refer to a table nested in a hierarchy (e.g. within a schema)} 10 | \usage{ 11 | Table(...) 12 | 13 | \S4method{dbQuoteIdentifier}{DBIConnection,Table}(conn, x, ...) 14 | 15 | \S4method{show}{Table}(object) 16 | } 17 | \arguments{ 18 | \item{...}{Components of the hierarchy, e.g. \code{schema}, \code{table}, 19 | or \code{cluster}, \code{catalog}, \code{schema}, \code{table}. 20 | For more on these concepts, see 21 | \url{http://stackoverflow.com/questions/7022755/}} 22 | 23 | \item{conn,x}{Connection and Table used when escaping.} 24 | 25 | \item{object}{Table object to print} 26 | } 27 | \description{ 28 | Refer to a table nested in a hierarchy (e.g. within a schema) 29 | } 30 | \examples{ 31 | x <- Table("a", "b") 32 | x 33 | dbQuoteIdentifier(ANSI(), x) 34 | } 35 | 36 | -------------------------------------------------------------------------------- /R/Table.R: -------------------------------------------------------------------------------- 1 | #' @export 2 | #' @rdname Table 3 | setClass("Table", slots = list(name = "character")) 4 | 5 | #' Refer to a table nested in a hierarchy (e.g. within a schema) 6 | #' 7 | #' @param ... Components of the hierarchy, e.g. \code{schema}, \code{table}, 8 | #' or \code{cluster}, \code{catalog}, \code{schema}, \code{table}. 9 | #' For more on these concepts, see 10 | #' \url{http://stackoverflow.com/questions/7022755/} 11 | #' @param conn,x Connection and Table used when escaping. 12 | #' @param object Table object to print 13 | #' @export 14 | #' @examples 15 | #' x <- Table("a", "b") 16 | #' x 17 | #' dbQuoteIdentifier(ANSI(), x) 18 | Table <- function(...) { 19 | new("Table", name = c(...)) 20 | } 21 | 22 | #' @export 23 | #' @rdname Table 24 | setMethod("dbQuoteIdentifier", c("DBIConnection", "Table"), 25 | function(conn, x, ...) { 26 | SQL(paste0(dbQuoteIdentifier(conn, x@name), collapse = ".")) 27 | } 28 | ) 29 | 30 | #' @export 31 | #' @rdname Table 32 | setMethod("show", "Table", function(object) { 33 | cat(" ", paste0(object@name, collapse = "."), "\n", sep = "") 34 | }) 35 | -------------------------------------------------------------------------------- /src/sqldelim.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | struct QuoteSpec { 5 | char startChar; 6 | char endChar; 7 | char escapeChar; 8 | bool doubleEscape; 9 | 10 | QuoteSpec(char startChar, char endChar, char escapeChar, char doubleEscape); 11 | }; 12 | 13 | struct CommentSpec { 14 | std::string startStr; 15 | std::string endStr; 16 | bool endStrRequired; 17 | 18 | CommentSpec(const std::string& startStr, const std::string& endStr, bool endStrRequired); 19 | }; 20 | 21 | struct Region { 22 | int startOffset; 23 | int length; 24 | }; 25 | 26 | typedef std::vector QuoteSpecs; 27 | typedef std::vector CommentSpecs; 28 | typedef std::vector Regions; 29 | 30 | struct ParseResult { 31 | bool success; 32 | Regions regions; 33 | std::string errorMessage; 34 | size_t errorOffset; 35 | 36 | ParseResult(const std::string& error, size_t pos) : 37 | success(false), errorMessage(error), errorOffset(pos) { 38 | } 39 | 40 | ParseResult(const Regions& regions) : 41 | success(true), regions(regions) { 42 | } 43 | }; 44 | 45 | ParseResult parseQuery(const std::string& query, const QuoteSpecs& quoteSpecs); 46 | -------------------------------------------------------------------------------- /man/transactions.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2 (4.1.0): do not edit by hand 2 | % Please edit documentation in R/Transactions.R 3 | \docType{methods} 4 | \name{transactions} 5 | \alias{sqlTransactionCommit} 6 | \alias{sqlTransactionCommit,DBIConnection-method} 7 | \alias{sqlTransactionRollback} 8 | \alias{sqlTransactionRollback,DBIConnection-method} 9 | \alias{sqlTransactionStart} 10 | \alias{sqlTransactionStart,DBIConnection-method} 11 | \alias{transactions} 12 | \title{Start, commit or rollback a transaction} 13 | \usage{ 14 | sqlTransactionStart(con, ...) 15 | 16 | \S4method{sqlTransactionStart}{DBIConnection}(con, ...) 17 | 18 | sqlTransactionCommit(con, ...) 19 | 20 | \S4method{sqlTransactionCommit}{DBIConnection}(con, ...) 21 | 22 | sqlTransactionRollback(con, ...) 23 | 24 | \S4method{sqlTransactionRollback}{DBIConnection}(con, ...) 25 | } 26 | \arguments{ 27 | \item{con}{A database connection.} 28 | 29 | \item{...}{Other arguments used by individual methods.} 30 | } 31 | \description{ 32 | Start, commit or rollback a transaction 33 | } 34 | \examples{ 35 | sqlTransactionStart(ANSI()) 36 | sqlTransactionCommit(ANSI()) 37 | sqlTransactionRollback(ANSI()) 38 | } 39 | 40 | -------------------------------------------------------------------------------- /R/Transactions.R: -------------------------------------------------------------------------------- 1 | #' Start, commit or rollback a transaction 2 | #' 3 | #' @inheritParams sqlTableCreate 4 | #' @name transactions 5 | #' @examples 6 | #' sqlTransactionStart(ANSI()) 7 | #' sqlTransactionCommit(ANSI()) 8 | #' sqlTransactionRollback(ANSI()) 9 | NULL 10 | 11 | #' @export 12 | #' @rdname transactions 13 | setGeneric("sqlTransactionStart", function(con, ...) { 14 | standardGeneric("sqlTransactionStart") 15 | }) 16 | 17 | #' @export 18 | #' @rdname transactions 19 | setMethod("sqlTransactionStart", "DBIConnection", function(con, ...) { 20 | SQL("START TRANSACTION") 21 | }) 22 | 23 | #' @export 24 | #' @rdname transactions 25 | setGeneric("sqlTransactionCommit", function(con, ...) { 26 | standardGeneric("sqlTransactionCommit") 27 | }) 28 | 29 | #' @export 30 | #' @rdname transactions 31 | setMethod("sqlTransactionCommit", "DBIConnection", function(con, ...) { 32 | SQL("COMMIT") 33 | }) 34 | 35 | #' @export 36 | #' @rdname transactions 37 | setGeneric("sqlTransactionRollback", function(con, ...) { 38 | standardGeneric("sqlTransactionRollback") 39 | }) 40 | 41 | #' @export 42 | #' @rdname transactions 43 | setMethod("sqlTransactionRollback", "DBIConnection", function(con, ...) { 44 | SQL("ROLLBACK") 45 | }) 46 | -------------------------------------------------------------------------------- /tests/testthat/test-rownames.R: -------------------------------------------------------------------------------- 1 | context("Rowname translation") 2 | 3 | test_that("logical row names uses default name", { 4 | df <- data.frame(x = c(a = 1)) 5 | 6 | expect_equal(rownamesToColumn(df, NA)$row_name, "a") 7 | expect_equal(rownamesToColumn(df, T)$row_name, "a") 8 | expect_equal(rownamesToColumn(df, F)$row_name, NULL) 9 | }) 10 | 11 | test_that("character row names uses supplied name", { 12 | df <- data.frame(x = c(a = 1)) 13 | 14 | expect_equal(rownamesToColumn(df, "x")$x, "a") 15 | }) 16 | 17 | test_that("no rownames in input gives no rownames in output", { 18 | df <- data.frame(x = 1) 19 | expect_equal(rownamesToColumn(df, NA)$row_name, NULL) 20 | }) 21 | 22 | test_that("guess identify of row_names columns", { 23 | df <- data.frame(row_names = "a", x = 1, stringsAsFactors = FALSE) 24 | 25 | expect_equal(row.names(columnToRownames(df, NA)), "a") 26 | expect_equal(row.names(columnToRownames(df, TRUE)), "a") 27 | expect_equal(row.names(columnToRownames(df, FALSE)), "1") 28 | }) 29 | 30 | test_that("override identify of row_names column", { 31 | df <- data.frame(x = 1, y = "a", stringsAsFactors = FALSE) 32 | 33 | expect_equal(row.names(columnToRownames(df, "y")), "a") 34 | expect_error(row.names(columnToRownames(df, "z")), "not present") 35 | }) 36 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2 (4.1.0): do not edit by hand 2 | 3 | export(ANSI) 4 | export(CommentSpec) 5 | export(QuoteSpec) 6 | export(Table) 7 | export(columnToRownames) 8 | export(rownamesToColumn) 9 | export(sqlData) 10 | export(sqlInterpolate) 11 | export(sqlParseVariables) 12 | export(sqlParseVariablesImpl) 13 | export(sqlTableCreate) 14 | export(sqlTableDrop) 15 | export(sqlTableInsertInto) 16 | export(sqlTableInsertIntoTemplate) 17 | export(sqlTransactionCommit) 18 | export(sqlTransactionRollback) 19 | export(sqlTransactionStart) 20 | exportClasses(Table) 21 | exportMethods(dbQuoteIdentifier) 22 | exportMethods(show) 23 | exportMethods(sqlData) 24 | exportMethods(sqlInterpolate) 25 | exportMethods(sqlParseVariables) 26 | exportMethods(sqlTableCreate) 27 | exportMethods(sqlTableDrop) 28 | exportMethods(sqlTableInsertInto) 29 | exportMethods(sqlTransactionCommit) 30 | exportMethods(sqlTransactionRollback) 31 | exportMethods(sqlTransactionStart) 32 | importClassesFrom(DBI,DBIConnection) 33 | importClassesFrom(DBI,DBIObject) 34 | importFrom(DBI,ANSI) 35 | importFrom(DBI,SQL) 36 | importFrom(DBI,dbQuoteIdentifier) 37 | importFrom(DBI,dbQuoteString) 38 | importFrom(Rcpp,sourceCpp) 39 | importFrom(methods,new) 40 | importFrom(methods,setClass) 41 | importFrom(methods,setGeneric) 42 | importFrom(methods,setMethod) 43 | useDynLib(SQL) 44 | -------------------------------------------------------------------------------- /man/sqlTableInsertIntoTemplate.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2 (4.1.0): do not edit by hand 2 | % Please edit documentation in R/TableInsertInto.R 3 | \name{sqlTableInsertIntoTemplate} 4 | \alias{sqlTableInsertIntoTemplate} 5 | \title{Generated parameterised template for inserting rows.} 6 | \usage{ 7 | sqlTableInsertIntoTemplate(con, table, values, row.names = NA, prefix = "?", 8 | ...) 9 | } 10 | \arguments{ 11 | \item{con}{A database connection.} 12 | 13 | \item{table}{Name of the table. Escaped with 14 | \code{\link[DBI]{dbQuoteIdentifier}}.} 15 | 16 | \item{values}{A data frame. Used only for the column names.} 17 | 18 | \item{row.names}{Either \code{TRUE}, \code{FALSE}, \code{NA} or a string. 19 | 20 | If \code{TRUE}, always translate row names to a column called "row_names". 21 | If \code{FALSE}, never translate row names. If \code{NA}, translate 22 | rownames only if they're a character vector. 23 | 24 | A string is equivalent to \code{TRUE}, but allows you to override the 25 | default name. 26 | 27 | For backward compatibility, \code{NULL} is equivalent to \code{FALSE}.} 28 | 29 | \item{prefix}{Parameter prefix to put in front of column id.} 30 | 31 | \item{...}{Other arguments used by individual methods.} 32 | } 33 | \description{ 34 | Generated parameterised template for inserting rows. 35 | } 36 | \examples{ 37 | sqlTableInsertIntoTemplate(ANSI(), "iris", iris) 38 | 39 | sqlTableInsertIntoTemplate(ANSI(), "mtcars", mtcars) 40 | sqlTableInsertIntoTemplate(ANSI(), "mtcars", mtcars, row.names = FALSE) 41 | } 42 | 43 | -------------------------------------------------------------------------------- /R/Data.R: -------------------------------------------------------------------------------- 1 | #' Convert a data frame into form suitable for upload to a SQL database. 2 | #' 3 | #' This is a generic method that coerces R objects into vectors suitable for 4 | #' upload to the database. The output will vary a little from method to 5 | #' method depending on whether the main upload device is through a single 6 | #' SQL string or multiple parameterised queries. 7 | #' 8 | #' The default method: 9 | #' \itemize{ 10 | #' \item Converts factors to characters 11 | #' \item Quotes all strings 12 | #' \item Converts all columns to strings 13 | #' \item Replaces NA with NULL 14 | #' } 15 | #' 16 | #' @inheritParams sqlTableCreate 17 | #' @inheritParams rownames 18 | #' @param value A data frame 19 | #' @export 20 | setGeneric("sqlData", function(con, value, row.names = NA, ...) { 21 | standardGeneric("sqlData") 22 | }) 23 | 24 | #' @rdname sqlData 25 | #' @export 26 | setMethod("sqlData", "DBIConnection", function(con, value, row.names = NA, ...) { 27 | value <- rownamesToColumn(value, row.names) 28 | 29 | # Convert factors to strings 30 | is_factor <- vapply(value, is.factor, logical(1)) 31 | value[is_factor] <- lapply(value[is_factor], as.character) 32 | 33 | # Quote all strings 34 | is_char <- vapply(value, is.character, logical(1)) 35 | value[is_char] <- lapply(value[is_char], function(x) { 36 | enc2utf8(dbQuoteString(con, x)) 37 | }) 38 | 39 | # Convert everything to character and turn NAs into NULL 40 | value[] <- lapply(value, as.character) 41 | value[is.na(value)] <- "NULL" 42 | 43 | value 44 | }) 45 | -------------------------------------------------------------------------------- /man/rownames.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2 (4.1.0): do not edit by hand 2 | % Please edit documentation in R/RowNames.R 3 | \name{rownames} 4 | \alias{columnToRownames} 5 | \alias{rownames} 6 | \alias{rownamesToColumn} 7 | \title{Convert row names back and forth between columns.} 8 | \usage{ 9 | rownamesToColumn(df, row.names = NA) 10 | 11 | columnToRownames(df, row.names = NA) 12 | } 13 | \arguments{ 14 | \item{df}{A data frame} 15 | 16 | \item{row.names}{Either \code{TRUE}, \code{FALSE}, \code{NA} or a string. 17 | 18 | If \code{TRUE}, always translate row names to a column called "row_names". 19 | If \code{FALSE}, never translate row names. If \code{NA}, translate 20 | rownames only if they're a character vector. 21 | 22 | A string is equivalent to \code{TRUE}, but allows you to override the 23 | default name. 24 | 25 | For backward compatibility, \code{NULL} is equivalent to \code{FALSE}.} 26 | } 27 | \description{ 28 | These functions provide a reasonably automatic way of preserving the row 29 | names of data frame during back-and-forth translation to a SQL table. 30 | By default, row names will be converted to an explicit column 31 | called "row_names", and any query returning a column called "row_names" 32 | will have those automatically set as row names. 33 | } 34 | \examples{ 35 | # If have row names 36 | rownamesToColumn(head(mtcars)) 37 | rownamesToColumn(head(mtcars), FALSE) 38 | rownamesToColumn(head(mtcars), "ROWNAMES") 39 | 40 | # If don't have 41 | rownamesToColumn(head(iris)) 42 | rownamesToColumn(head(iris), TRUE) 43 | rownamesToColumn(head(iris), "ROWNAMES") 44 | } 45 | 46 | -------------------------------------------------------------------------------- /man/sqlData.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2 (4.1.0): do not edit by hand 2 | % Please edit documentation in R/Data.R 3 | \docType{methods} 4 | \name{sqlData} 5 | \alias{sqlData} 6 | \alias{sqlData,DBIConnection-method} 7 | \title{Convert a data frame into form suitable for upload to a SQL database.} 8 | \usage{ 9 | sqlData(con, value, row.names = NA, ...) 10 | 11 | \S4method{sqlData}{DBIConnection}(con, value, row.names = NA, ...) 12 | } 13 | \arguments{ 14 | \item{con}{A database connection.} 15 | 16 | \item{value}{A data frame} 17 | 18 | \item{row.names}{Either \code{TRUE}, \code{FALSE}, \code{NA} or a string. 19 | 20 | If \code{TRUE}, always translate row names to a column called "row_names". 21 | If \code{FALSE}, never translate row names. If \code{NA}, translate 22 | rownames only if they're a character vector. 23 | 24 | A string is equivalent to \code{TRUE}, but allows you to override the 25 | default name. 26 | 27 | For backward compatibility, \code{NULL} is equivalent to \code{FALSE}.} 28 | 29 | \item{...}{Other arguments used by individual methods.} 30 | } 31 | \description{ 32 | This is a generic method that coerces R objects into vectors suitable for 33 | upload to the database. The output will vary a little from method to 34 | method depending on whether the main upload device is through a single 35 | SQL string or multiple parameterised queries. 36 | } 37 | \details{ 38 | The default method: 39 | \itemize{ 40 | \item Converts factors to characters 41 | \item Quotes all strings 42 | \item Converts all columns to strings 43 | \item Replaces NA with NULL 44 | } 45 | } 46 | 47 | -------------------------------------------------------------------------------- /man/sqlTableInsertInto.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2 (4.1.0): do not edit by hand 2 | % Please edit documentation in R/TableInsertInto.R 3 | \docType{methods} 4 | \name{sqlTableInsertInto} 5 | \alias{sqlTableInsertInto} 6 | \alias{sqlTableInsertInto,DBIConnection-method} 7 | \title{Insert rows into a table.} 8 | \usage{ 9 | sqlTableInsertInto(con, table, values, row.names = NA, ...) 10 | 11 | \S4method{sqlTableInsertInto}{DBIConnection}(con, table, values, 12 | row.names = NA, ...) 13 | } 14 | \arguments{ 15 | \item{con}{A database connection.} 16 | 17 | \item{table}{Name of the table. Escaped with 18 | \code{\link[DBI]{dbQuoteIdentifier}}.} 19 | 20 | \item{values}{A data frame. Factors will be converted to character vectors. 21 | Character vectors will be escaped with \code{\link[DBI]{dbQuoteString}}.} 22 | 23 | \item{row.names}{Either \code{TRUE}, \code{FALSE}, \code{NA} or a string. 24 | 25 | If \code{TRUE}, always translate row names to a column called "row_names". 26 | If \code{FALSE}, never translate row names. If \code{NA}, translate 27 | rownames only if they're a character vector. 28 | 29 | A string is equivalent to \code{TRUE}, but allows you to override the 30 | default name. 31 | 32 | For backward compatibility, \code{NULL} is equivalent to \code{FALSE}.} 33 | 34 | \item{...}{Other arguments used by individual methods.} 35 | } 36 | \description{ 37 | Insert rows into a table. 38 | } 39 | \examples{ 40 | sqlTableInsertInto(ANSI(), "iris", head(iris)) 41 | 42 | sqlTableInsertInto(ANSI(), "mtcars", head(mtcars)) 43 | sqlTableInsertInto(ANSI(), "mtcars", head(mtcars), row.names = FALSE) 44 | } 45 | 46 | -------------------------------------------------------------------------------- /man/sqlInterpolate.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2 (4.1.0): do not edit by hand 2 | % Please edit documentation in R/Interpolate.R 3 | \docType{methods} 4 | \name{sqlInterpolate} 5 | \alias{sqlInterpolate} 6 | \alias{sqlInterpolate,DBIConnection-method} 7 | \title{Safely interpolate values into an SQL string.} 8 | \usage{ 9 | sqlInterpolate(`_con`, `_sql`, ...) 10 | 11 | \S4method{sqlInterpolate}{DBIConnection}(`_con`, `_sql`, ...) 12 | } 13 | \arguments{ 14 | \item{_con}{A database connection.} 15 | 16 | \item{...}{Named values to interpolate into string. All strings 17 | will be first escaped with \code{\link[DBI]{dbQuoteString}} prior 18 | to interpolation to protect against SQL interpolation attacks.} 19 | 20 | \item{`_sql`}{A SQL string containing containing variables to interpolate. 21 | Variables must start with a question mark and can be any valid R 22 | identifier, i.e. it must start with a letter or \code{.}, and be followed 23 | by a letter, digit, \code{.} or \code{_}.} 24 | } 25 | \description{ 26 | Safely interpolate values into an SQL string. 27 | } 28 | \section{Backend authors}{ 29 | 30 | If you are implementing a SQL backend with non-ANSI quoting rules, you'll 31 | need to implement a method for \code{\link{sqlParseVariables}}. Failure to 32 | do so does not expose you to SQL injection attacks, but will (rarely) result 33 | in errors matching supplied and interpolated variables. 34 | } 35 | \examples{ 36 | sql <- "SELECT * FROM X WHERE name = ?name" 37 | sqlInterpolate(ANSI(), sql, name = "Hadley") 38 | # This is safe because the single quote has been double escaped 39 | sqlInterpolate(ANSI(), sql, name = "H'); DROP TABLE--;") 40 | } 41 | 42 | -------------------------------------------------------------------------------- /man/sqlParseVariables.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2 (4.1.0): do not edit by hand 2 | % Please edit documentation in R/Interpolate.R, R/RcppExports.R 3 | \docType{methods} 4 | \name{sqlParseVariables} 5 | \alias{CommentSpec} 6 | \alias{QuoteSpec} 7 | \alias{sqlParseVariables} 8 | \alias{sqlParseVariables,DBIConnection-method} 9 | \alias{sqlParseVariablesImpl} 10 | \title{Parse interpolated variables from SQL.} 11 | \usage{ 12 | sqlParseVariables(con, sql, ...) 13 | 14 | \S4method{sqlParseVariables}{DBIConnection}(con, sql, ...) 15 | 16 | CommentSpec(start, end, endRequired) 17 | 18 | QuoteSpec(start, end, escape = "", doubleEscape = TRUE) 19 | 20 | sqlParseVariablesImpl(sql, quotes, comments) 21 | } 22 | \arguments{ 23 | \item{sql}{SQL to parse (a character vector of length 1)} 24 | 25 | \item{start,end}{Start and end characters for quotes and comments} 26 | 27 | \item{endRequired}{Is the ending character of a comment required?} 28 | 29 | \item{escape}{What character can be used to escape quoting characters? 30 | Defaults to \code{""}, i.e. nothing.} 31 | 32 | \item{doubleEscape}{Can quoting characters be escaped by doubling them? 33 | Defaults to \code{TRUE}.} 34 | 35 | \item{quotes}{A list of \code{QuoteSpec} calls defining the quoting 36 | specification.} 37 | 38 | \item{comments}{A list of \code{CommentSpec} calls defining the commenting 39 | specification.} 40 | } 41 | \description{ 42 | If you're implementing a backend that uses non-ANSI quoting or commenting 43 | rules, you'll need to implement a method for \code{sqlParseVariables} that 44 | calls \code{sqlParseVariablesImpl} with the appropriate quote and 45 | comment specifications. 46 | } 47 | \examples{ 48 | # Use [] for quoting and no comments 49 | sqlParseVariablesImpl("[?a]", 50 | list(QuoteSpec("[", "]", "\\\\", FALSE)), 51 | list() 52 | ) 53 | 54 | # Standard quotes, use # for commenting 55 | sqlParseVariablesImpl("# ?a\\n?b", 56 | list(QuoteSpec("'", "'"), QuoteSpec('"', '"')), 57 | list(CommentSpec("#", "\\n", FALSE)) 58 | ) 59 | } 60 | \keyword{internal} 61 | 62 | -------------------------------------------------------------------------------- /man/sqlTableCreate.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2 (4.1.0): do not edit by hand 2 | % Please edit documentation in R/TableCreate.R 3 | \docType{methods} 4 | \name{sqlTableCreate} 5 | \alias{sqlTableCreate} 6 | \alias{sqlTableCreate,DBIConnection-method} 7 | \title{Create a simple table.} 8 | \usage{ 9 | sqlTableCreate(con, table, fields, row.names = NA, temporary = FALSE, ...) 10 | 11 | \S4method{sqlTableCreate}{DBIConnection}(con, table, fields, row.names = NA, 12 | temporary = FALSE...) 13 | } 14 | \arguments{ 15 | \item{con}{A database connection.} 16 | 17 | \item{table}{Name of the table. Escaped with 18 | \code{\link[DBI]{dbQuoteIdentifier}}.} 19 | 20 | \item{fields}{Either a character vector or a data frame. 21 | 22 | A named character vector: Names are column names, values are types. 23 | Names are escaped with \code{\link[DBI]{dbQuoteIdentifier}}. 24 | Field types are unescaped. 25 | 26 | A data frame: field types are generated using 27 | \code{\link[DBI]{dbDataType}}.} 28 | 29 | \item{row.names}{Either \code{TRUE}, \code{FALSE}, \code{NA} or a string. 30 | 31 | If \code{TRUE}, always translate row names to a column called "row_names". 32 | If \code{FALSE}, never translate row names. If \code{NA}, translate 33 | rownames only if they're a character vector. 34 | 35 | A string is equivalent to \code{TRUE}, but allows you to override the 36 | default name. 37 | 38 | For backward compatibility, \code{NULL} is equivalent to \code{FALSE}.} 39 | 40 | \item{temporary}{If \code{TRUE}, will generate a temporary table statement.} 41 | 42 | \item{...}{Other arguments used by individual methods.} 43 | } 44 | \description{ 45 | Exposes interface to simple \code{CREATE TABLE} commands. The default 46 | method is ANSI SQL 99 compliant. 47 | } 48 | \section{DBI-backends}{ 49 | 50 | If you implement one method (i.e. for strings or data frames), you need 51 | to implement both, otherwise the S4 dispatch rules will be ambiguous 52 | and will generate an error on every call. 53 | } 54 | \examples{ 55 | sqlTableCreate(ANSI(), "my-table", c(a = "integer", b = "text")) 56 | sqlTableCreate(ANSI(), "my-table", iris) 57 | 58 | # By default, character row names are converted to a row_names colum 59 | sqlTableCreate(ANSI(), "mtcars", mtcars[, 1:5]) 60 | sqlTableCreate(ANSI(), "mtcars", mtcars[, 1:5], row.names = FALSE) 61 | } 62 | 63 | -------------------------------------------------------------------------------- /R/TableInsertInto.R: -------------------------------------------------------------------------------- 1 | #' Insert rows into a table. 2 | #' 3 | #' @inheritParams sqlTableCreate 4 | #' @inheritParams rownames 5 | #' @param values A data frame. Factors will be converted to character vectors. 6 | #' Character vectors will be escaped with \code{\link[DBI]{dbQuoteString}}. 7 | #' @export 8 | #' @examples 9 | #' sqlTableInsertInto(ANSI(), "iris", head(iris)) 10 | #' 11 | #' sqlTableInsertInto(ANSI(), "mtcars", head(mtcars)) 12 | #' sqlTableInsertInto(ANSI(), "mtcars", head(mtcars), row.names = FALSE) 13 | setGeneric("sqlTableInsertInto", function(con, table, values, row.names = NA, 14 | ...) { 15 | standardGeneric("sqlTableInsertInto") 16 | }) 17 | 18 | #' @export 19 | #' @rdname sqlTableInsertInto 20 | setMethod("sqlTableInsertInto", "DBIConnection", 21 | function(con, table, values, row.names = NA, ...) { 22 | stopifnot(is.data.frame(values)) 23 | 24 | sql_values <- sqlData(con, values, row.names) 25 | table <- dbQuoteIdentifier(con, table) 26 | fields <- dbQuoteIdentifier(con, names(sql_values)) 27 | 28 | # Convert fields into a character matrix 29 | rows <- do.call(paste, c(sql_values, sep = ", ")) 30 | SQL(paste0( 31 | "INSERT INTO ", table, "\n", 32 | " (", paste(fields, collapse = ", "), ")\n", 33 | "VALUES\n", 34 | paste0(" (", rows, ")", collapse = ",\n") 35 | )) 36 | } 37 | ) 38 | 39 | #' Generated parameterised template for inserting rows. 40 | #' 41 | #' @inheritParams sqlTableCreate 42 | #' @inheritParams sqlTableInsertInto 43 | #' @inheritParams rownames 44 | #' @param prefix Parameter prefix to put in front of column id. 45 | #' @param values A data frame. Used only for the column names. 46 | #' @export 47 | #' @examples 48 | #' sqlTableInsertIntoTemplate(ANSI(), "iris", iris) 49 | #' 50 | #' sqlTableInsertIntoTemplate(ANSI(), "mtcars", mtcars) 51 | #' sqlTableInsertIntoTemplate(ANSI(), "mtcars", mtcars, row.names = FALSE) 52 | sqlTableInsertIntoTemplate <- function(con, table, values, row.names = NA, prefix = "?", ...) { 53 | table <- dbQuoteIdentifier(con, table) 54 | 55 | values <- rownamesToColumn(values[0, , drop = FALSE], row.names) 56 | fields <- dbQuoteIdentifier(con, names(values)) 57 | 58 | # Convert fields into a character matrix 59 | SQL(paste0( 60 | "INSERT INTO ", table, "\n", 61 | " (", paste(fields, collapse = ", "), ")\n", 62 | "VALUES\n", 63 | paste0(" (", paste0(prefix, seq_along(fields), collapse = ", "), ")", collapse = ",\n") 64 | )) 65 | } 66 | -------------------------------------------------------------------------------- /R/TableCreate.R: -------------------------------------------------------------------------------- 1 | #' @importFrom DBI dbQuoteString dbQuoteIdentifier SQL 2 | #' @importFrom methods setGeneric setMethod setClass new 3 | NULL 4 | 5 | #' Create a simple table. 6 | #' 7 | #' Exposes interface to simple \code{CREATE TABLE} commands. The default 8 | #' method is ANSI SQL 99 compliant. 9 | #' 10 | #' @section DBI-backends: 11 | #' If you implement one method (i.e. for strings or data frames), you need 12 | #' to implement both, otherwise the S4 dispatch rules will be ambiguous 13 | #' and will generate an error on every call. 14 | #' 15 | #' @param con A database connection. 16 | #' @param table Name of the table. Escaped with 17 | #' \code{\link[DBI]{dbQuoteIdentifier}}. 18 | #' @param fields Either a character vector or a data frame. 19 | #' 20 | #' A named character vector: Names are column names, values are types. 21 | #' Names are escaped with \code{\link[DBI]{dbQuoteIdentifier}}. 22 | #' Field types are unescaped. 23 | #' 24 | #' A data frame: field types are generated using 25 | #' \code{\link[DBI]{dbDataType}}. 26 | #' @param temporary If \code{TRUE}, will generate a temporary table statement. 27 | #' @inheritParams rownames 28 | #' @param ... Other arguments used by individual methods. 29 | #' @export 30 | #' @examples 31 | #' sqlTableCreate(ANSI(), "my-table", c(a = "integer", b = "text")) 32 | #' sqlTableCreate(ANSI(), "my-table", iris) 33 | #' 34 | #' # By default, character row names are converted to a row_names colum 35 | #' sqlTableCreate(ANSI(), "mtcars", mtcars[, 1:5]) 36 | #' sqlTableCreate(ANSI(), "mtcars", mtcars[, 1:5], row.names = FALSE) 37 | setGeneric("sqlTableCreate", function(con, table, fields, row.names = NA, 38 | temporary = FALSE, ...) { 39 | standardGeneric("sqlTableCreate") 40 | }) 41 | 42 | #' @export 43 | #' @rdname sqlTableCreate 44 | setMethod("sqlTableCreate", "DBIConnection", 45 | function(con, table, fields, row.names = NA, temporary = FALSE...) { 46 | table <- dbQuoteIdentifier(con, table) 47 | 48 | if (is.data.frame(fields)) { 49 | fields <- rownamesToColumn(fields, row.names) 50 | fields <- vapply(fields, function(x) DBI::dbDataType(con, x), character(1)) 51 | } 52 | 53 | field_names <- dbQuoteIdentifier(con, names(fields)) 54 | field_types <- unname(fields) 55 | fields <- paste0(field_names, " ", field_types) 56 | 57 | SQL(paste0( 58 | "CREATE ", if (temporary) "TEMPORARY ", "TABLE ", table, " (\n", 59 | " ", paste(fields, collapse = ",\n "), "\n)\n" 60 | )) 61 | } 62 | ) 63 | -------------------------------------------------------------------------------- /R/RowNames.R: -------------------------------------------------------------------------------- 1 | #' Convert row names back and forth between columns. 2 | #' 3 | #' These functions provide a reasonably automatic way of preserving the row 4 | #' names of data frame during back-and-forth translation to a SQL table. 5 | #' By default, row names will be converted to an explicit column 6 | #' called "row_names", and any query returning a column called "row_names" 7 | #' will have those automatically set as row names. 8 | #' 9 | #' @param df A data frame 10 | #' @param row.names Either \code{TRUE}, \code{FALSE}, \code{NA} or a string. 11 | #' 12 | #' If \code{TRUE}, always translate row names to a column called "row_names". 13 | #' If \code{FALSE}, never translate row names. If \code{NA}, translate 14 | #' rownames only if they're a character vector. 15 | #' 16 | #' A string is equivalent to \code{TRUE}, but allows you to override the 17 | #' default name. 18 | #' 19 | #' For backward compatibility, \code{NULL} is equivalent to \code{FALSE}. 20 | #' @name rownames 21 | #' @examples 22 | #' # If have row names 23 | #' rownamesToColumn(head(mtcars)) 24 | #' rownamesToColumn(head(mtcars), FALSE) 25 | #' rownamesToColumn(head(mtcars), "ROWNAMES") 26 | #' 27 | #' # If don't have 28 | #' rownamesToColumn(head(iris)) 29 | #' rownamesToColumn(head(iris), TRUE) 30 | #' rownamesToColumn(head(iris), "ROWNAMES") 31 | #' 32 | NULL 33 | 34 | #' @export 35 | #' @rdname rownames 36 | rownamesToColumn <- function(df, row.names = NA) { 37 | name <- guessRowName(df, row.names) 38 | if (is.null(name)) { 39 | rownames(df) <- NULL 40 | return(df) 41 | } 42 | 43 | rn <- setNames(list(row.names(df)), name) 44 | 45 | df <- c(rn, df) 46 | class(df) <- "data.frame" 47 | attr(df, "row.names") <- .set_row_names(length(rn[[1]])) 48 | 49 | df 50 | } 51 | 52 | #' @export 53 | #' @rdname rownames 54 | columnToRownames <- function(df, row.names = NA) { 55 | name <- guessColName(df, row.names) 56 | if (is.null(name)) return(df) 57 | 58 | if (!(name %in% names(df))) { 59 | stop("Column ", name, " not present in output", call. = FALSE) 60 | } 61 | 62 | row.names(df) <- df[[name]] 63 | df[[name]] <- NULL 64 | 65 | df 66 | } 67 | 68 | guessRowName <- function(df, row.names) { 69 | if (identical(row.names, TRUE)) { 70 | "row_names" 71 | } else if (identical(row.names, FALSE) || is.null(row.names)) { 72 | NULL 73 | } else if (identical(row.names, NA)) { 74 | is_char <- is.character(attr(df, "row.names")) 75 | if (is_char) { 76 | "row_names" 77 | } else { 78 | NULL 79 | } 80 | } else if (is.character(row.names)) { 81 | row.names[1] 82 | } else { 83 | stop("Unknown input") 84 | } 85 | } 86 | 87 | guessColName <- function(df, row.names) { 88 | if (identical(row.names, TRUE)) { 89 | "row_names" 90 | } else if (identical(row.names, FALSE) || is.null(row.names)) { 91 | NULL 92 | } else if (identical(row.names, NA)) { 93 | if ("row_names" %in% names(df)) { 94 | "row_names" 95 | } else { 96 | NULL 97 | } 98 | } else if (is.character(row.names)) { 99 | row.names[1] 100 | } else { 101 | stop("Unknown input") 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /R/Interpolate.R: -------------------------------------------------------------------------------- 1 | #' Safely interpolate values into an SQL string. 2 | #' 3 | #' @section Backend authors: 4 | #' If you are implementing a SQL backend with non-ANSI quoting rules, you'll 5 | #' need to implement a method for \code{\link{sqlParseVariables}}. Failure to 6 | #' do so does not expose you to SQL injection attacks, but will (rarely) result 7 | #' in errors matching supplied and interpolated variables. 8 | #' 9 | #' @param _con A database connection. 10 | #' @param `_sql` A SQL string containing containing variables to interpolate. 11 | #' Variables must start with a question mark and can be any valid R 12 | #' identifier, i.e. it must start with a letter or \code{.}, and be followed 13 | #' by a letter, digit, \code{.} or \code{_}. 14 | #' @param ... Named values to interpolate into string. All strings 15 | #' will be first escaped with \code{\link[DBI]{dbQuoteString}} prior 16 | #' to interpolation to protect against SQL interpolation attacks. 17 | #' @export 18 | #' @examples 19 | #' sql <- "SELECT * FROM X WHERE name = ?name" 20 | #' sqlInterpolate(ANSI(), sql, name = "Hadley") 21 | #' # This is safe because the single quote has been double escaped 22 | #' sqlInterpolate(ANSI(), sql, name = "H'); DROP TABLE--;") 23 | setGeneric("sqlInterpolate", function(`_con`, `_sql`, ...) { 24 | standardGeneric("sqlInterpolate") 25 | }) 26 | 27 | #' @export 28 | #' @rdname sqlInterpolate 29 | setMethod("sqlInterpolate", "DBIConnection", function(`_con`, `_sql`, ...) { 30 | sql <- `_sql` 31 | pos <- sqlParseVariables(`_con`, sql) 32 | 33 | if (length(pos$start) == 0) 34 | return(SQL(sql)) 35 | 36 | vars <- substring(sql, pos$start + 1, pos$end) 37 | 38 | values <- list(...) 39 | if (!setequal(vars, names(values))) { 40 | stop("Supplied vars don't match vars to interpolate", call. = FALSE) 41 | } 42 | values <- values[vars] 43 | 44 | safe_values <- vapply(values, function(x) { 45 | if (is.character(x)) { 46 | dbQuoteString(`_con`, x) 47 | } else { 48 | as.character(x) 49 | } 50 | }, character(1)) 51 | 52 | for (i in rev(seq_along(vars))) { 53 | sql <- paste0( 54 | substring(sql, 0, pos$start[i] - 1), 55 | safe_values[i], 56 | substring(sql, pos$end[i] + 1, nchar(sql)) 57 | ) 58 | } 59 | 60 | SQL(sql) 61 | }) 62 | 63 | #' Parse interpolated variables from SQL. 64 | #' 65 | #' If you're implementing a backend that uses non-ANSI quoting or commenting 66 | #' rules, you'll need to implement a method for \code{sqlParseVariables} that 67 | #' calls \code{sqlParseVariablesImpl} with the appropriate quote and 68 | #' comment specifications. 69 | #' 70 | #' 71 | #' @param start,end Start and end characters for quotes and comments 72 | #' @param endRequired Is the ending character of a comment required? 73 | #' @param doubleEscape Can quoting characters be escaped by doubling them? 74 | #' Defaults to \code{TRUE}. 75 | #' @param escape What character can be used to escape quoting characters? 76 | #' Defaults to \code{""}, i.e. nothing. 77 | #' @keywords internal 78 | #' @export 79 | #' @examples 80 | #' # Use [] for quoting and no comments 81 | #' sqlParseVariablesImpl("[?a]", 82 | #' list(QuoteSpec("[", "]", "\\", FALSE)), 83 | #' list() 84 | #' ) 85 | #' 86 | #' # Standard quotes, use # for commenting 87 | #' sqlParseVariablesImpl("# ?a\n?b", 88 | #' list(QuoteSpec("'", "'"), QuoteSpec('"', '"')), 89 | #' list(CommentSpec("#", "\n", FALSE)) 90 | #' ) 91 | setGeneric("sqlParseVariables", function(con, sql, ...) { 92 | standardGeneric("sqlParseVariables") 93 | }) 94 | 95 | #' @export 96 | #' @rdname sqlParseVariables 97 | setMethod("sqlParseVariables", "DBIConnection", function(con, sql, ...) { 98 | sqlParseVariablesImpl(sql, 99 | list( 100 | QuoteSpec('"', "'"), 101 | QuoteSpec("'", "'") 102 | ), 103 | list( 104 | CommentSpec("/*", "*/", TRUE), 105 | CommentSpec("--", "\n", FALSE) 106 | ) 107 | ) 108 | }) 109 | 110 | #' @export 111 | #' @rdname sqlParseVariables 112 | CommentSpec <- function(start, end, endRequired) { 113 | list(start, end, endRequired) 114 | } 115 | 116 | #' @export 117 | #' @rdname sqlParseVariables 118 | QuoteSpec <- function(start, end, escape = "", doubleEscape = TRUE) { 119 | list(start, end, escape, doubleEscape) 120 | } 121 | -------------------------------------------------------------------------------- /src/sqldelim.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | using namespace Rcpp; 3 | 4 | #include "sqldelim.h" 5 | #include 6 | #include 7 | #include 8 | 9 | QuoteSpec::QuoteSpec(char startChar, char endChar, char escapeChar, char doubleEscape) : 10 | startChar(startChar), endChar(endChar), escapeChar(escapeChar), doubleEscape(doubleEscape) { 11 | } 12 | 13 | CommentSpec::CommentSpec(const std::string& startStr, const std::string& endStr, bool endStrRequired) : 14 | startStr(startStr), endStr(endStr), endStrRequired(endStrRequired) { 15 | } 16 | 17 | 18 | namespace { 19 | template 20 | bool hasPrefix(T begin, T end, const std::string& prefix) { 21 | if (prefix.size() == 0) { 22 | return false; 23 | } 24 | 25 | if (end - begin < prefix.size()) { 26 | return false; 27 | } 28 | for (size_t i = 0; i < prefix.size(); i++) { 29 | if (*(begin + i) != prefix[i]) { 30 | return false; 31 | } 32 | } 33 | return true; 34 | } 35 | 36 | template 37 | bool findQuoteEnd(T begin, T end, const QuoteSpec& quoteSpec, T *regionEnd) { 38 | T pos = begin; 39 | 40 | // Assert that the quoted region starts with the character we 41 | // expect. If not, we were called in error. 42 | assert(*pos == quoteSpec.startChar); 43 | pos++; 44 | 45 | const int STATE_NORMAL = 0; 46 | const int STATE_ESCAPE = 1; 47 | const int STATE_DOUBLE = 2; 48 | int state = STATE_NORMAL; 49 | 50 | for (; pos != end; pos++) { 51 | switch (state) { 52 | case STATE_NORMAL: 53 | if (*pos == quoteSpec.endChar) { 54 | if (quoteSpec.doubleEscape) { 55 | state = STATE_DOUBLE; 56 | } else { 57 | *regionEnd = pos + 1; 58 | return true; 59 | } 60 | } else if (*pos == quoteSpec.escapeChar) { 61 | state = STATE_ESCAPE; 62 | } 63 | break; 64 | case STATE_ESCAPE: 65 | state = STATE_NORMAL; 66 | break; 67 | case STATE_DOUBLE: 68 | if (*pos == quoteSpec.endChar) { 69 | state = STATE_NORMAL; 70 | } else { 71 | *regionEnd = pos; 72 | return true; 73 | } 74 | break; 75 | default: 76 | assert(false); 77 | return false; 78 | } 79 | } 80 | 81 | if (state == STATE_DOUBLE) { 82 | *regionEnd = end; 83 | return true; 84 | } 85 | 86 | return false; 87 | } 88 | 89 | template 90 | bool findCommentEnd(T begin, T end, const CommentSpec& commentSpec, T *regionEnd) { 91 | for (; begin != end; begin++) { 92 | if (hasPrefix(begin, end, commentSpec.endStr)) { 93 | *regionEnd = begin + commentSpec.endStr.size(); 94 | return true; 95 | } 96 | } 97 | 98 | if (commentSpec.endStrRequired) { 99 | return false; 100 | } else { 101 | *regionEnd = end; 102 | return true; 103 | } 104 | } 105 | 106 | size_t whichQuote(char c, const QuoteSpecs& quoteSpecs) { 107 | for (size_t i = 0; i < quoteSpecs.size(); i++) { 108 | if (c == quoteSpecs[i].startChar) { 109 | return i; 110 | } 111 | } 112 | return -1; 113 | } 114 | 115 | template 116 | size_t whichComment(T begin, T end, const CommentSpecs& commentSpecs) { 117 | for (size_t i = 0; i < commentSpecs.size(); i++) { 118 | if (hasPrefix(begin, end, commentSpecs[i].startStr)) { 119 | return i; 120 | } 121 | } 122 | return -1; 123 | } 124 | } 125 | 126 | ParseResult parseQuery(const std::string& query, const QuoteSpecs& quoteSpecs, 127 | const CommentSpecs& commentSpecs) { 128 | 129 | Regions regions; 130 | 131 | for (std::string::const_iterator it = query.begin(); 132 | it != query.end(); 133 | it++) { 134 | 135 | // Check each character to see if it starts a quoted region 136 | size_t qi = whichQuote(*it, quoteSpecs); 137 | if (qi != -1) { 138 | // It's a quoted region! Find where it ends 139 | std::string::const_iterator regionEnd; 140 | if (!findQuoteEnd(it, query.end(), quoteSpecs[qi], ®ionEnd)) { 141 | // Report error and exit 142 | return ParseResult("Unterminated literal", it - query.begin()); 143 | } else { 144 | it = regionEnd; it--; 145 | continue; 146 | } 147 | } 148 | 149 | size_t ci = whichComment(it, query.end(), commentSpecs); 150 | if (ci != -1) { 151 | // It's a comment; find where it ends 152 | std::string::const_iterator regionEnd; 153 | if (!findCommentEnd(it, query.end(), commentSpecs[ci], ®ionEnd)) { 154 | // Report error and exit 155 | return ParseResult("Unterminated comment", it - query.begin()); 156 | } else { 157 | it = regionEnd; it--; 158 | continue; 159 | } 160 | } 161 | 162 | // Is this a variable? 163 | if (*it == '?') { 164 | if (it + 1 == query.end()) 165 | return ParseResult("Length 0 variable", it - query.begin()); 166 | 167 | size_t start = it - query.begin(); 168 | 169 | it++; 170 | while (it != query.end() && (isalnum(*it) || *it == '_' || *it == '.')) 171 | it++; 172 | 173 | it--; 174 | Region region; 175 | region.startOffset = start; 176 | region.length = (it - query.begin()) - start + 1; 177 | regions.push_back(region); 178 | 179 | continue; 180 | } 181 | } 182 | 183 | return ParseResult(regions); 184 | } 185 | 186 | //' @export 187 | //' @rdname sqlParseVariables 188 | //' @param sql SQL to parse (a character vector of length 1) 189 | //' @param quotes A list of \code{QuoteSpec} calls defining the quoting 190 | //' specification. 191 | //' @param comments A list of \code{CommentSpec} calls defining the commenting 192 | //' specification. 193 | // [[Rcpp::export]] 194 | List sqlParseVariablesImpl(std::string sql, ListOf quotes, ListOf comments) { 195 | if (sql.size() == 0) { 196 | return List::create( 197 | _["start"] = IntegerVector(0), 198 | _["end"] = IntegerVector(0) 199 | ); 200 | } 201 | 202 | QuoteSpecs quoteSpecs; 203 | for (int i = 0; i < quotes.size(); ++i) { 204 | List quote = quotes[i]; 205 | 206 | quoteSpecs.push_back(QuoteSpec( 207 | as(quote[0])[0], 208 | as(quote[1])[0], 209 | as(quote[2])[0], 210 | as(quote[3]) 211 | )); 212 | } 213 | 214 | CommentSpecs commentSpecs; 215 | for (int i = 0; i < comments.size(); ++i) { 216 | List comment = comments[i]; 217 | 218 | commentSpecs.push_back(CommentSpec( 219 | as(comment[0]), 220 | as(comment[1]), 221 | as(comment[2]) 222 | )); 223 | } 224 | 225 | ParseResult result = parseQuery(sql, quoteSpecs, commentSpecs); 226 | 227 | if (!result.success) { 228 | std::stringstream err; 229 | err << result.errorMessage << " (pos: " << result.errorOffset << ")"; 230 | stop(err.str()); 231 | } 232 | 233 | // Convert result into list 234 | Regions regions = result.regions; 235 | int n = regions.size(); 236 | IntegerVector start(n), end(n); 237 | 238 | for (int i = 0; i < n; ++i) { 239 | Region reg = regions[i]; 240 | 241 | start[i] = reg.startOffset + 1; 242 | end[i] = reg.startOffset + 1 + reg.length - 1; // -1 because string indexing is inclusive in R 243 | } 244 | return List::create( 245 | _["start"] = start, 246 | _["end"] = end 247 | ); 248 | } 249 | 250 | --------------------------------------------------------------------------------