diff --git a/DESCRIPTION b/DESCRIPTION
new file mode 100644
index 0000000..5de5c82
--- /dev/null
+++ b/DESCRIPTION
@@ -0,0 +1,42 @@
+Encoding: UTF-8
+Package: RPostgres
+Version: 1.0-3
+Date: 2017-12-06
+Title: 'Rcpp' Interface to 'PostgreSQL'
+Authors@R: c(
+ person("Hadley", "Wickham", role = "aut"),
+ person("Jeroen", "Ooms", role = "aut"),
+ person("Kirill", "Müller", role = "cre", email = "krlmlr+r@mailbox.org"),
+ person("RStudio", role = "cph"),
+ person("R Consortium", role = "cph"),
+ person("Tomoaki", "Nishiyama", role = "ctb",
+ comment = "Code for encoding vectors into strings derived from RPostgreSQL"),
+ person("Kungliga Tekniska Högskolan", role = "ctb", comment = "Source code for timegm")
+ )
+Description:
+ Fully 'DBI'-compliant 'Rcpp'-backed interface to 'PostgreSQL' ,
+ an open-source relational database.
+License: GPL-2
+LazyLoad: true
+Depends: R (>= 3.1.0)
+Imports: bit64, blob, DBI (>= 0.7), hms, methods, Rcpp (>= 0.11.4.2),
+ withr
+Suggests: DBItest, testthat
+LinkingTo: BH, plogr, Rcpp
+Collate: 'PqDriver.R' 'PqConnection.R' 'PqResult.R' 'RPostgres-pkg.R'
+ 'RcppExports.R' 'default.R' 'quote.R' 'tables.R'
+ 'transactions.R' 'utils.R'
+RoxygenNote: 6.0.1
+NeedsCompilation: yes
+Packaged: 2017-12-06 00:17:52 UTC; muelleki
+Author: Hadley Wickham [aut],
+ Jeroen Ooms [aut],
+ Kirill Müller [cre],
+ RStudio [cph],
+ R Consortium [cph],
+ Tomoaki Nishiyama [ctb] (Code for encoding vectors into strings derived
+ from RPostgreSQL),
+ Kungliga Tekniska Högskolan [ctb] (Source code for timegm)
+Maintainer: Kirill Müller
+Repository: CRAN
+Date/Publication: 2017-12-06 10:21:43 UTC
diff --git a/MD5 b/MD5
new file mode 100644
index 0000000..e187fa5
--- /dev/null
+++ b/MD5
@@ -0,0 +1,81 @@
+488ff95ba8e2d7e9719b961276dff586 *DESCRIPTION
+feec1bfbc596fa53152300f94a3a7ab7 *NAMESPACE
+466d97216e230f9984d18d9eba02221e *NEWS.md
+d3949d39c6e0d3ebb30f5f47e69ed7d2 *R/PqConnection.R
+03a867755b841450b855879abbf62b30 *R/PqDriver.R
+b91897c5beee9b99af6fd55051171a80 *R/PqResult.R
+0b846aae48eacea9831cd61b50eedea2 *R/RPostgres-pkg.R
+971e28b404b0401f5b8c38db70e85ff2 *R/RcppExports.R
+ff99d2cac7decf472cd0bc8291a78e49 *R/default.R
+4ab74e90a8c8573ecb483e069793bb28 *R/quote.R
+178feaef07207f895f30fd44b038ace6 *R/tables.R
+bbad0daadf44d8a9a43a0a720bef3803 *R/transactions.R
+c3efac6c8ef323cae26e037cce6c0d16 *R/utils.R
+f3da0de24104764362936f522e26ec6f *README.md
+8131313a7c1ae9b3ca097dc23a33918d *configure
+d41d8cd98f00b204e9800998ecf8427e *configure.win
+254056c489b1f3e7cc8cdc9fe4a14255 *man/Postgres.Rd
+fcfcf00fa9190113b6ee073f9fd11e2c *man/PqConnection-class.Rd
+26ccd704aecacc6484f3ea5b2e3eaa5f *man/PqDriver-class.Rd
+6ebae0927715384b8e90764e3ad2ba87 *man/PqResult-class.Rd
+16132b2b8d5c154d5ad5f9f4bf63cc81 *man/RPostgres-package.Rd
+3968c41c099acd7c228e85cc99655961 *man/dbConnect-PqDriver-method.Rd
+dda833f4b2c036ce15b0555c49add570 *man/dbDataType.Rd
+549393f4e48dad8a411932f23b543930 *man/postgres-query.Rd
+8e99dd84756b338faa2981ff82a8bfc0 *man/postgres-tables.Rd
+068c48521487f453e0c5b15bc6605cfe *man/postgres-transactions.Rd
+0f380511ba5e72720224904f24e3a42b *man/postgresHasDefault.Rd
+c4a8dbefb34cf9b1b599d26fc3ab3612 *man/quote.Rd
+fac812734f22d35b7a19561ea33d2a7c *src/DbColumn.cpp
+0d0f6283e1aee5ba374ac6c30ce35a57 *src/DbColumn.h
+1bead3e4afde4eb4237507be9fd81801 *src/DbColumnDataSource.cpp
+c1bd654b0f1d00b85c0213fb15266100 *src/DbColumnDataSource.h
+b54f1bed5e031730d84ea6ac5678d57c *src/DbColumnDataSourceFactory.cpp
+bdb2b7ed1a0c451b46b90f5ba0cae405 *src/DbColumnDataSourceFactory.h
+1ba5afa61895d800dbe19155f7d2c492 *src/DbColumnDataType.h
+4d4e148248bbebe297320f33a3244def *src/DbColumnStorage.cpp
+00308bd348a82fa234bc3faf490c3bb5 *src/DbColumnStorage.h
+e6793b8d19bb3599363801a69f689fb7 *src/DbConnection.cpp
+91fd7039d2c543d73abf05c10937928a *src/DbConnection.h
+52f74e46125617344b9ef6c064922057 *src/DbDataFrame.cpp
+ce8cede04ec884229dfec09a95128ad9 *src/DbDataFrame.h
+add0543c36d85ecd4d07d3aec205a3d1 *src/DbResult.cpp
+a924adc6fcb47073a85a603d579bb3fc *src/DbResult.h
+111e1efb50ff675feb507ea42a24b300 *src/Makevars.in
+c5a5d51517569b4577b384cbbc412ad5 *src/Makevars.win
+16024658adfb1ccb34b929298ac0396e *src/PqColumnDataSource.cpp
+5b65a687e65c37ef8b4f6b0427313c9b *src/PqColumnDataSource.h
+d94f34a4e740f7d2f7b1f46c5bbc9411 *src/PqColumnDataSourceFactory.cpp
+5932e6e86bc3c43a31954bfd1dac8117 *src/PqColumnDataSourceFactory.h
+8dd8d1fe7af6e996859ef849d6b72993 *src/PqDataFrame.cpp
+3fb6f7104460055e29d87402ef538b55 *src/PqDataFrame.h
+f0cc2e38a2445b24a32298045d42396b *src/PqResultImpl.cpp
+68d9a7002c6ded7a86df79e6281b367c *src/PqResultImpl.h
+4601b2b6e925fa6761186d25fa93f9f5 *src/PqResultSource.cpp
+e2d8563ac79450e3f2347ebc5259ba0e *src/PqResultSource.h
+74630eb1d3dd72f0e20c90f0deac0ba6 *src/RPostgres-init.c
+5fd33ab4f8ba8480acb085aa8a5bf94d *src/RPostgres_types.h
+57b657eeee6da528d4522e3c31781597 *src/RcppExports.cpp
+861292357d8bb45348cbc0a2c3c00d2c *src/connection.cpp
+40ca6054ebb1ae2e41a65c4f7d8ee5f3 *src/encode.cpp
+dfb7deb192ff48e8d33f196f1afaa3d6 *src/encode.h
+6b44efff9fefa69c04f41a3cbf3b1ce3 *src/encrypt.cpp
+9a3d1653a1ced0d8daac4102701ec18a *src/integer64.h
+ecf4e945c36fea5564e665ff5012c19d *src/logging.cpp
+7291096e5ad632a53bf9483e3020c1ea *src/pch.h
+99039714005147435bebbbd0a640d897 *src/result.cpp
+e2f57a97ad5e5d484dc0de7d0656faea *src/win32/timegm.c
+4111945e05f4e4a31c72f6ef63b1f1e6 *tests/testthat.R
+a74d10622304ddd4ebf25b9b2a11b71c *tests/testthat/helper-DBItest.R
+ac3971b62e24ae2cc633a12eb1aa0d3d *tests/testthat/helper-astyle.R
+5f99bf46169a27c39db6b35611d0da42 *tests/testthat/helper-with_database_connection.R
+f251e8d8c1249e774674be514615eeec *tests/testthat/helper-with_table.R
+78eff1c41624d5eb4d7e5036b59b7d80 *tests/testthat/helper-without_rownames.R
+000f052dbee7a744d411e507f6efbb9e *tests/testthat/test-DBItest.R
+29890f7f72946cbf870c714ebeaee604 *tests/testthat/test-bigint.R
+d0d355c427d7373ab8e6a85d655a381b *tests/testthat/test-data-type.R
+3fb2c6809894d3fc84a1832ca0509707 *tests/testthat/test-dbConnect.R
+9939d01b8b9c8b6c138e1466a1a74c09 *tests/testthat/test-dbGetQuery.R
+dfc643c4d14d440fb7b02648551dca58 *tests/testthat/test-dbWriteTable.R
+07ec38d74f52ade1ddc67997730ad1ca *tests/testthat/test-encoding.R
+5170ffd2f4d60933fe94753bc048c7f2 *tools/winlibs.R
diff --git a/NAMESPACE b/NAMESPACE
new file mode 100644
index 0000000..1a9b079
--- /dev/null
+++ b/NAMESPACE
@@ -0,0 +1,44 @@
+# Generated by roxygen2: do not edit by hand
+
+export(Postgres)
+export(postgresDefault)
+export(postgresHasDefault)
+exportClasses(PqConnection)
+exportClasses(PqDriver)
+exportClasses(PqResult)
+exportMethods(dbBegin)
+exportMethods(dbBind)
+exportMethods(dbClearResult)
+exportMethods(dbColumnInfo)
+exportMethods(dbCommit)
+exportMethods(dbConnect)
+exportMethods(dbDataType)
+exportMethods(dbDisconnect)
+exportMethods(dbExistsTable)
+exportMethods(dbFetch)
+exportMethods(dbGetInfo)
+exportMethods(dbGetRowCount)
+exportMethods(dbGetRowsAffected)
+exportMethods(dbGetStatement)
+exportMethods(dbHasCompleted)
+exportMethods(dbIsValid)
+exportMethods(dbListFields)
+exportMethods(dbListTables)
+exportMethods(dbQuoteIdentifier)
+exportMethods(dbQuoteLiteral)
+exportMethods(dbQuoteString)
+exportMethods(dbReadTable)
+exportMethods(dbRemoveTable)
+exportMethods(dbRollback)
+exportMethods(dbSendQuery)
+exportMethods(dbUnloadDriver)
+exportMethods(dbWriteTable)
+exportMethods(show)
+exportMethods(sqlData)
+import(DBI)
+import(methods)
+importFrom(Rcpp,evalCpp)
+importFrom(bit64,integer64)
+importFrom(blob,blob)
+importFrom(hms,hms)
+useDynLib(RPostgres, .registration = TRUE)
diff --git a/NEWS.md b/NEWS.md
new file mode 100644
index 0000000..218ee75
--- /dev/null
+++ b/NEWS.md
@@ -0,0 +1,10 @@
+# RPostgres 1.0-3 (2017-12-01)
+
+Initial release, compliant to the DBI specification.
+
+- Test almost all test cases of the DBI specification.
+- Fully support parametrized queries.
+- Spec-compliant transactions.
+- 64-bit integers are now supported through the `bit64` package. This also means that numeric literals (as in `SELECT 1`) are returned as 64-bit integers. The `bigint` argument to `dbConnect()` allows overriding the data type on a per-connection basis.
+- Correct handling of DATETIME and TIME columns.
+- New default `row.names = FALSE`.
diff --git a/R/PqConnection.R b/R/PqConnection.R
new file mode 100644
index 0000000..c98c617
--- /dev/null
+++ b/R/PqConnection.R
@@ -0,0 +1,174 @@
+#' @include PqDriver.R
+NULL
+
+#' PqConnection and methods.
+#'
+#' @keywords internal
+#' @export
+setClass("PqConnection",
+ contains = "DBIConnection",
+ slots = list(ptr = "externalptr", bigint = "character")
+)
+
+# show()
+#' @export
+#' @rdname PqConnection-class
+setMethod("show", "PqConnection", function(object) {
+ info <- dbGetInfo(object)
+
+ if (info$host == "") {
+ host <- "socket"
+ } else {
+ host <- paste0(info$host, ":", info$port)
+ }
+
+ cat(" ", info$dbname, "@", host, "\n", sep = "")
+})
+
+# dbIsValid()
+#' @export
+#' @rdname PqConnection-class
+setMethod("dbIsValid", "PqConnection", function(dbObj, ...) {
+ connection_valid(dbObj@ptr)
+})
+
+# dbDisconnect()
+#' @export
+#' @rdname dbConnect-PqDriver-method
+setMethod("dbDisconnect", "PqConnection", function(conn, ...) {
+ connection_release(conn@ptr)
+ invisible(TRUE)
+})
+
+# dbSendQuery()
+
+# dbSendStatement()
+
+# dbDataType()
+#' @export
+#' @rdname dbDataType
+setMethod("dbDataType", "PqConnection", function(dbObj, obj, ...) {
+ if (is.data.frame(obj)) return(vapply(obj, dbDataType, "", dbObj = dbObj))
+ get_data_type(obj)
+})
+
+get_data_type <- function(obj) {
+ if (is.factor(obj)) return("TEXT")
+ if (inherits(obj, "POSIXt")) return("TIMESTAMPTZ")
+ if (inherits(obj, "Date")) return("DATE")
+ if (inherits(obj, "difftime")) return("TIME")
+ switch(typeof(obj),
+ integer = "INTEGER",
+ double = "REAL",
+ character = "TEXT",
+ logical = "BOOLEAN",
+ list = "BYTEA",
+ stop("Unsupported type", call. = FALSE)
+ )
+}
+
+# dbQuoteString()
+
+# dbQuoteIdentifier()
+
+# dbWriteTable()
+
+# dbReadTable()
+
+# dbListTables()
+
+# dbExistsTable()
+
+# dbListFields()
+
+# dbRemoveTable()
+
+# dbGetInfo()
+#' @export
+#' @rdname PqConnection-class
+setMethod("dbGetInfo", "PqConnection", function(dbObj, ...) {
+ connection_info(dbObj@ptr)
+})
+
+# dbBegin()
+
+# dbCommit()
+
+# dbRollback()
+
+# other
+
+#' Connect to a PostgreSQL database.
+#'
+#' Manually disconnecting a connection is not necessary with RPostgres, but
+#' still recommended;
+#' if you delete the object containing the connection, it will be automatically
+#' disconnected during the next GC with a warning.
+#'
+#' @param drv `RPostgres::Postgres()`
+#' @param dbname Database name. If `NULL`, defaults to the user name.
+#' Note that this argument can only contain the database name, it will not
+#' be parsed as a connection string (internally, `expand_dbname` is set to
+#' `false` in the call to
+#' [`PQconnectdbParams()`](https://www.postgresql.org/docs/9.6/static/libpq-connect.html)).
+#' @param user,password User name and password. If `NULL`, will be
+#' retrieved from `PGUSER` and `PGPASSWORD` envvars, or from the
+#' appropriate line in `~/.pgpass`. See
+#' for
+#' more details.
+#' @param host,port Host and port. If `NULL`, will be retrieved from
+#' `PGHOST` and `PGPORT` env vars.
+#' @param service Name of service to connect as. If `NULL`, will be
+#' ignored. Otherwise, connection parameters will be loaded from the pg_service.conf
+#' file and used. See
+#' for details on this file and syntax.
+#' @param ... Other name-value pairs that describe additional connection
+#' options as described at
+#'
+#' @param bigint The R type that 64-bit integer types should be mapped to,
+#' default is [bit64::integer64], which allows the full range of 64 bit
+#' integers.
+#' @param conn Connection to disconnect.
+#' @export
+#' @examples
+#' if (postgresHasDefault()) {
+#' library(DBI)
+#' # Pass more arguments as necessary to dbConnect()
+#' con <- dbConnect(RPostgres::Postgres())
+#' dbDisconnect(con)
+#' }
+setMethod("dbConnect", "PqDriver",
+ function(drv, dbname = NULL,
+ host = NULL, port = NULL, password = NULL, user = NULL, service = NULL, ...,
+ bigint = c("integer64", "integer", "numeric", "character")) {
+
+ opts <- unlist(list(dbname = dbname, user = user, password = password,
+ host = host, port = as.character(port), service = service, client_encoding = "utf8", ...))
+ if (!is.character(opts)) {
+ stop("All options should be strings", call. = FALSE)
+ }
+ bigint <- match.arg(bigint)
+
+ if (length(opts) == 0) {
+ ptr <- connection_create(character(), character())
+ } else {
+ ptr <- connection_create(names(opts), as.vector(opts))
+ }
+
+ con <- new("PqConnection", ptr = ptr, bigint = bigint)
+ dbExecute(con, "SET TIMEZONE='UTC'")
+ con
+ })
+
+
+#' Determine database type for R vector.
+#'
+#' @export
+#' @param dbObj Postgres driver or connection.
+#' @param obj Object to convert
+#' @keywords internal
+#' @rdname dbDataType
+setMethod("dbDataType", "PqDriver", function(dbObj, obj, ...) {
+ if (is.data.frame(obj)) return(vapply(obj, dbDataType, "", dbObj = dbObj))
+ get_data_type(obj)
+})
diff --git a/R/PqDriver.R b/R/PqDriver.R
new file mode 100644
index 0000000..6c694cd
--- /dev/null
+++ b/R/PqDriver.R
@@ -0,0 +1,33 @@
+#' Postgres driver
+#'
+#' This driver never needs to be unloaded and hence `dbUnload()` is a
+#' null-op.
+#'
+#' @export
+#' @useDynLib RPostgres, .registration = TRUE
+#' @importFrom Rcpp evalCpp
+#' @import methods DBI
+#' @examples
+#' library(DBI)
+#' RPostgres::Postgres()
+Postgres <- function() {
+ new("PqDriver")
+}
+
+#' PqDriver and methods.
+#'
+#' @export
+#' @keywords internal
+setClass("PqDriver", contains = "DBIDriver")
+
+#' @export
+#' @rdname PqDriver-class
+setMethod("dbUnloadDriver", "PqDriver", function(drv, ...) {
+ NULL
+})
+
+#' @rdname PqResult-class
+#' @export
+setMethod("dbIsValid", "PqDriver", function(dbObj, ...) {
+ TRUE
+})
diff --git a/R/PqResult.R b/R/PqResult.R
new file mode 100644
index 0000000..caa9062
--- /dev/null
+++ b/R/PqResult.R
@@ -0,0 +1,205 @@
+#' PostgreSQL results.
+#'
+#' @keywords internal
+#' @include PqConnection.R
+#' @export
+setClass("PqResult",
+ contains = "DBIResult",
+ slots = list(
+ conn = "PqConnection",
+ ptr = "externalptr",
+ sql = "character",
+ bigint = "character"
+ )
+)
+
+#' @rdname PqResult-class
+#' @export
+setMethod("dbGetStatement", "PqResult", function(res, ...) {
+ if (!dbIsValid(res)) {
+ stop("Invalid result set.", call. = FALSE)
+ }
+ res@sql
+})
+
+#' @rdname PqResult-class
+#' @export
+setMethod("dbIsValid", "PqResult", function(dbObj, ...) {
+ result_valid(dbObj@ptr)
+})
+
+#' @rdname PqResult-class
+#' @export
+setMethod("dbGetRowCount", "PqResult", function(res, ...) {
+ result_rows_fetched(res@ptr)
+})
+
+#' @rdname PqResult-class
+#' @export
+setMethod("dbGetRowsAffected", "PqResult", function(res, ...) {
+ result_rows_affected(res@ptr)
+})
+
+#' @rdname PqResult-class
+#' @export
+setMethod("dbColumnInfo", "PqResult", function(res, ...) {
+ result_column_info(res@ptr)
+})
+
+#' Execute a SQL statement on a database connection
+#'
+#' To retrieve results a chunk at a time, use `dbSendQuery()`,
+#' `dbFetch()`, then `dbClearResult()`. Alternatively, if you want all the
+#' results (and they'll fit in memory) use `dbGetQuery()` which sends,
+#' fetches and clears for you.
+#'
+#' @param conn A [PqConnection-class] created by [dbConnect()].
+#' @param statement An SQL string to execute
+#' @param params A list of query parameters to be substituted into
+#' a parameterised query. Query parameters are sent as strings, and the
+#' correct type is imputed by PostgreSQL. If this fails, you can manually
+#' cast the parameter with e.g. `"$1::bigint"`.
+#' @param ... Another arguments needed for compatibility with generic (
+#' currently ignored).
+#' @examples
+#' # For running the examples on systems without PostgreSQL connection:
+#' run <- postgresHasDefault()
+#'
+#' library(DBI)
+#' if (run) db <- dbConnect(RPostgres::Postgres())
+#' if (run) dbWriteTable(db, "usarrests", datasets::USArrests, temporary = TRUE)
+#'
+#' # Run query to get results as dataframe
+#' if (run) dbGetQuery(db, "SELECT * FROM usarrests LIMIT 3")
+#'
+#' # Send query to pull requests in batches
+#' if (run) res <- dbSendQuery(db, "SELECT * FROM usarrests")
+#' if (run) dbFetch(res, n = 2)
+#' if (run) dbFetch(res, n = 2)
+#' if (run) dbHasCompleted(res)
+#' if (run) dbClearResult(res)
+#'
+#' if (run) dbRemoveTable(db, "usarrests")
+#'
+#' if (run) dbDisconnect(db)
+#' @name postgres-query
+NULL
+
+#' @export
+#' @rdname postgres-query
+setMethod("dbSendQuery", c("PqConnection", "character"), function(conn, statement, params = NULL, ...) {
+ statement <- enc2utf8(statement)
+
+ rs <- new("PqResult",
+ conn = conn,
+ ptr = result_create(conn@ptr, statement),
+ sql = statement,
+ bigint = conn@bigint
+ )
+
+ if (!is.null(params)) {
+ dbBind(rs, params)
+ }
+
+ rs
+})
+
+#' @param res Code a [PqResult-class] produced by
+#' [DBI::dbSendQuery()].
+#' @param n Number of rows to return. If less than zero returns all rows.
+#' @inheritParams DBI::sqlRownamesToColumn
+#' @export
+#' @rdname postgres-query
+setMethod("dbFetch", "PqResult", function(res, n = -1, ..., row.names = FALSE) {
+ if (length(n) != 1) stopc("n must be scalar")
+ if (n < -1) stopc("n must be nonnegative or -1")
+ if (is.infinite(n)) n <- -1
+ if (trunc(n) != n) stopc("n must be a whole number")
+ ret <- sqlColumnToRownames(result_fetch(res@ptr, n = n), row.names)
+ convert_bigint(ret, res@bigint)
+})
+
+convert_bigint <- function(ret, bigint) {
+ if (bigint == "integer64") return(ret)
+ fun <- switch(bigint,
+ integer = as.integer,
+ numeric = as.numeric,
+ character = as.character
+ )
+ is_int64 <- which(vlapply(ret, inherits, "integer64"))
+ ret[is_int64] <- lapply(ret[is_int64], fun)
+ ret
+}
+
+#' @rdname postgres-query
+#' @export
+setMethod("dbBind", "PqResult", function(res, params, ...) {
+ if (!is.null(names(params))) {
+ stop("Named parameters not supported", call. = FALSE)
+ }
+ if (!is.list(params)) params <- as.list(params)
+ lengths <- unique(viapply(params, length))
+ if (length(lengths) > 1) {
+ stop("All parameters must have the same length.", call. = FALSE)
+ }
+
+ params <- factor_to_string(params, warn = TRUE)
+ params <- posixlt_to_posixct(params)
+ params <- difftime_to_hms(params)
+ params <- prepare_for_binding(params)
+ result_bind(res@ptr, params)
+ invisible(res)
+})
+
+factor_to_string <- function(value, warn = FALSE) {
+ is_factor <- vlapply(value, is.factor)
+ if (warn && any(is_factor)) {
+ warning("Factors converted to character", call. = FALSE)
+ }
+ value[is_factor] <- lapply(value[is_factor], as.character)
+ value
+}
+
+posixlt_to_posixct <- function(value) {
+ is_posixlt <- vlapply(value, inherits, "POSIXlt")
+ value[is_posixlt] <- lapply(value[is_posixlt], as.POSIXct)
+ value
+}
+
+difftime_to_hms <- function(value) {
+ is_difftime <- vlapply(value, inherits, "difftime")
+ value[is_difftime] <- lapply(value[is_difftime], hms::as.hms)
+ value
+}
+
+prepare_for_binding <- function(value) {
+ is_list <- vlapply(value, is.list)
+ value[!is_list] <- lapply(value[!is_list], as.character)
+ value[!is_list] <- lapply(value[!is_list], enc2utf8)
+ value[is_list] <- lapply(value[is_list], vcapply, function(x) {
+ if (is.null(x)) NA_character_
+ else if (is.raw(x)) {
+ paste(sprintf("\\%.3o", as.integer(x)), collapse = "")
+ } else {
+ stop("Lists must contain raw vectors or NULL", call. = FALSE)
+ }
+ })
+ value
+}
+
+#' @rdname postgres-query
+#' @export
+setMethod("dbHasCompleted", "PqResult", function(res, ...) {
+ result_has_completed(res@ptr)
+})
+
+#' @rdname postgres-query
+#' @export
+setMethod("dbClearResult", "PqResult", function(res, ...) {
+ if (!dbIsValid(res)) {
+ warningc("Expired, result set already closed")
+ return(invisible(TRUE))
+ }
+ result_release(res@ptr)
+ invisible(TRUE)
+})
diff --git a/R/RPostgres-pkg.R b/R/RPostgres-pkg.R
new file mode 100644
index 0000000..7724956
--- /dev/null
+++ b/R/RPostgres-pkg.R
@@ -0,0 +1,3 @@
+#' @importFrom hms hms
+#' @importFrom bit64 integer64
+"_PACKAGE"
diff --git a/R/RcppExports.R b/R/RcppExports.R
new file mode 100644
index 0000000..53c1c60
--- /dev/null
+++ b/R/RcppExports.R
@@ -0,0 +1,91 @@
+# Generated by using Rcpp::compileAttributes() -> do not edit by hand
+# Generator token: 10BE3573-1514-4C36-9D1C-5A225CD40393
+
+connection_create <- function(keys, values) {
+ .Call(`_RPostgres_connection_create`, keys, values)
+}
+
+connection_valid <- function(con_) {
+ .Call(`_RPostgres_connection_valid`, con_)
+}
+
+connection_release <- function(con_) {
+ invisible(.Call(`_RPostgres_connection_release`, con_))
+}
+
+connection_info <- function(con) {
+ .Call(`_RPostgres_connection_info`, con)
+}
+
+connection_quote_string <- function(con, xs) {
+ .Call(`_RPostgres_connection_quote_string`, con, xs)
+}
+
+connection_quote_identifier <- function(con, xs) {
+ .Call(`_RPostgres_connection_quote_identifier`, con, xs)
+}
+
+connection_is_transacting <- function(con) {
+ .Call(`_RPostgres_connection_is_transacting`, con)
+}
+
+connection_set_transacting <- function(con, transacting) {
+ invisible(.Call(`_RPostgres_connection_set_transacting`, con, transacting))
+}
+
+connection_copy_data <- function(con, sql, df) {
+ invisible(.Call(`_RPostgres_connection_copy_data`, con, sql, df))
+}
+
+encode_vector <- function(x) {
+ .Call(`_RPostgres_encode_vector`, x)
+}
+
+encode_data_frame <- function(x) {
+ .Call(`_RPostgres_encode_data_frame`, x)
+}
+
+encrypt_password <- function(password, user) {
+ .Call(`_RPostgres_encrypt_password`, password, user)
+}
+
+init_logging <- function(log_level) {
+ invisible(.Call(`_RPostgres_init_logging`, log_level))
+}
+
+result_create <- function(con, sql, is_statement = FALSE) {
+ .Call(`_RPostgres_result_create`, con, sql, is_statement)
+}
+
+result_release <- function(res) {
+ invisible(.Call(`_RPostgres_result_release`, res))
+}
+
+result_valid <- function(res_) {
+ .Call(`_RPostgres_result_valid`, res_)
+}
+
+result_fetch <- function(res, n) {
+ .Call(`_RPostgres_result_fetch`, res, n)
+}
+
+result_bind <- function(res, params) {
+ invisible(.Call(`_RPostgres_result_bind`, res, params))
+}
+
+result_has_completed <- function(res) {
+ .Call(`_RPostgres_result_has_completed`, res)
+}
+
+result_rows_fetched <- function(res) {
+ .Call(`_RPostgres_result_rows_fetched`, res)
+}
+
+result_rows_affected <- function(res) {
+ .Call(`_RPostgres_result_rows_affected`, res)
+}
+
+result_column_info <- function(res) {
+ .Call(`_RPostgres_result_column_info`, res)
+}
+
diff --git a/R/default.R b/R/default.R
new file mode 100644
index 0000000..9ea7ced
--- /dev/null
+++ b/R/default.R
@@ -0,0 +1,46 @@
+#' Check if default database is available.
+#'
+#' RPostgres examples and tests connect to a default database via
+#' `dbConnect(`[RPostgres::Postgres()]`)`. This function checks if that
+#' database is available, and if not, displays an informative message.
+#'
+#' @param ... Additional arguments passed on to [dbConnect()]
+#' @export
+#' @examples
+#' if (postgresHasDefault()) {
+#' db <- postgresDefault()
+#' dbListTables(db)
+#' dbDisconnect(db)
+#' }
+postgresHasDefault <- function(...) {
+ tryCatch({
+ con <- connect_default(...)
+ dbDisconnect(con)
+ TRUE
+ }, error = function(...) {
+ message(
+ "Could not initialise default postgres database. If postgres is running\n",
+ "check that the environment variables PGHOST, PGPORT, \n",
+ "PGUSER, PGPASSWORD, and PGDATABASE, are defined and\n",
+ "point to your database."
+ )
+ FALSE
+ })
+}
+
+#' `postgresDefault()` works similarly but returns a connection on success and
+#' throws a testthat skip condition on failure, making it suitable for use in
+#' tests.
+#' @export
+#' @rdname postgresHasDefault
+postgresDefault <- function(...) {
+ tryCatch({
+ connect_default(...)
+ }, error = function(...) {
+ testthat::skip("Test database not available")
+ })
+}
+
+connect_default <- function(...) {
+ dbConnect(Postgres(), ...)
+}
diff --git a/R/quote.R b/R/quote.R
new file mode 100644
index 0000000..06d7688
--- /dev/null
+++ b/R/quote.R
@@ -0,0 +1,140 @@
+#' @include PqConnection.R
+NULL
+
+#' Quote postgres strings and identifiers.
+#'
+#' @param conn A [PqConnection-class] created by `dbConnect()`
+#' @param x A character to escaped
+#' @param ... Other arguments needed for compatibility with generic
+#' @examples
+#' # For running the examples on systems without PostgreSQL connection:
+#' run <- postgresHasDefault()
+#'
+#' library(DBI)
+#' if (run) con <- dbConnect(RPostgres::Postgres())
+#'
+#' x <- c("a", "b c", "d'e", "\\f")
+#' if (run) dbQuoteString(con, x)
+#' if (run) dbQuoteIdentifier(con, x)
+#' if (run) dbDisconnect(con)
+#' @name quote
+NULL
+
+#' @export
+#' @rdname quote
+setMethod("dbQuoteString", c("PqConnection", "character"), function(conn, x, ...) {
+ if (length(x) == 0) return(SQL(character()))
+ res <- SQL(connection_quote_string(conn@ptr, enc2utf8(x)))
+ res
+})
+
+#' @export
+#' @rdname quote
+setMethod("dbQuoteString", c("PqConnection", "SQL"), function(conn, x, ...) {
+ x
+})
+
+#' @export
+#' @rdname quote
+setMethod("dbQuoteIdentifier", c("PqConnection", "character"), function(conn, x, ...) {
+ if (anyNA(x)) {
+ stop("Cannot pass NA to dbQuoteIdentifier()", call. = FALSE)
+ }
+ SQL(connection_quote_identifier(conn@ptr, x))
+})
+
+#' @export
+#' @rdname quote
+setMethod("dbQuoteIdentifier", c("PqConnection", "SQL"), function(conn, x, ...) {
+ x
+})
+
+# locally for now, requires DBI > 0.7
+#' @rdname quote
+setGeneric("dbQuoteLiteral",
+ def = function(conn, x, ...) standardGeneric("dbQuoteLiteral")
+)
+
+#' @export
+#' @rdname quote
+setMethod("dbQuoteLiteral", c("PqConnection", "logical"), function(conn, x, ...) {
+ x <- as.character(x)
+ x[is.na(x)] <- "NULL"
+ SQL(x)
+})
+
+#' @export
+#' @rdname quote
+setMethod("dbQuoteLiteral", c("PqConnection", "integer"), function(conn, x, ...) {
+ ret <- paste0(as.character(x), "::int4")
+ ret[is.na(x)] <- "NULL"
+ SQL(ret)
+})
+
+#' @export
+#' @rdname quote
+setMethod("dbQuoteLiteral", c("PqConnection", "numeric"), function(conn, x, ...) {
+ ret <- paste0(as.character(x), "::float8")
+ ret[is.na(x)] <- "NULL"
+ SQL(ret)
+})
+
+#' @export
+#' @rdname quote
+setMethod("dbQuoteLiteral", c("PqConnection", "factor"), function(conn, x, ...) {
+ dbQuoteLiteral(conn, as.character(x))
+})
+
+#' @export
+#' @rdname quote
+setMethod("dbQuoteLiteral", c("PqConnection", "Date"), function(conn, x, ...) {
+ ret <- paste0("'", as.character(x), "'::date")
+ ret[is.na(x)] <- "NULL"
+ SQL(ret)
+})
+
+#' @export
+#' @rdname quote
+setMethod("dbQuoteLiteral", c("PqConnection", "POSIXt"), function(conn, x, ...) {
+ ret <- paste0("'", as.character(x), "'::timestamp")
+ ret[is.na(x)] <- "NULL"
+ SQL(ret)
+})
+
+#' @export
+#' @rdname quote
+setMethod("dbQuoteLiteral", c("PqConnection", "difftime"), function(conn, x, ...) {
+ ret <- paste0(as.character(x), "::time")
+ ret[is.na(x)] <- "NULL"
+ SQL(ret)
+})
+
+#' @export
+#' @rdname quote
+setMethod("dbQuoteLiteral", c("PqConnection", "list"), function(conn, x, ...) {
+ quote_blob(x)
+})
+
+# Workaround, remove when blob > 1.1.0 is on CRAN
+setOldClass("blob")
+
+#' @export
+#' @rdname quote
+#' @importFrom blob blob
+setMethod("dbQuoteLiteral", c("PqConnection", "blob"), function(conn, x, ...) {
+ quote_blob(x)
+})
+
+quote_blob <- function(x) {
+ blob_data <- vcapply(
+ x,
+ function(x) {
+ if (is.null(x)) "NULL"
+ else if (is.raw(x)) paste0("E'\\\\x", paste(format(x), collapse = ""), "'")
+ else {
+ stop("Lists must contain raw vectors or NULL", call. = FALSE)
+ }
+ }
+ )
+ SQL(blob_data)
+}
diff --git a/R/tables.R b/R/tables.R
new file mode 100644
index 0000000..f6cd8ad
--- /dev/null
+++ b/R/tables.R
@@ -0,0 +1,244 @@
+#' Convenience functions for reading/writing DBMS tables
+#'
+#' @param conn a [PqConnection-class] object, produced by
+#' [DBI::dbConnect()]
+#' @param name a character string specifying a table name. Names will be
+#' automatically quoted so you can use any sequence of characters, not
+#' just any valid bare table name.
+#' @param value A data.frame to write to the database.
+#' @inheritParams DBI::sqlCreateTable
+#' @param overwrite a logical specifying whether to overwrite an existing table
+#' or not. Its default is `FALSE`.
+#' @param append a logical specifying whether to append to an existing table
+#' in the DBMS. Its default is `FALSE`.
+#' @param field.types character vector of named SQL field types where
+#' the names are the names of new table's columns. If missing, types inferred
+#' with [DBI::dbDataType()]).
+#' @param copy If `TRUE`, serializes the data frame to a single string
+#' and uses `COPY name FROM stdin`. This is fast, but not supported by
+#' all postgres servers (e.g. Amazon's redshift). If `FALSE`, generates
+#' a single SQL string. This is slower, but always supported.
+#'
+#' RPostgres does not use parameterised queries to insert rows because
+#' benchmarks revealed that this was considerably slower than using a single
+#' SQL string.
+#' @examples
+#' # For running the examples on systems without PostgreSQL connection:
+#' run <- postgresHasDefault()
+#'
+#' library(DBI)
+#' if (run) con <- dbConnect(RPostgres::Postgres())
+#' if (run) dbListTables(con)
+#' if (run) dbWriteTable(con, "mtcars", mtcars, temporary = TRUE)
+#' if (run) dbReadTable(con, "mtcars")
+#'
+#' if (run) dbListTables(con)
+#' if (run) dbExistsTable(con, "mtcars")
+#'
+#' # A zero row data frame just creates a table definition.
+#' if (run) dbWriteTable(con, "mtcars2", mtcars[0, ], temporary = TRUE)
+#' if (run) dbReadTable(con, "mtcars2")
+#'
+#' if (run) dbDisconnect(con)
+#' @name postgres-tables
+NULL
+
+#' @export
+#' @rdname postgres-tables
+setMethod("dbWriteTable", c("PqConnection", "character", "data.frame"),
+ function(conn, name, value, ..., row.names = FALSE, overwrite = FALSE, append = FALSE,
+ field.types = NULL, temporary = FALSE, copy = TRUE) {
+
+ if (is.null(row.names)) row.names <- FALSE
+ if ((!is.logical(row.names) && !is.character(row.names)) || length(row.names) != 1L) {
+ stopc("`row.names` must be a logical scalar or a string")
+ }
+ if (!is.logical(overwrite) || length(overwrite) != 1L || is.na(overwrite)) {
+ stopc("`overwrite` must be a logical scalar")
+ }
+ if (!is.logical(append) || length(append) != 1L || is.na(append)) {
+ stopc("`append` must be a logical scalar")
+ }
+ if (!is.logical(temporary) || length(temporary) != 1L) {
+ stopc("`temporary` must be a logical scalar")
+ }
+ if (overwrite && append) {
+ stopc("overwrite and append cannot both be TRUE")
+ }
+ if (append && !is.null(field.types)) {
+ stopc("Cannot specify field.types with append = TRUE")
+ }
+
+ found <- dbExistsTable(conn, name)
+ if (found && !overwrite && !append) {
+ stop("Table ", name, " exists in database, and both overwrite and",
+ " append are FALSE", call. = FALSE)
+ }
+ if (found && overwrite) {
+ dbRemoveTable(conn, name)
+ }
+
+ if (!found || overwrite) {
+ if (!is.null(field.types)) {
+ if (is.null(names(field.types)))
+ types <- structure(field.types, .Names = colnames(value))
+ else
+ types <- field.types
+ } else {
+ types <- value
+ }
+ sql <- sqlCreateTable(conn, name, if (is.null(field.types)) value else types,
+ row.names = row.names, temporary = temporary)
+ dbExecute(conn, sql)
+ }
+
+ if (nrow(value) > 0) {
+ value <- sqlData(conn, value, row.names = row.names, copy = copy)
+ if (!copy) {
+ sql <- sqlAppendTable(conn, name, value)
+ dbExecute(conn, sql)
+ } else {
+ fields <- dbQuoteIdentifier(conn, names(value))
+ sql <- paste0(
+ "COPY ", dbQuoteIdentifier(conn, name),
+ " (", paste(fields, collapse = ", "), ")",
+ " FROM STDIN"
+ )
+ connection_copy_data(conn@ptr, sql, value)
+ }
+ }
+
+ invisible(TRUE)
+ }
+)
+
+
+#' @export
+#' @inheritParams DBI::sqlRownamesToColumn
+#' @rdname postgres-tables
+setMethod("sqlData", "PqConnection", function(con, value, row.names = FALSE, copy = TRUE) {
+ if (is.null(row.names)) row.names <- FALSE
+ value <- sqlRownamesToColumn(value, row.names)
+
+ # C code takes care of atomic vectors, just need to coerce objects
+ is_object <- vlapply(value, is.object)
+ is_posix <- vlapply(value, function(c) inherits(c, "POSIXt"))
+ is_difftime <- vlapply(value, function(c) inherits(c, "difftime"))
+ is_blob <- vlapply(value, function(c) is.list(c))
+ is_whole_number <- vlapply(value, is_whole_number_vector)
+
+ withr::with_options(
+ list(digits.secs = 6),
+ value[is_posix] <- lapply(value[is_posix], function(col) format_keep_na(col, usetz = T))
+ )
+ value[is_difftime] <- lapply(value[is_difftime], function(col) format_keep_na(hms::as.hms(col)))
+ value[is_blob] <- lapply(
+ value[is_blob],
+ function(col) {
+ vapply(
+ col,
+ function(x) {
+ if (is.null(x)) NA_character_
+ else paste0("\\x", paste(format(x), collapse = ""))
+ },
+ character(1)
+ )
+ }
+ )
+
+ value[is_whole_number] <- lapply(
+ value[is_whole_number],
+ function(x) {
+ is_value <- which(!is.na(x))
+ x[is_value] <- format(x[is_value], scientific = FALSE, na.encode = FALSE)
+ x
+ }
+ )
+
+ value[is_object] <- lapply(value[is_object], as.character)
+ value
+})
+
+format_keep_na <- function(x, ...) {
+ is_na <- is.na(x)
+ ret <- format(x, ...)
+ ret[is_na] <- NA
+ ret
+}
+
+
+#' @export
+#' @param check.names If `TRUE`, the default, column names will be
+#' converted to valid R identifiers.
+#' @rdname postgres-tables
+setMethod("dbReadTable", c("PqConnection", "character"),
+ function(conn, name, ..., check.names = TRUE, row.names = FALSE) {
+
+ if (is.null(row.names)) row.names <- FALSE
+ if ((!is.logical(row.names) && !is.character(row.names)) || length(row.names) != 1L) {
+ stopc("`row.names` must be a logical scalar or a string")
+ }
+
+ if (!is.logical(check.names) || length(check.names) != 1L) {
+ stopc("`check.names` must be a logical scalar")
+ }
+
+ name <- dbQuoteIdentifier(conn, name)
+ out <- dbGetQuery(conn, paste("SELECT * FROM ", name), row.names = row.names)
+
+ if (check.names) {
+ names(out) <- make.names(names(out), unique = TRUE)
+ }
+
+ out
+ }
+)
+
+#' @export
+#' @rdname postgres-tables
+setMethod("dbListTables", "PqConnection", function(conn, ...) {
+ query <- paste0(
+ "SELECT table_name FROM INFORMATION_SCHEMA.tables ",
+ "WHERE ",
+ "(table_schema = ANY(current_schemas(false)) OR table_type = 'LOCAL TEMPORARY')"
+ )
+ dbGetQuery(conn, query)[[1]]
+})
+
+#' @export
+#' @rdname postgres-tables
+setMethod("dbExistsTable", c("PqConnection", "character"), function(conn, name, ...) {
+ stopifnot(length(name) == 1L)
+ name <- dbQuoteIdentifier(conn, name)
+ # Convert to plain string
+ name <- paste0(gsub('^"|"$', '', name))
+ name <- dbQuoteString(conn, name)
+
+ query <- paste0(
+ "SELECT COUNT(*) FROM INFORMATION_SCHEMA.tables WHERE table_name = ",
+ name, " ",
+ "AND ",
+ "(table_schema = ANY(current_schemas(false)) OR table_type = 'LOCAL TEMPORARY')"
+ )
+ dbGetQuery(conn, query)[[1]] >= 1
+})
+
+#' @export
+#' @rdname postgres-tables
+setMethod("dbRemoveTable", c("PqConnection", "character"),
+ function(conn, name, ...) {
+ name <- dbQuoteIdentifier(conn, name)
+ dbExecute(conn, paste("DROP TABLE ", name))
+ invisible(TRUE)
+ }
+)
+
+#' @export
+#' @rdname postgres-tables
+setMethod("dbListFields", c("PqConnection", "character"),
+ function(conn, name, ...) {
+ name <- dbQuoteString(conn, name)
+ dbGetQuery(conn, paste("SELECT column_name FROM information_schema.columns
+WHERE table_name=", name))$column_name
+ }
+)
diff --git a/R/transactions.R b/R/transactions.R
new file mode 100644
index 0000000..3ed7247
--- /dev/null
+++ b/R/transactions.R
@@ -0,0 +1,64 @@
+#' Transaction management.
+#'
+#' `dbBegin()` starts a transaction. `dbCommit()` and `dbRollback()`
+#' end the transaction by either committing or rolling back the changes.
+#'
+#' @param conn a [PqConnection-class] object, produced by
+#' [DBI::dbConnect()]
+#' @param ... Unused, for extensibility.
+#' @return A boolean, indicating success or failure.
+#' @examples
+#' # For running the examples on systems without PostgreSQL connection:
+#' run <- postgresHasDefault()
+#'
+#' library(DBI)
+#' if (run) con <- dbConnect(RPostgres::Postgres())
+#' if (run) dbWriteTable(con, "USarrests", datasets::USArrests, temporary = TRUE)
+#' if (run) dbGetQuery(con, 'SELECT count(*) from "USarrests"')
+#'
+#' if (run) dbBegin(con)
+#' if (run) dbExecute(con, 'DELETE from "USarrests" WHERE "Murder" > 1')
+#' if (run) dbGetQuery(con, 'SELECT count(*) from "USarrests"')
+#' if (run) dbRollback(con)
+#'
+#' # Rolling back changes leads to original count
+#' if (run) dbGetQuery(con, 'SELECT count(*) from "USarrests"')
+#'
+#' if (run) dbRemoveTable(con, "USarrests")
+#' if (run) dbDisconnect(con)
+#' @name postgres-transactions
+NULL
+
+#' @export
+#' @rdname postgres-transactions
+setMethod("dbBegin", "PqConnection", function(conn, ...) {
+ if (connection_is_transacting(conn@ptr)) {
+ stop("Nested transactions not supported.", call. = FALSE)
+ }
+
+ dbExecute(conn, "BEGIN")
+ connection_set_transacting(conn@ptr, TRUE)
+ invisible(TRUE)
+})
+
+#' @export
+#' @rdname postgres-transactions
+setMethod("dbCommit", "PqConnection", function(conn, ...) {
+ if (!connection_is_transacting(conn@ptr)) {
+ stop("Call dbBegin() to start a transaction.", call. = FALSE)
+ }
+ dbExecute(conn, "COMMIT")
+ connection_set_transacting(conn@ptr, FALSE)
+ invisible(TRUE)
+})
+
+#' @export
+#' @rdname postgres-transactions
+setMethod("dbRollback", "PqConnection", function(conn, ...) {
+ if (!connection_is_transacting(conn@ptr)) {
+ stop("Call dbBegin() to start a transaction.", call. = FALSE)
+ }
+ dbExecute(conn, "ROLLBACK")
+ connection_set_transacting(conn@ptr, FALSE)
+ invisible(TRUE)
+})
diff --git a/R/utils.R b/R/utils.R
new file mode 100644
index 0000000..a023b0a
--- /dev/null
+++ b/R/utils.R
@@ -0,0 +1,25 @@
+vlapply <- function(X, FUN, ..., USE.NAMES = TRUE) {
+ vapply(X = X, FUN = FUN, FUN.VALUE = logical(1L), ..., USE.NAMES = USE.NAMES)
+}
+
+viapply <- function(X, FUN, ..., USE.NAMES = TRUE) {
+ vapply(X = X, FUN = FUN, FUN.VALUE = integer(1L), ..., USE.NAMES = USE.NAMES)
+}
+
+vcapply <- function(X, FUN, ..., USE.NAMES = TRUE) {
+ vapply(X = X, FUN = FUN, FUN.VALUE = character(1L), ..., USE.NAMES = USE.NAMES)
+}
+
+stopc <- function(...) {
+ stop(..., call. = FALSE, domain = NA)
+}
+
+warningc <- function(...) {
+ warning(..., call. = FALSE, domain = NA)
+}
+
+is_whole_number_vector <- function(x) {
+ if (!is.numeric(x)) return(FALSE)
+ is_value <- which(!is.na(x))
+ isTRUE(all.equal(x[is_value] - trunc(x[is_value]), rep_len(0, length(is_value))))
+}
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..fb83776
--- /dev/null
+++ b/README.md
@@ -0,0 +1,73 @@
+# RPostgres
+
+[![Travis-CI Build Status](https://travis-ci.org/r-dbi/RPostgres.png?branch=master)](https://travis-ci.org/r-dbi/RPostgres) [![AppVeyor Build Status](https://ci.appveyor.com/api/projects/status/github/r-dbi/RPostgres?branch=master&svg=true)](https://ci.appveyor.com/project/r-dbi/RPostgres) [![codecov](https://codecov.io/gh/r-dbi/RPostgres/branch/master/graph/badge.svg)](https://codecov.io/gh/r-dbi/RPostgres)
+
+RPostgres is an DBI-compliant interface to the postgres database. It's a ground-up rewrite using C++ and Rcpp. Compared to PostgresSQL, it:
+
+* Has full support for parameterised queries via `dbSendQuery()`, and `dbBind()`.
+
+* Automatically cleans up open connections and result sets, ensuring that you
+ don't need to worry about leaking connections or memory.
+
+* Is a little faster, saving ~5 ms per query. (For refernce, it takes around 5ms
+ to retrive a 1000 x 25 result set from a local database, so this is
+ decent speed up for smaller queries.)
+
+* A simplified build process that relies on system libpq.
+
+## Installation
+
+RPostgres isn't available from CRAN yet, but you can get it from github with:
+
+```R
+# install.packages("remotes")
+remotes::install_github("r-dbi/RPostgres")
+```
+
+## Basic usage
+
+```R
+library(DBI)
+# Connect to the default postgres database
+con <- dbConnect(RPostgres::Postgres())
+
+dbListTables(con)
+dbWriteTable(con, "mtcars", mtcars)
+dbListTables(con)
+
+dbListFields(con, "mtcars")
+dbReadTable(con, "mtcars")
+
+# You can fetch all results:
+res <- dbSendQuery(con, "SELECT * FROM mtcars WHERE cyl = 4")
+dbFetch(res)
+dbClearResult(res)
+
+# Or a chunk at a time
+res <- dbSendQuery(con, "SELECT * FROM mtcars WHERE cyl = 4")
+while(!dbHasCompleted(res)){
+ chunk <- dbFetch(res, n = 5)
+ print(nrow(chunk))
+}
+# Clear the result
+dbClearResult(res)
+
+# Disconnect from the database
+dbDisconnect(con)
+```
+## Connecting to a specific Postgres instance
+
+```R
+library(DBI)
+# Connect to a specific postgres database i.e. Heroku
+con <- dbConnect(RPostgres::Postgres(),dbname = 'DATABASE_NAME',
+ host = 'HOST', # i.e. 'ec2-54-83-201-96.compute-1.amazonaws.com'
+ port = 5432, # or any other port specified by your DBA
+ user = 'USERNAME',
+ password = 'PASSWORD')
+
+```
+
+## Design notes
+
+The original DBI design imagined that each package could instantiate X drivers, with each driver having Y connections and each connection having Z results. This turns out to be too general: a driver has no real state, for PostgreSQL each connection can only have one result set. In the RPostgres package there's only one class on the C side: a connection, which optionally contains a result set. On the R side, the driver class is just a dummy class with no contents (used only for dispatch), and both the connection and result objects point to the same external pointer.
diff --git a/configure b/configure
new file mode 100755
index 0000000..637bc10
--- /dev/null
+++ b/configure
@@ -0,0 +1,104 @@
+#!/bin/bash
+# Anticonf (tm) script by Jeroen Ooms & Murat Tasan (2017)
+# This script will prefer cflags (specifically includefile dirs) and lib dirs
+# in the following order of precedence:
+# (1) INCLUDE_DIR or LIB_DIR entered explicitly on the command line, e.g.
+# R CMD INSTALL --configure-vars='INCLUDE_DIR=/.../include LIB_DIR=/.../lib'
+# (2) Values found via 'pkg-config' for the libpq package.
+# (3) Values found via 'pg_config' given a PostgreSQL installation.
+
+# Library settings
+PKG_CONFIG_NAME="libpq"
+PKG_DEB_NAME="libpq-dev"
+PKG_RPM_NAME="postgresql-devel"
+PKG_AMZ_RPM_NAMES="postgreql8-devel, psstgresql92-devel, postgresql93-devel, or postgresql94-devel"
+PKG_CSW_NAME="postgresql_dev"
+PKG_BREW_NAME="libpq"
+PKG_TEST_HEADER=""
+PKG_LIBS="-lpq"
+PKG_LIBS_STATIC="-lpq -lssl -lcrypto -lldap"
+
+# pkg-config values (if available)
+if [ $(command -v pkg-config) ]; then
+ PKGCONFIG_CFLAGS=$(pkg-config --cflags --silence-errors ${PKG_CONFIG_NAME})
+ PKGCONFIG_LIBS=$(pkg-config --libs --silence-errors ${PKG_CONFIG_NAME})
+
+ # MacOS seems to ship a broken libpq.pc file
+ if [[ "$PKGCONFIG_CFLAGS" == *"Internal.sdk"* ]]; then
+ unset PKGCONFIG_CFLAGS
+ unset PKGCONFIG_LIBS
+ fi
+fi
+
+# pg_config values (if available)
+if [ $(command -v pg_config) ]; then
+ PG_INC_DIR=$(pg_config --includedir)
+ PG_LIB_DIR=$(pg_config --libdir)
+fi
+
+# Note that cflags may be empty in case of success
+if [ "$INCLUDE_DIR" ] || [ "$LIB_DIR" ]; then
+ echo "Found INCLUDE_DIR and/or LIB_DIR!"
+ PKG_CFLAGS="-I$INCLUDE_DIR $PKG_CFLAGS"
+ PKG_LIBS="-L$LIB_DIR $PKG_LIBS"
+elif [ "$PKGCONFIG_CFLAGS" ] || [ "$PKGCONFIG_LIBS" ]; then
+ echo "Using pkg-config cflags and libs!"
+ PKG_CFLAGS=${PKGCONFIG_CFLAGS}
+ PKG_LIBS=${PKGCONFIG_LIBS}
+elif [ "$PG_INC_DIR" ] || [ "$PG_LIB_DIR" ]; then
+ echo "Using pg_config includedir and libdir!"
+ PKG_CFLAGS="-I${PG_INC_DIR}"
+ PKG_LIBS="-L${PG_LIB_DIR} ${PKG_LIBS}"
+elif [[ "$OSTYPE" == "darwin"* ]]; then
+ if [ $(command -v brew) ]; then
+ BREWDIR=$(brew --prefix)
+ else
+ curl -sfL "https://jeroen.github.io/autobrew/$PKG_BREW_NAME" > autobrew
+ source autobrew
+ fi
+ PKG_CFLAGS="-I$BREWDIR/opt/$PKG_BREW_NAME/include"
+ PKG_LIBS="-L$BREWDIR/opt/$PKG_BREW_NAME/lib $PKG_LIBS"
+fi
+
+# For debugging
+echo "Using PKG_CFLAGS=$PKG_CFLAGS"
+echo "Using PKG_LIBS=$PKG_LIBS"
+
+# Find compiler
+CC=$(${R_HOME}/bin/R CMD config CC)
+CFLAGS=$(${R_HOME}/bin/R CMD config CFLAGS)
+CPPFLAGS=$(${R_HOME}/bin/R CMD config CPPFLAGS)
+
+# Test configuration
+echo "#include $PKG_TEST_HEADER" | ${CC} ${CPPFLAGS} ${PKG_CFLAGS} ${CFLAGS} -E -xc - >/dev/null 2>&1 || R_CONFIG_ERROR=1;
+
+# Customize the error
+if [ $R_CONFIG_ERROR ]; then
+ echo "------------------------- ANTICONF ERROR ---------------------------"
+ echo "Configuration failed because $PKG_CONFIG_NAME was not found. Try installing:"
+ echo " * deb: $PKG_DEB_NAME (Debian, Ubuntu, etc)"
+ echo " * rpm: $PKG_RPM_NAME (Fedora, EPEL)"
+ echo " * rpm: $PKG_AMZ_RPM_NAMES (Amazon Linux)"
+ echo " * csw: $PKG_CSW_NAME (Solaris)"
+ echo " * brew: $PKG_BREW_NAME (OSX)"
+ echo "If $PKG_CONFIG_NAME is already installed, check that either:"
+ echo "(i) 'pkg-config' is in your PATH AND PKG_CONFIG_PATH contains"
+ echo " a $PKG_CONFIG_NAME.pc file; or"
+ echo "(ii) 'pg_config' is in your PATH."
+ echo "If neither can detect $PGK_CONFIG_NAME, you can set INCLUDE_DIR"
+ echo "and LIB_DIR manually via:"
+ echo "R CMD INSTALL --configure-vars='INCLUDE_DIR=... LIB_DIR=...'"
+ echo "--------------------------------------------------------------------"
+ exit 1;
+fi
+
+# Write to Makevars
+sed -e "s|@cflags@|$PKG_CFLAGS|" -e "s|@libs@|$PKG_LIBS|" src/Makevars.in > src/Makevars.new
+if [ ! -f src/Makevars.new ] || (which diff > /dev/null && ! diff -q src/Makevars src/Makevars.new); then
+ mv src/Makevars.new src/Makevars
+else
+ rm src/Makevars.new
+fi
+
+# Success
+exit 0
diff --git a/configure.win b/configure.win
new file mode 100644
index 0000000..e69de29
diff --git a/man/Postgres.Rd b/man/Postgres.Rd
new file mode 100644
index 0000000..f3b1b00
--- /dev/null
+++ b/man/Postgres.Rd
@@ -0,0 +1,16 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/PqDriver.R
+\name{Postgres}
+\alias{Postgres}
+\title{Postgres driver}
+\usage{
+Postgres()
+}
+\description{
+This driver never needs to be unloaded and hence \code{dbUnload()} is a
+null-op.
+}
+\examples{
+library(DBI)
+RPostgres::Postgres()
+}
diff --git a/man/PqConnection-class.Rd b/man/PqConnection-class.Rd
new file mode 100644
index 0000000..de2b6db
--- /dev/null
+++ b/man/PqConnection-class.Rd
@@ -0,0 +1,20 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/PqConnection.R
+\docType{class}
+\name{PqConnection-class}
+\alias{PqConnection-class}
+\alias{show,PqConnection-method}
+\alias{dbIsValid,PqConnection-method}
+\alias{dbGetInfo,PqConnection-method}
+\title{PqConnection and methods.}
+\usage{
+\S4method{show}{PqConnection}(object)
+
+\S4method{dbIsValid}{PqConnection}(dbObj, ...)
+
+\S4method{dbGetInfo}{PqConnection}(dbObj, ...)
+}
+\description{
+PqConnection and methods.
+}
+\keyword{internal}
diff --git a/man/PqDriver-class.Rd b/man/PqDriver-class.Rd
new file mode 100644
index 0000000..37cce55
--- /dev/null
+++ b/man/PqDriver-class.Rd
@@ -0,0 +1,14 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/PqDriver.R
+\docType{class}
+\name{PqDriver-class}
+\alias{PqDriver-class}
+\alias{dbUnloadDriver,PqDriver-method}
+\title{PqDriver and methods.}
+\usage{
+\S4method{dbUnloadDriver}{PqDriver}(drv, ...)
+}
+\description{
+PqDriver and methods.
+}
+\keyword{internal}
diff --git a/man/PqResult-class.Rd b/man/PqResult-class.Rd
new file mode 100644
index 0000000..32f57d1
--- /dev/null
+++ b/man/PqResult-class.Rd
@@ -0,0 +1,29 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/PqDriver.R, R/PqResult.R
+\docType{methods}
+\name{dbIsValid,PqDriver-method}
+\alias{dbIsValid,PqDriver-method}
+\alias{PqResult-class}
+\alias{dbGetStatement,PqResult-method}
+\alias{dbIsValid,PqResult-method}
+\alias{dbGetRowCount,PqResult-method}
+\alias{dbGetRowsAffected,PqResult-method}
+\alias{dbColumnInfo,PqResult-method}
+\title{PostgreSQL results.}
+\usage{
+\S4method{dbIsValid}{PqDriver}(dbObj, ...)
+
+\S4method{dbGetStatement}{PqResult}(res, ...)
+
+\S4method{dbIsValid}{PqResult}(dbObj, ...)
+
+\S4method{dbGetRowCount}{PqResult}(res, ...)
+
+\S4method{dbGetRowsAffected}{PqResult}(res, ...)
+
+\S4method{dbColumnInfo}{PqResult}(res, ...)
+}
+\description{
+PostgreSQL results.
+}
+\keyword{internal}
diff --git a/man/RPostgres-package.Rd b/man/RPostgres-package.Rd
new file mode 100644
index 0000000..4b45c8e
--- /dev/null
+++ b/man/RPostgres-package.Rd
@@ -0,0 +1,29 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/RPostgres-pkg.R
+\docType{package}
+\name{RPostgres-package}
+\alias{RPostgres}
+\alias{RPostgres-package}
+\title{RPostgres: 'Rcpp' Interface to 'PostgreSQL'}
+\description{
+Fully 'DBI'-compliant 'Rcpp'-backed interface to 'PostgreSQL' ,
+an open-source relational database.
+}
+\author{
+\strong{Maintainer}: Kirill Müller \email{krlmlr+r@mailbox.org}
+
+Authors:
+\itemize{
+ \item Hadley Wickham
+ \item Jeroen Ooms
+}
+
+Other contributors:
+\itemize{
+ \item RStudio [copyright holder]
+ \item R Consortium [copyright holder]
+ \item Tomoaki Nishiyama (Code for encoding vectors into strings derived from RPostgreSQL) [contributor]
+ \item Kungliga Tekniska Högskolan (Source code for timegm) [contributor]
+}
+
+}
diff --git a/man/dbConnect-PqDriver-method.Rd b/man/dbConnect-PqDriver-method.Rd
new file mode 100644
index 0000000..2591425
--- /dev/null
+++ b/man/dbConnect-PqDriver-method.Rd
@@ -0,0 +1,61 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/PqConnection.R
+\docType{methods}
+\name{dbDisconnect,PqConnection-method}
+\alias{dbDisconnect,PqConnection-method}
+\alias{dbConnect,PqDriver-method}
+\title{Connect to a PostgreSQL database.}
+\usage{
+\S4method{dbDisconnect}{PqConnection}(conn, ...)
+
+\S4method{dbConnect}{PqDriver}(drv, dbname = NULL, host = NULL,
+ port = NULL, password = NULL, user = NULL, service = NULL, ...,
+ bigint = c("integer64", "integer", "numeric", "character"))
+}
+\arguments{
+\item{conn}{Connection to disconnect.}
+
+\item{...}{Other name-value pairs that describe additional connection
+options as described at
+\url{http://www.postgresql.org/docs/9.6/static/libpq-connect.html#LIBPQ-PARAMKEYWORDS}}
+
+\item{drv}{\code{RPostgres::Postgres()}}
+
+\item{dbname}{Database name. If \code{NULL}, defaults to the user name.
+Note that this argument can only contain the database name, it will not
+be parsed as a connection string (internally, \code{expand_dbname} is set to
+\code{false} in the call to
+\href{https://www.postgresql.org/docs/9.6/static/libpq-connect.html}{PQconnectdbParams()}).}
+
+\item{host, port}{Host and port. If \code{NULL}, will be retrieved from
+\code{PGHOST} and \code{PGPORT} env vars.}
+
+\item{user, password}{User name and password. If \code{NULL}, will be
+retrieved from \code{PGUSER} and \code{PGPASSWORD} envvars, or from the
+appropriate line in \code{~/.pgpass}. See
+\url{http://www.postgresql.org/docs/9.6/static/libpq-pgpass.html} for
+more details.}
+
+\item{service}{Name of service to connect as. If \code{NULL}, will be
+ignored. Otherwise, connection parameters will be loaded from the pg_service.conf
+file and used. See \url{http://www.postgresql.org/docs/9.6/static/libpq-pgservice.html}
+for details on this file and syntax.}
+
+\item{bigint}{The R type that 64-bit integer types should be mapped to,
+default is \link[bit64:integer64]{bit64::integer64}, which allows the full range of 64 bit
+integers.}
+}
+\description{
+Manually disconnecting a connection is not necessary with RPostgres, but
+still recommended;
+if you delete the object containing the connection, it will be automatically
+disconnected during the next GC with a warning.
+}
+\examples{
+if (postgresHasDefault()) {
+library(DBI)
+# Pass more arguments as necessary to dbConnect()
+con <- dbConnect(RPostgres::Postgres())
+dbDisconnect(con)
+}
+}
diff --git a/man/dbDataType.Rd b/man/dbDataType.Rd
new file mode 100644
index 0000000..562e6cf
--- /dev/null
+++ b/man/dbDataType.Rd
@@ -0,0 +1,21 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/PqConnection.R
+\docType{methods}
+\name{dbDataType,PqConnection-method}
+\alias{dbDataType,PqConnection-method}
+\alias{dbDataType,PqDriver-method}
+\title{Determine database type for R vector.}
+\usage{
+\S4method{dbDataType}{PqConnection}(dbObj, obj, ...)
+
+\S4method{dbDataType}{PqDriver}(dbObj, obj, ...)
+}
+\arguments{
+\item{dbObj}{Postgres driver or connection.}
+
+\item{obj}{Object to convert}
+}
+\description{
+Determine database type for R vector.
+}
+\keyword{internal}
diff --git a/man/postgres-query.Rd b/man/postgres-query.Rd
new file mode 100644
index 0000000..b7d029a
--- /dev/null
+++ b/man/postgres-query.Rd
@@ -0,0 +1,80 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/PqResult.R
+\docType{methods}
+\name{postgres-query}
+\alias{postgres-query}
+\alias{dbSendQuery,PqConnection,character-method}
+\alias{dbFetch,PqResult-method}
+\alias{dbBind,PqResult-method}
+\alias{dbHasCompleted,PqResult-method}
+\alias{dbClearResult,PqResult-method}
+\title{Execute a SQL statement on a database connection}
+\usage{
+\S4method{dbSendQuery}{PqConnection,character}(conn, statement, params = NULL,
+ ...)
+
+\S4method{dbFetch}{PqResult}(res, n = -1, ..., row.names = FALSE)
+
+\S4method{dbBind}{PqResult}(res, params, ...)
+
+\S4method{dbHasCompleted}{PqResult}(res, ...)
+
+\S4method{dbClearResult}{PqResult}(res, ...)
+}
+\arguments{
+\item{conn}{A \linkS4class{PqConnection} created by \code{\link[=dbConnect]{dbConnect()}}.}
+
+\item{statement}{An SQL string to execute}
+
+\item{params}{A list of query parameters to be substituted into
+a parameterised query. Query parameters are sent as strings, and the
+correct type is imputed by PostgreSQL. If this fails, you can manually
+cast the parameter with e.g. \code{"$1::bigint"}.}
+
+\item{...}{Another arguments needed for compatibility with generic (
+currently ignored).}
+
+\item{res}{Code a \linkS4class{PqResult} produced by
+\code{\link[DBI:dbSendQuery]{DBI::dbSendQuery()}}.}
+
+\item{n}{Number of rows to return. If less than zero returns all rows.}
+
+\item{row.names}{Either \code{TRUE}, \code{FALSE}, \code{NA} or a string.
+
+If \code{TRUE}, always translate row names to a column called "row_names".
+If \code{FALSE}, never translate row names. If \code{NA}, translate
+rownames only if they're a character vector.
+
+A string is equivalent to \code{TRUE}, but allows you to override the
+default name.
+
+For backward compatibility, \code{NULL} is equivalent to \code{FALSE}.}
+}
+\description{
+To retrieve results a chunk at a time, use \code{dbSendQuery()},
+\code{dbFetch()}, then \code{dbClearResult()}. Alternatively, if you want all the
+results (and they'll fit in memory) use \code{dbGetQuery()} which sends,
+fetches and clears for you.
+}
+\examples{
+# For running the examples on systems without PostgreSQL connection:
+run <- postgresHasDefault()
+
+library(DBI)
+if (run) db <- dbConnect(RPostgres::Postgres())
+if (run) dbWriteTable(db, "usarrests", datasets::USArrests, temporary = TRUE)
+
+# Run query to get results as dataframe
+if (run) dbGetQuery(db, "SELECT * FROM usarrests LIMIT 3")
+
+# Send query to pull requests in batches
+if (run) res <- dbSendQuery(db, "SELECT * FROM usarrests")
+if (run) dbFetch(res, n = 2)
+if (run) dbFetch(res, n = 2)
+if (run) dbHasCompleted(res)
+if (run) dbClearResult(res)
+
+if (run) dbRemoveTable(db, "usarrests")
+
+if (run) dbDisconnect(db)
+}
diff --git a/man/postgres-tables.Rd b/man/postgres-tables.Rd
new file mode 100644
index 0000000..2cd1653
--- /dev/null
+++ b/man/postgres-tables.Rd
@@ -0,0 +1,102 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/tables.R
+\docType{methods}
+\name{postgres-tables}
+\alias{postgres-tables}
+\alias{dbWriteTable,PqConnection,character,data.frame-method}
+\alias{sqlData,PqConnection-method}
+\alias{dbReadTable,PqConnection,character-method}
+\alias{dbListTables,PqConnection-method}
+\alias{dbExistsTable,PqConnection,character-method}
+\alias{dbRemoveTable,PqConnection,character-method}
+\alias{dbListFields,PqConnection,character-method}
+\title{Convenience functions for reading/writing DBMS tables}
+\usage{
+\S4method{dbWriteTable}{PqConnection,character,data.frame}(conn, name, value,
+ ..., row.names = FALSE, overwrite = FALSE, append = FALSE,
+ field.types = NULL, temporary = FALSE, copy = TRUE)
+
+\S4method{sqlData}{PqConnection}(con, value, row.names = FALSE, copy = TRUE)
+
+\S4method{dbReadTable}{PqConnection,character}(conn, name, ...,
+ check.names = TRUE, row.names = FALSE)
+
+\S4method{dbListTables}{PqConnection}(conn, ...)
+
+\S4method{dbExistsTable}{PqConnection,character}(conn, name, ...)
+
+\S4method{dbRemoveTable}{PqConnection,character}(conn, name, ...)
+
+\S4method{dbListFields}{PqConnection,character}(conn, name, ...)
+}
+\arguments{
+\item{conn}{a \linkS4class{PqConnection} object, produced by
+\code{\link[DBI:dbConnect]{DBI::dbConnect()}}}
+
+\item{name}{a character string specifying a table name. Names will be
+automatically quoted so you can use any sequence of characters, not
+just any valid bare table name.}
+
+\item{value}{A data.frame to write to the database.}
+
+\item{...}{Other arguments used by individual methods.}
+
+\item{row.names}{Either \code{TRUE}, \code{FALSE}, \code{NA} or a string.
+
+If \code{TRUE}, always translate row names to a column called "row_names".
+If \code{FALSE}, never translate row names. If \code{NA}, translate
+rownames only if they're a character vector.
+
+A string is equivalent to \code{TRUE}, but allows you to override the
+default name.
+
+For backward compatibility, \code{NULL} is equivalent to \code{FALSE}.}
+
+\item{overwrite}{a logical specifying whether to overwrite an existing table
+or not. Its default is \code{FALSE}.}
+
+\item{append}{a logical specifying whether to append to an existing table
+in the DBMS. Its default is \code{FALSE}.}
+
+\item{field.types}{character vector of named SQL field types where
+the names are the names of new table's columns. If missing, types inferred
+with \code{\link[DBI:dbDataType]{DBI::dbDataType()}}).}
+
+\item{temporary}{If \code{TRUE}, will generate a temporary table statement.}
+
+\item{copy}{If \code{TRUE}, serializes the data frame to a single string
+and uses \code{COPY name FROM stdin}. This is fast, but not supported by
+all postgres servers (e.g. Amazon's redshift). If \code{FALSE}, generates
+a single SQL string. This is slower, but always supported.
+
+RPostgres does not use parameterised queries to insert rows because
+benchmarks revealed that this was considerably slower than using a single
+SQL string.}
+
+\item{con}{A database connection.}
+
+\item{check.names}{If \code{TRUE}, the default, column names will be
+converted to valid R identifiers.}
+}
+\description{
+Convenience functions for reading/writing DBMS tables
+}
+\examples{
+# For running the examples on systems without PostgreSQL connection:
+run <- postgresHasDefault()
+
+library(DBI)
+if (run) con <- dbConnect(RPostgres::Postgres())
+if (run) dbListTables(con)
+if (run) dbWriteTable(con, "mtcars", mtcars, temporary = TRUE)
+if (run) dbReadTable(con, "mtcars")
+
+if (run) dbListTables(con)
+if (run) dbExistsTable(con, "mtcars")
+
+# A zero row data frame just creates a table definition.
+if (run) dbWriteTable(con, "mtcars2", mtcars[0, ], temporary = TRUE)
+if (run) dbReadTable(con, "mtcars2")
+
+if (run) dbDisconnect(con)
+}
diff --git a/man/postgres-transactions.Rd b/man/postgres-transactions.Rd
new file mode 100644
index 0000000..e8dfdd6
--- /dev/null
+++ b/man/postgres-transactions.Rd
@@ -0,0 +1,49 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/transactions.R
+\docType{methods}
+\name{postgres-transactions}
+\alias{postgres-transactions}
+\alias{dbBegin,PqConnection-method}
+\alias{dbCommit,PqConnection-method}
+\alias{dbRollback,PqConnection-method}
+\title{Transaction management.}
+\usage{
+\S4method{dbBegin}{PqConnection}(conn, ...)
+
+\S4method{dbCommit}{PqConnection}(conn, ...)
+
+\S4method{dbRollback}{PqConnection}(conn, ...)
+}
+\arguments{
+\item{conn}{a \linkS4class{PqConnection} object, produced by
+\code{\link[DBI:dbConnect]{DBI::dbConnect()}}}
+
+\item{...}{Unused, for extensibility.}
+}
+\value{
+A boolean, indicating success or failure.
+}
+\description{
+\code{dbBegin()} starts a transaction. \code{dbCommit()} and \code{dbRollback()}
+end the transaction by either committing or rolling back the changes.
+}
+\examples{
+# For running the examples on systems without PostgreSQL connection:
+run <- postgresHasDefault()
+
+library(DBI)
+if (run) con <- dbConnect(RPostgres::Postgres())
+if (run) dbWriteTable(con, "USarrests", datasets::USArrests, temporary = TRUE)
+if (run) dbGetQuery(con, 'SELECT count(*) from "USarrests"')
+
+if (run) dbBegin(con)
+if (run) dbExecute(con, 'DELETE from "USarrests" WHERE "Murder" > 1')
+if (run) dbGetQuery(con, 'SELECT count(*) from "USarrests"')
+if (run) dbRollback(con)
+
+# Rolling back changes leads to original count
+if (run) dbGetQuery(con, 'SELECT count(*) from "USarrests"')
+
+if (run) dbRemoveTable(con, "USarrests")
+if (run) dbDisconnect(con)
+}
diff --git a/man/postgresHasDefault.Rd b/man/postgresHasDefault.Rd
new file mode 100644
index 0000000..07d50e7
--- /dev/null
+++ b/man/postgresHasDefault.Rd
@@ -0,0 +1,30 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/default.R
+\name{postgresHasDefault}
+\alias{postgresHasDefault}
+\alias{postgresDefault}
+\title{Check if default database is available.}
+\usage{
+postgresHasDefault(...)
+
+postgresDefault(...)
+}
+\arguments{
+\item{...}{Additional arguments passed on to \code{\link[=dbConnect]{dbConnect()}}}
+}
+\description{
+RPostgres examples and tests connect to a default database via
+\code{dbConnect(}\code{\link[RPostgres:Postgres]{RPostgres::Postgres()}}\code{)}. This function checks if that
+database is available, and if not, displays an informative message.
+
+\code{postgresDefault()} works similarly but returns a connection on success and
+throws a testthat skip condition on failure, making it suitable for use in
+tests.
+}
+\examples{
+if (postgresHasDefault()) {
+ db <- postgresDefault()
+ dbListTables(db)
+ dbDisconnect(db)
+}
+}
diff --git a/man/quote.Rd b/man/quote.Rd
new file mode 100644
index 0000000..51f0fb8
--- /dev/null
+++ b/man/quote.Rd
@@ -0,0 +1,71 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/quote.R
+\docType{methods}
+\name{quote}
+\alias{quote}
+\alias{dbQuoteString,PqConnection,character-method}
+\alias{dbQuoteString,PqConnection,SQL-method}
+\alias{dbQuoteIdentifier,PqConnection,character-method}
+\alias{dbQuoteIdentifier,PqConnection,SQL-method}
+\alias{dbQuoteLiteral}
+\alias{dbQuoteLiteral,PqConnection,logical-method}
+\alias{dbQuoteLiteral,PqConnection,integer-method}
+\alias{dbQuoteLiteral,PqConnection,numeric-method}
+\alias{dbQuoteLiteral,PqConnection,factor-method}
+\alias{dbQuoteLiteral,PqConnection,Date-method}
+\alias{dbQuoteLiteral,PqConnection,POSIXt-method}
+\alias{dbQuoteLiteral,PqConnection,difftime-method}
+\alias{dbQuoteLiteral,PqConnection,list-method}
+\alias{dbQuoteLiteral,PqConnection,blob-method}
+\title{Quote postgres strings and identifiers.}
+\usage{
+\S4method{dbQuoteString}{PqConnection,character}(conn, x, ...)
+
+\S4method{dbQuoteString}{PqConnection,SQL}(conn, x, ...)
+
+\S4method{dbQuoteIdentifier}{PqConnection,character}(conn, x, ...)
+
+\S4method{dbQuoteIdentifier}{PqConnection,SQL}(conn, x, ...)
+
+dbQuoteLiteral(conn, x, ...)
+
+\S4method{dbQuoteLiteral}{PqConnection,logical}(conn, x, ...)
+
+\S4method{dbQuoteLiteral}{PqConnection,integer}(conn, x, ...)
+
+\S4method{dbQuoteLiteral}{PqConnection,numeric}(conn, x, ...)
+
+\S4method{dbQuoteLiteral}{PqConnection,factor}(conn, x, ...)
+
+\S4method{dbQuoteLiteral}{PqConnection,Date}(conn, x, ...)
+
+\S4method{dbQuoteLiteral}{PqConnection,POSIXt}(conn, x, ...)
+
+\S4method{dbQuoteLiteral}{PqConnection,difftime}(conn, x, ...)
+
+\S4method{dbQuoteLiteral}{PqConnection,list}(conn, x, ...)
+
+\S4method{dbQuoteLiteral}{PqConnection,blob}(conn, x, ...)
+}
+\arguments{
+\item{conn}{A \linkS4class{PqConnection} created by \code{dbConnect()}}
+
+\item{x}{A character to escaped}
+
+\item{...}{Other arguments needed for compatibility with generic}
+}
+\description{
+Quote postgres strings and identifiers.
+}
+\examples{
+# For running the examples on systems without PostgreSQL connection:
+run <- postgresHasDefault()
+
+library(DBI)
+if (run) con <- dbConnect(RPostgres::Postgres())
+
+x <- c("a", "b c", "d'e", "\\\\f")
+if (run) dbQuoteString(con, x)
+if (run) dbQuoteIdentifier(con, x)
+if (run) dbDisconnect(con)
+}
diff --git a/src/DbColumn.cpp b/src/DbColumn.cpp
new file mode 100644
index 0000000..aa9f9de
--- /dev/null
+++ b/src/DbColumn.cpp
@@ -0,0 +1,116 @@
+#include "pch.h"
+#include "DbColumn.h"
+#include "DbColumnDataSource.h"
+#include "DbColumnStorage.h"
+
+
+DbColumn::DbColumn(DATA_TYPE dt, const int n_max_, DbColumnDataSourceFactory* factory, const int j)
+ : source(factory->create(j)),
+ n(0)
+{
+ if (dt == DT_BOOL)
+ dt = DT_UNKNOWN;
+ storage.push_back(new DbColumnStorage(dt, 0, n_max_, *source));
+}
+
+DbColumn::~DbColumn() {
+}
+
+void DbColumn::set_col_value() {
+ DbColumnStorage* last = get_last_storage();
+ DATA_TYPE dt = last->get_item_data_type();
+ data_types_seen.insert(dt);
+
+ DbColumnStorage* next = last->append_col();
+ if (last != next) storage.push_back(next);
+}
+
+void DbColumn::finalize(const int n_) {
+ n = n_;
+}
+
+void DbColumn::warn_type_conflicts(const String& name) const {
+ std::set my_data_types_seen = data_types_seen;
+ DATA_TYPE dt = get_last_storage()->get_data_type();
+
+ switch (dt) {
+ case DT_REAL:
+ my_data_types_seen.erase(DT_INT);
+ break;
+
+ case DT_INT64:
+ my_data_types_seen.erase(DT_INT);
+ break;
+
+ default:
+ break;
+ }
+
+ my_data_types_seen.erase(DT_UNKNOWN);
+ my_data_types_seen.erase(DT_BOOL);
+ my_data_types_seen.erase(dt);
+
+ if (my_data_types_seen.size() == 0) return;
+
+ String name_utf8 = name;
+ name_utf8.set_encoding(CE_UTF8);
+
+ std::stringstream ss;
+ ss << "Column `" << name_utf8.get_cstring() << "`: " <<
+ "mixed type, first seen values of type " << format_data_type(dt) << ", " <<
+ "coercing other values of type ";
+
+ bool first = true;
+ for (std::set::const_iterator it = my_data_types_seen.begin(); it != my_data_types_seen.end(); ++it) {
+ if (!first) ss << ", ";
+ else first = false;
+ ss << format_data_type(*it);
+ }
+
+ warning(ss.str());
+}
+
+DbColumn::operator SEXP() const {
+ DATA_TYPE dt = get_last_storage()->get_data_type();
+ SEXP ret = DbColumnStorage::allocate(n, dt);
+ int pos = 0;
+ for (size_t k = 0; k < storage.size(); ++k) {
+ const DbColumnStorage& current = storage[k];
+ pos += current.copy_to(ret, dt, pos);
+ }
+ return ret;
+}
+
+DATA_TYPE DbColumn::get_type() const {
+ const DATA_TYPE dt = get_last_storage()->get_data_type();
+ return dt;
+}
+
+const char* DbColumn::format_data_type(const DATA_TYPE dt) {
+ switch (dt) {
+ case DT_UNKNOWN:
+ return "unknown";
+ case DT_BOOL:
+ return "boolean";
+ case DT_INT:
+ return "integer";
+ case DT_INT64:
+ return "integer64";
+ case DT_REAL:
+ return "real";
+ case DT_STRING:
+ return "string";
+ case DT_BLOB:
+ return "blob";
+ default:
+ return "";
+ }
+}
+
+DbColumnStorage* DbColumn::get_last_storage() {
+ return &storage.end()[-1];
+}
+
+const DbColumnStorage* DbColumn::get_last_storage() const {
+ return &storage.end()[-1];
+}
diff --git a/src/DbColumn.h b/src/DbColumn.h
new file mode 100644
index 0000000..6d4da73
--- /dev/null
+++ b/src/DbColumn.h
@@ -0,0 +1,40 @@
+#ifndef DB_COLUMN_H
+#define DB_COLUMN_H
+
+
+#include "DbColumnDataType.h"
+#include "DbColumnDataSourceFactory.h"
+#include
+#include
+
+class DbColumnDataSourceFactory;
+class DbColumnDataSource;
+class DbColumnStorage;
+
+class DbColumn {
+private:
+ boost::shared_ptr source;
+ boost::ptr_vector storage;
+ int n;
+ std::set data_types_seen;
+
+public:
+ DbColumn(DATA_TYPE dt_, const int n_max_, DbColumnDataSourceFactory* factory, const int j);
+ ~DbColumn();
+
+public:
+ void set_col_value();
+ void finalize(const int n_);
+ void warn_type_conflicts(const String& name) const;
+
+ operator SEXP() const;
+ DATA_TYPE get_type() const;
+ static const char* format_data_type(const DATA_TYPE dt);
+
+private:
+ DbColumnStorage* get_last_storage();
+ const DbColumnStorage* get_last_storage() const;
+};
+
+
+#endif // DB_COLUMN_H
diff --git a/src/DbColumnDataSource.cpp b/src/DbColumnDataSource.cpp
new file mode 100644
index 0000000..e2f7f9c
--- /dev/null
+++ b/src/DbColumnDataSource.cpp
@@ -0,0 +1,14 @@
+#include "pch.h"
+#include "DbColumnDataSource.h"
+
+DbColumnDataSource::DbColumnDataSource(const int j_) :
+j(j_)
+{
+}
+
+DbColumnDataSource::~DbColumnDataSource() {
+}
+
+int DbColumnDataSource::get_j() const {
+ return j;
+}
diff --git a/src/DbColumnDataSource.h b/src/DbColumnDataSource.h
new file mode 100644
index 0000000..c0c8aac
--- /dev/null
+++ b/src/DbColumnDataSource.h
@@ -0,0 +1,36 @@
+#ifndef DB_COLUMNDATASOURCE_H
+#define DB_COLUMNDATASOURCE_H
+
+#include "DbColumnDataType.h"
+
+class DbColumnDataSource {
+ const int j;
+
+protected:
+ DbColumnDataSource(const int j);
+
+public:
+ virtual ~DbColumnDataSource();
+
+public:
+ virtual DATA_TYPE get_data_type() const = 0;
+ virtual DATA_TYPE get_decl_data_type() const = 0;
+
+ virtual bool is_null() const = 0;
+
+ virtual int fetch_bool() const = 0;
+ virtual int fetch_int() const = 0;
+ virtual int64_t fetch_int64() const = 0;
+ virtual double fetch_real() const = 0;
+ virtual SEXP fetch_string() const = 0;
+ virtual SEXP fetch_blob() const = 0;
+ virtual double fetch_date() const = 0;
+ virtual double fetch_datetime_local() const = 0;
+ virtual double fetch_datetime() const = 0;
+ virtual double fetch_time() const = 0;
+
+protected:
+ int get_j() const;
+};
+
+#endif //DB_COLUMNDATASOURCE_H
diff --git a/src/DbColumnDataSourceFactory.cpp b/src/DbColumnDataSourceFactory.cpp
new file mode 100644
index 0000000..66dc58c
--- /dev/null
+++ b/src/DbColumnDataSourceFactory.cpp
@@ -0,0 +1,8 @@
+#include "pch.h"
+#include "DbColumnDataSourceFactory.h"
+
+DbColumnDataSourceFactory::DbColumnDataSourceFactory() {
+}
+
+DbColumnDataSourceFactory::~DbColumnDataSourceFactory() {
+}
diff --git a/src/DbColumnDataSourceFactory.h b/src/DbColumnDataSourceFactory.h
new file mode 100644
index 0000000..04fcff1
--- /dev/null
+++ b/src/DbColumnDataSourceFactory.h
@@ -0,0 +1,17 @@
+#ifndef DB_COLUMNDATASOURCEFACTORY_H
+#define DB_COLUMNDATASOURCEFACTORY_H
+
+class DbColumnDataSource;
+
+class DbColumnDataSourceFactory {
+protected:
+ DbColumnDataSourceFactory();
+
+public:
+ virtual ~DbColumnDataSourceFactory();
+
+public:
+ virtual DbColumnDataSource* create(const int j) = 0;
+};
+
+#endif //DB_COLUMNDATASOURCEFACTORY_H
diff --git a/src/DbColumnDataType.h b/src/DbColumnDataType.h
new file mode 100644
index 0000000..7b7f3fc
--- /dev/null
+++ b/src/DbColumnDataType.h
@@ -0,0 +1,18 @@
+#ifndef RSQLITE_COLUMNDATATYPE_H
+#define RSQLITE_COLUMNDATATYPE_H
+
+enum DATA_TYPE {
+ DT_UNKNOWN,
+ DT_BOOL,
+ DT_INT,
+ DT_INT64,
+ DT_REAL,
+ DT_STRING,
+ DT_BLOB,
+ DT_DATE,
+ DT_DATETIME,
+ DT_DATETIMETZ,
+ DT_TIME
+};
+
+#endif // RSQLITE_COLUMNDATATYPE_H
diff --git a/src/DbColumnStorage.cpp b/src/DbColumnStorage.cpp
new file mode 100644
index 0000000..32c5914
--- /dev/null
+++ b/src/DbColumnStorage.cpp
@@ -0,0 +1,312 @@
+#include "pch.h"
+#include "DbColumnStorage.h"
+#include "DbColumnDataSource.h"
+#include "integer64.h"
+
+
+using namespace Rcpp;
+
+DbColumnStorage::DbColumnStorage(DATA_TYPE dt_, const R_xlen_t capacity_, const int n_max_,
+ const DbColumnDataSource& source_)
+ :
+ i(0),
+ dt(dt_),
+ n_max(n_max_),
+ source(source_)
+{
+ data = allocate(get_new_capacity(capacity_), dt);
+}
+
+DbColumnStorage::~DbColumnStorage() {
+}
+
+DbColumnStorage* DbColumnStorage::append_col() {
+ if (source.is_null()) return append_null();
+ return append_data();
+}
+
+DATA_TYPE DbColumnStorage::get_item_data_type() const {
+ return source.get_data_type();
+}
+
+DATA_TYPE DbColumnStorage::get_data_type() const {
+ if (dt == DT_UNKNOWN) return source.get_decl_data_type();
+ return dt;
+}
+
+SEXP DbColumnStorage::allocate(const R_xlen_t length, DATA_TYPE dt) {
+ SEXPTYPE type = sexptype_from_datatype(dt);
+ RObject class_ = class_from_datatype(dt);
+
+ SEXP ret = Rf_allocVector(type, length);
+ if (!Rf_isNull(class_)) Rf_setAttrib(ret, R_ClassSymbol, class_);
+ set_attribs_from_datatype(ret, dt);
+ return ret;
+}
+
+int DbColumnStorage::copy_to(SEXP x, DATA_TYPE dt, const int pos) const {
+ R_xlen_t n = Rf_xlength(x);
+ int src, tgt;
+ R_xlen_t capacity = get_capacity();
+ for (src = 0, tgt = pos; src < capacity && src < i && tgt < n; ++src, ++tgt) {
+ copy_value(x, dt, tgt, src);
+ }
+
+ for (; src < i && tgt < n; ++src, ++tgt) {
+ fill_default_value(x, dt, tgt);
+ }
+
+ return src;
+}
+
+R_xlen_t DbColumnStorage::get_capacity() const {
+ return Rf_xlength(data);
+}
+
+R_xlen_t DbColumnStorage::get_new_capacity(const R_xlen_t desired_capacity) const {
+ if (n_max < 0) {
+ const R_xlen_t MIN_DATA_CAPACITY = 100;
+ return std::max(desired_capacity, MIN_DATA_CAPACITY);
+ }
+ else {
+ return std::max(desired_capacity, R_xlen_t(1));
+ }
+}
+
+DbColumnStorage* DbColumnStorage::append_null() {
+ if (i < get_capacity()) fill_default_value();
+ ++i;
+ return this;
+}
+
+void DbColumnStorage::fill_default_value() {
+ fill_default_value(data, dt, i);
+}
+
+DbColumnStorage* DbColumnStorage::append_data() {
+ if (dt == DT_UNKNOWN) return append_data_to_new(dt);
+ if (i >= get_capacity()) return append_data_to_new(dt);
+ DATA_TYPE new_dt = source.get_data_type();
+ if (dt == DT_INT && new_dt == DT_INT64) return append_data_to_new(DT_INT64);
+ if (dt == DT_INT && new_dt == DT_REAL) return append_data_to_new(DT_REAL);
+
+ fetch_value();
+ ++i;
+ return this;
+}
+
+DbColumnStorage* DbColumnStorage::append_data_to_new(DATA_TYPE new_dt) {
+ if (new_dt == DT_UNKNOWN) new_dt = source.get_data_type();
+
+ R_xlen_t desired_capacity = (n_max < 0) ? (get_capacity() * 2) : (n_max - i);
+
+ DbColumnStorage* spillover = new DbColumnStorage(new_dt, desired_capacity, n_max, source);
+ return spillover->append_data();
+}
+
+void DbColumnStorage::fetch_value() {
+ switch (dt) {
+ case DT_BOOL:
+ LOGICAL(data)[i] = source.fetch_bool();
+ break;
+
+ case DT_INT:
+ INTEGER(data)[i] = source.fetch_int();
+ break;
+
+ case DT_INT64:
+ INTEGER64(data)[i] = source.fetch_int64();
+ break;
+
+ case DT_REAL:
+ REAL(data)[i] = source.fetch_real();
+ break;
+
+ case DT_STRING:
+ SET_STRING_ELT(data, i, source.fetch_string());
+ break;
+
+ case DT_BLOB:
+ SET_VECTOR_ELT(data, i, source.fetch_blob());
+ break;
+
+ case DT_DATE:
+ REAL(data)[i] = source.fetch_date();
+ break;
+
+ case DT_DATETIME:
+ REAL(data)[i] = source.fetch_datetime_local();
+ break;
+
+ case DT_DATETIMETZ:
+ REAL(data)[i] = source.fetch_datetime();
+ break;
+
+ case DT_TIME:
+ REAL(data)[i] = source.fetch_time();
+ break;
+
+ default:
+ stop("NYI");
+ }
+}
+
+SEXPTYPE DbColumnStorage::sexptype_from_datatype(DATA_TYPE dt) {
+ switch (dt) {
+ case DT_UNKNOWN:
+ return NILSXP;
+
+ case DT_BOOL:
+ return LGLSXP;
+
+ case DT_INT:
+ return INTSXP;
+
+ case DT_INT64:
+ return INT64SXP;
+
+ case DT_REAL:
+ case DT_DATE:
+ case DT_DATETIME:
+ case DT_DATETIMETZ:
+ case DT_TIME:
+ return REALSXP;
+
+ case DT_STRING:
+ return STRSXP;
+
+ case DT_BLOB:
+ return VECSXP;
+
+ default:
+ stop("Unknown type %d", dt);
+ }
+}
+
+Rcpp::RObject DbColumnStorage::class_from_datatype(DATA_TYPE dt) {
+ switch (dt) {
+ case DT_INT64:
+ return CharacterVector::create("integer64");
+
+ case DT_BLOB:
+ return CharacterVector::create("blob");
+
+ case DT_DATE:
+ return CharacterVector::create("Date");
+
+ case DT_DATETIME:
+ case DT_DATETIMETZ:
+ return CharacterVector::create("POSIXct", "POSIXt");
+
+ case DT_TIME:
+ return CharacterVector::create("hms", "difftime");
+
+ default:
+ return R_NilValue;
+ }
+}
+
+void DbColumnStorage::set_attribs_from_datatype(SEXP x, DATA_TYPE dt) {
+ switch (dt) {
+ case DT_TIME:
+ Rf_setAttrib(x, CharacterVector::create("units"), CharacterVector::create("secs"));
+ break;
+
+ default:
+ ;
+ }
+}
+
+void DbColumnStorage::fill_default_value(SEXP data, DATA_TYPE dt, R_xlen_t i) {
+ switch (dt) {
+ case DT_BOOL:
+ LOGICAL(data)[i] = NA_LOGICAL;
+ break;
+
+ case DT_INT:
+ INTEGER(data)[i] = NA_INTEGER;
+ break;
+
+ case DT_INT64:
+ INTEGER64(data)[i] = NA_INTEGER64;
+ break;
+
+ case DT_REAL:
+ case DT_DATE:
+ case DT_DATETIME:
+ case DT_DATETIMETZ:
+ case DT_TIME:
+ REAL(data)[i] = NA_REAL;
+ break;
+
+ case DT_STRING:
+ SET_STRING_ELT(data, i, NA_STRING);
+ break;
+
+ case DT_BLOB:
+ SET_VECTOR_ELT(data, i, R_NilValue);
+ break;
+
+ case DT_UNKNOWN:
+ stop("Not setting value for unknown data type");
+ }
+}
+
+void DbColumnStorage::copy_value(SEXP x, DATA_TYPE dt, const int tgt, const int src) const {
+ if (Rf_isNull(data)) {
+ fill_default_value(x, dt, tgt);
+ }
+ else {
+ switch (dt) {
+ case DT_BOOL:
+ LOGICAL(x)[tgt] = LOGICAL(data)[src];
+ break;
+
+ case DT_INT:
+ INTEGER(x)[tgt] = INTEGER(data)[src];
+ break;
+
+ case DT_INT64:
+ switch (TYPEOF(data)) {
+ case INTSXP:
+ INTEGER64(x)[tgt] = INTEGER(data)[src];
+ break;
+
+ case REALSXP:
+ INTEGER64(x)[tgt] = INTEGER64(data)[src];
+ break;
+ }
+ break;
+
+ case DT_REAL:
+ switch (TYPEOF(data)) {
+ case INTSXP:
+ REAL(x)[tgt] = INTEGER(data)[src];
+ break;
+
+ case REALSXP:
+ REAL(x)[tgt] = REAL(data)[src];
+ break;
+ }
+ break;
+
+ case DT_STRING:
+ SET_STRING_ELT(x, tgt, STRING_ELT(data, src));
+ break;
+
+ case DT_BLOB:
+ SET_VECTOR_ELT(x, tgt, VECTOR_ELT(data, src));
+ break;
+
+ case DT_DATE:
+ case DT_DATETIME:
+ case DT_DATETIMETZ:
+ case DT_TIME:
+ REAL(x)[tgt] = REAL(data)[src];
+ break;
+
+ default:
+ stop("NYI: default");
+ }
+ }
+}
diff --git a/src/DbColumnStorage.h b/src/DbColumnStorage.h
new file mode 100644
index 0000000..d99a12e
--- /dev/null
+++ b/src/DbColumnStorage.h
@@ -0,0 +1,54 @@
+#ifndef DB_COLUMNSTORAGE_H
+#define DB_COLUMNSTORAGE_H
+
+
+#include "DbColumnDataType.h"
+
+
+class DbColumnDataSource;
+
+class DbColumnStorage {
+ Rcpp::RObject data;
+ int i;
+ DATA_TYPE dt;
+ const int n_max;
+ const DbColumnDataSource& source;
+
+public:
+ DbColumnStorage(DATA_TYPE dt_, const R_xlen_t capacity_, const int n_max_, const DbColumnDataSource& source_);
+ ~DbColumnStorage();
+
+public:
+ DbColumnStorage* append_col();
+
+ DATA_TYPE get_item_data_type() const;
+ DATA_TYPE get_data_type() const;
+ static SEXP allocate(const R_xlen_t length, DATA_TYPE dt);
+ int copy_to(SEXP x, DATA_TYPE dt, const int pos) const;
+
+ // allocate()
+ static SEXPTYPE sexptype_from_datatype(DATA_TYPE type);
+
+private:
+ // append_col()
+ R_xlen_t get_capacity() const;
+ R_xlen_t get_new_capacity(const R_xlen_t desired_capacity) const;
+
+ DbColumnStorage* append_null();
+ void fill_default_value();
+
+ DbColumnStorage* append_data();
+ DbColumnStorage* append_data_to_new(DATA_TYPE new_dt);
+ void fetch_value();
+
+ // allocate()
+ static Rcpp::RObject class_from_datatype(DATA_TYPE dt);
+ static void set_attribs_from_datatype(SEXP x, DATA_TYPE dt);
+
+ // copy_to()
+ static void fill_default_value(SEXP data, DATA_TYPE dt, R_xlen_t i);
+ void copy_value(SEXP x, DATA_TYPE dt, const int tgt, const int src) const;
+};
+
+
+#endif // DB_COLUMNSTORAGE_H
diff --git a/src/DbConnection.cpp b/src/DbConnection.cpp
new file mode 100644
index 0000000..d50b772
--- /dev/null
+++ b/src/DbConnection.cpp
@@ -0,0 +1,222 @@
+#include "pch.h"
+#include "DbConnection.h"
+#include "encode.h"
+
+
+DbConnection::DbConnection(std::vector keys, std::vector values) :
+pCurrentResult_(NULL),
+transacting_(false)
+{
+ size_t n = keys.size();
+ std::vector c_keys(n + 1), c_values(n + 1);
+
+ for (size_t i = 0; i < n; ++i) {
+ c_keys[i] = keys[i].c_str();
+ c_values[i] = values[i].c_str();
+ }
+ c_keys[n] = NULL;
+ c_values[n] = NULL;
+
+ pConn_ = PQconnectdbParams(&c_keys[0], &c_values[0], false);
+
+ if (PQstatus(pConn_) != CONNECTION_OK) {
+ std::string err = PQerrorMessage(pConn_);
+ PQfinish(pConn_);
+ stop(err);
+ }
+
+ PQsetClientEncoding(pConn_, "UTF-8");
+}
+
+DbConnection::~DbConnection() {
+ disconnect();
+}
+
+void DbConnection::disconnect() {
+ try {
+ PQfinish(pConn_);
+ pConn_ = NULL;
+ } catch (...) {}
+}
+
+PGconn* DbConnection::conn() {
+ return pConn_;
+}
+
+void DbConnection::set_current_result(const DbResult* pResult) {
+ // Cancels previous query, if needed.
+ if (pResult == pCurrentResult_)
+ return;
+
+ if (pCurrentResult_ != NULL) {
+ if (pResult != NULL)
+ warning("Cancelling previous query");
+
+ cleanup_query();
+ }
+ pCurrentResult_ = pResult;
+}
+
+void DbConnection::cancel_query() {
+ check_connection();
+
+ // Cancel running query
+ PGcancel* cancel = PQgetCancel(pConn_);
+ if (cancel == NULL) {
+ warning("Failed to cancel running query");
+ return;
+ }
+
+ char errbuf[256];
+ if (!PQcancel(cancel, errbuf, sizeof(errbuf))) {
+ warning(errbuf);
+ }
+
+ PQfreeCancel(cancel);
+}
+
+void DbConnection::finish_query() const {
+ // Clear pending results
+ PGresult* result;
+ while ((result = PQgetResult(pConn_)) != NULL) {
+ PQclear(result);
+ }
+}
+
+bool DbConnection::is_current_result(const DbResult* pResult) {
+ return pCurrentResult_ == pResult;
+}
+
+bool DbConnection::has_query() {
+ return pCurrentResult_ != NULL;
+}
+
+void DbConnection::copy_data(std::string sql, List df) {
+ LOG_DEBUG << sql;
+
+ R_xlen_t p = df.size();
+ if (p == 0)
+ return;
+
+ PGresult* pInit = PQexec(pConn_, sql.c_str());
+ if (PQresultStatus(pInit) != PGRES_COPY_IN) {
+ PQclear(pInit);
+ conn_stop("Failed to initialise COPY");
+ }
+ PQclear(pInit);
+
+
+ std::string buffer;
+ int n = Rf_length(df[0]);
+ // Sending row at-a-time is faster, presumable because it avoids copies
+ // of buffer. Sending data asynchronously appears to be no faster.
+ for (int i = 0; i < n; ++i) {
+ buffer.clear();
+ encode_row_in_buffer(df, i, buffer);
+
+ if (PQputCopyData(pConn_, buffer.data(), static_cast(buffer.size())) != 1) {
+ conn_stop("Failed to put data");
+ }
+ }
+
+
+ if (PQputCopyEnd(pConn_, NULL) != 1) {
+ conn_stop("Failed to finish COPY");
+ }
+
+ PGresult* pComplete = PQgetResult(pConn_);
+ if (PQresultStatus(pComplete) != PGRES_COMMAND_OK) {
+ PQclear(pComplete);
+ conn_stop("COPY returned error");
+ }
+ PQclear(pComplete);
+}
+
+void DbConnection::check_connection() {
+ if (!pConn_) {
+ stop("Disconnected");
+ }
+
+ ConnStatusType status = PQstatus(pConn_);
+ if (status == CONNECTION_OK) return;
+
+ // Status was bad, so try resetting.
+ PQreset(pConn_);
+ status = PQstatus(pConn_);
+ if (status == CONNECTION_OK) return;
+
+ conn_stop("Lost connection to database");
+}
+
+List DbConnection::info() {
+ check_connection();
+
+ const char* dbnm = PQdb(pConn_);
+ const char* host = PQhost(pConn_);
+ const char* port = PQport(pConn_);
+ const char* user = PQuser(pConn_);
+ int pver = PQprotocolVersion(pConn_);
+ int sver = PQserverVersion(pConn_);
+ int pid = PQbackendPID(pConn_);
+ return
+ List::create(
+ _["dbname"] = dbnm == NULL ? "" : std::string(dbnm),
+ _["host"] = host == NULL ? "" : std::string(host),
+ _["port"] = port == NULL ? "" : std::string(port),
+ _["user"] = user == NULL ? "" : std::string(user),
+ _["protocol_version"] = pver,
+ _["server_version"] = sver,
+ _["pid"] = pid
+ );
+}
+
+SEXP DbConnection::quote_string(const String& x) {
+ // Returns a single CHRSXP
+ check_connection();
+
+ if (x == NA_STRING)
+ return get_null_string();
+
+ char* pq_escaped = PQescapeLiteral(pConn_, x.get_cstring(), static_cast(-1));
+ SEXP escaped = Rf_mkCharCE(pq_escaped, CE_UTF8);
+ PQfreemem(pq_escaped);
+
+ return escaped;
+}
+
+SEXP DbConnection::quote_identifier(const String& x) {
+ // Returns a single CHRSXP
+ check_connection();
+
+ char* pq_escaped = PQescapeIdentifier(pConn_, x.get_cstring(), static_cast(-1));
+ SEXP escaped = Rf_mkCharCE(pq_escaped, CE_UTF8);
+ PQfreemem(pq_escaped);
+
+ return escaped;
+}
+
+SEXP DbConnection::get_null_string() {
+ static RObject null = Rf_mkCharCE("NULL", CE_UTF8);
+ return null;
+}
+
+bool DbConnection::is_transacting() const {
+ return transacting_;
+}
+
+void DbConnection::set_transacting(bool transacting) {
+ transacting_ = transacting;
+}
+
+void DbConnection::conn_stop(const char* msg) {
+ conn_stop(conn(), msg);
+}
+
+void DbConnection::conn_stop(PGconn* conn, const char* msg) {
+ stop("%s: %s", msg, PQerrorMessage(conn));
+}
+
+void DbConnection::cleanup_query() {
+ cancel_query();
+ finish_query();
+}
diff --git a/src/DbConnection.h b/src/DbConnection.h
new file mode 100644
index 0000000..fc8f4ed
--- /dev/null
+++ b/src/DbConnection.h
@@ -0,0 +1,55 @@
+#ifndef __RPOSTGRES_PQ_CONNECTION__
+#define __RPOSTGRES_PQ_CONNECTION__
+
+#include
+#include
+
+class DbResult;
+
+// convenience typedef for shared_ptr to DbConnection
+class DbConnection;
+typedef boost::shared_ptr DbConnectionPtr;
+
+// DbConnection ----------------------------------------------------------------
+
+class DbConnection : boost::noncopyable {
+ PGconn* pConn_;
+ const DbResult* pCurrentResult_;
+ bool transacting_;
+
+public:
+ DbConnection(std::vector keys, std::vector values);
+ virtual ~DbConnection();
+
+public:
+ void disconnect();
+
+ PGconn* conn();
+
+ void set_current_result(const DbResult* pResult);
+ bool is_current_result(const DbResult* pResult);
+ bool has_query();
+
+ void copy_data(std::string sql, List df);
+
+ void check_connection();
+ List info();
+
+ SEXP quote_string(const String& x);
+ SEXP quote_identifier(const String& x);
+ static SEXP get_null_string();
+
+ bool is_transacting() const;
+ void set_transacting(bool transacting);
+
+ void conn_stop(const char* msg);
+ static void conn_stop(PGconn* conn, const char* msg);
+
+ void cleanup_query();
+ void finish_query() const;
+
+private:
+ void cancel_query();
+};
+
+#endif
diff --git a/src/DbDataFrame.cpp b/src/DbDataFrame.cpp
new file mode 100644
index 0000000..bb94e80
--- /dev/null
+++ b/src/DbDataFrame.cpp
@@ -0,0 +1,69 @@
+#include "pch.h"
+#include "DbDataFrame.h"
+#include "DbColumn.h"
+#include "DbColumnStorage.h"
+#include "DbColumnDataSource.h"
+#include "DbColumnDataSourceFactory.h"
+#include
+#include
+
+DbDataFrame::DbDataFrame(DbColumnDataSourceFactory* factory_, std::vector names_, const int n_max_,
+ const std::vector& types_)
+ : n_max(n_max_),
+ i(0),
+ names(names_)
+{
+ factory.reset(factory_);
+
+ data.reserve(types_.size());
+ for (size_t j = 0; j < types_.size(); ++j) {
+ DbColumn x(types_[j], n_max, factory.get(), (int)j);
+ data.push_back(x);
+ }
+}
+
+DbDataFrame::~DbDataFrame() {
+}
+
+void DbDataFrame::set_col_values() {
+ std::for_each(data.begin(), data.end(), boost::bind(&DbColumn::set_col_value, _1));
+}
+
+bool DbDataFrame::advance() {
+ ++i;
+
+ if (i % 1000 == 0)
+ checkUserInterrupt();
+
+ return (n_max < 0 || i < n_max);
+}
+
+List DbDataFrame::get_data() {
+ // Throws away new data types
+ std::vector types_;
+ return get_data(types_);
+}
+
+List DbDataFrame::get_data(std::vector& types_) {
+ // Trim back to what we actually used
+ finalize_cols();
+
+ types_.clear();
+ std::transform(data.begin(), data.end(), std::back_inserter(types_), std::mem_fun_ref(&DbColumn::get_type));
+
+ boost::for_each(data, names, boost::bind(&DbColumn::warn_type_conflicts, _1, _2));
+
+ List out(data.begin(), data.end());
+ out.attr("names") = names;
+ out.attr("class") = "data.frame";
+ out.attr("row.names") = IntegerVector::create(NA_INTEGER, -i);
+ return out;
+}
+
+size_t DbDataFrame::get_ncols() const {
+ return data.size();
+}
+
+void DbDataFrame::finalize_cols() {
+ std::for_each(data.begin(), data.end(), boost::bind(&DbColumn::finalize, _1, i));
+}
diff --git a/src/DbDataFrame.h b/src/DbDataFrame.h
new file mode 100644
index 0000000..c023fad
--- /dev/null
+++ b/src/DbDataFrame.h
@@ -0,0 +1,35 @@
+#ifndef DB_DATAFRAME_H
+#define DB_DATAFRAME_H
+
+#include
+#include
+#include "DbColumnDataType.h"
+
+class DbColumn;
+class DbColumnDataSourceFactory;
+
+class DbDataFrame {
+ boost::scoped_ptr factory;
+ const int n_max;
+ int i;
+ boost::container::stable_vector data;
+ std::vector names;
+
+public:
+ DbDataFrame(DbColumnDataSourceFactory* factory, std::vector names, const int n_max_, const std::vector& types);
+ virtual ~DbDataFrame();
+
+public:
+ void set_col_values();
+ bool advance();
+
+ List get_data();
+ List get_data(std::vector& types);
+ size_t get_ncols() const;
+
+private:
+ void finalize_cols();
+};
+
+
+#endif //DB_DATAFRAME_H
diff --git a/src/DbResult.cpp b/src/DbResult.cpp
new file mode 100644
index 0000000..5481da9
--- /dev/null
+++ b/src/DbResult.cpp
@@ -0,0 +1,63 @@
+#include "pch.h"
+#include "DbResult.h"
+#include "DbConnection.h"
+#include "PqResultImpl.h"
+
+
+DbResult::DbResult(const DbConnectionPtr& pConn, const std::string& sql) :
+pConn_(pConn)
+{
+ pConn->check_connection();
+ pConn->set_current_result(this);
+
+ try {
+ impl.reset(new PqResultImpl(this, pConn->conn(), sql));
+ }
+ catch (...) {
+ pConn->set_current_result(NULL);
+ throw;
+ }
+}
+
+DbResult::~DbResult() {
+ try {
+ if (active()) {
+ pConn_->set_current_result(NULL);
+ }
+ } catch (...) {}
+}
+
+void DbResult::bind(const List& params) {
+ return impl->bind(params);
+}
+
+bool DbResult::active() const {
+ return pConn_->is_current_result(this);
+}
+
+List DbResult::fetch(int n_max) {
+ if (!active())
+ stop("Inactive result set");
+
+ return impl->fetch(n_max);
+}
+
+int DbResult::n_rows_affected() {
+ return impl->n_rows_affected();
+}
+
+int DbResult::n_rows_fetched() {
+ return impl->n_rows_fetched();
+}
+
+bool DbResult::complete() {
+ return impl->complete();
+}
+
+List DbResult::get_column_info() {
+ return impl->get_column_info();
+}
+
+void DbResult::finish_query() {
+ pConn_->finish_query();
+}
diff --git a/src/DbResult.h b/src/DbResult.h
new file mode 100644
index 0000000..7390960
--- /dev/null
+++ b/src/DbResult.h
@@ -0,0 +1,42 @@
+#ifndef __RPOSTGRES_PQ_RESULT__
+#define __RPOSTGRES_PQ_RESULT__
+
+#include
+#include
+#include
+
+
+class DbConnection;
+typedef boost::shared_ptr DbConnectionPtr;
+
+// DbResult --------------------------------------------------------------------
+// There is no object analogous to DbResult in libpq: this provides a result set
+// like object for the R API. There is only ever one active result set (the
+// most recent) for each connection.
+
+class PqResultImpl;
+
+class DbResult : boost::noncopyable {
+ DbConnectionPtr pConn_;
+ boost::scoped_ptr impl;
+
+public:
+ DbResult(const DbConnectionPtr& pConn, const std::string& sql);
+ ~DbResult();
+
+public:
+ bool complete();
+ bool active() const;
+ int n_rows_fetched();
+ int n_rows_affected();
+
+ void bind(const List& params);
+ List fetch(int n_max = -1);
+
+ List get_column_info();
+
+public:
+ void finish_query();
+};
+
+#endif
diff --git a/src/Makevars.in b/src/Makevars.in
new file mode 100644
index 0000000..627ef02
--- /dev/null
+++ b/src/Makevars.in
@@ -0,0 +1,2 @@
+PKG_CPPFLAGS=@cflags@
+PKG_LIBS=@libs@
diff --git a/src/Makevars.win b/src/Makevars.win
new file mode 100644
index 0000000..9266481
--- /dev/null
+++ b/src/Makevars.win
@@ -0,0 +1,14 @@
+PKG_CPPFLAGS= -I../windows/libpq-9.5.2/include
+PKG_LIBS= -L../windows/libpq-9.5.2/lib${R_ARCH} \
+ -lpq -lssl -lcrypto -lwsock32 -lsecur32 -lws2_32 -lgdi32 -lcrypt32 -lwldap32 win32/timegm.o
+CXX_STD=CXX11
+
+$(SHLIB): win32/timegm.o
+
+$(OBJECTS): winlibs
+
+clean:
+ rm -f $(SHLIB) $(OBJECTS) win32/timegm.o
+
+winlibs:
+ "${R_HOME}/bin${R_ARCH_BIN}/Rscript.exe" "../tools/winlibs.R"
diff --git a/src/PqColumnDataSource.cpp b/src/PqColumnDataSource.cpp
new file mode 100644
index 0000000..76aa982
--- /dev/null
+++ b/src/PqColumnDataSource.cpp
@@ -0,0 +1,175 @@
+#include "pch.h"
+#include
+#include "PqColumnDataSource.h"
+#include "PqResultSource.h"
+
+#if defined(WIN32) || defined(_WIN32)
+#define timegm _mkgmtime
+#endif
+
+PqColumnDataSource::PqColumnDataSource(PqResultSource* result_source_, const DATA_TYPE dt_, const int j) :
+DbColumnDataSource(j),
+result_source(result_source_),
+dt(dt_)
+{
+}
+
+PqColumnDataSource::~PqColumnDataSource() {
+}
+
+DATA_TYPE PqColumnDataSource::get_data_type() const {
+ return dt;
+}
+
+DATA_TYPE PqColumnDataSource::get_decl_data_type() const {
+ return dt;
+}
+
+bool PqColumnDataSource::is_null() const {
+ LOG_VERBOSE;
+ return PQgetisnull(get_result(), 0, get_j()) != 0;
+}
+
+int PqColumnDataSource::fetch_bool() const {
+ LOG_VERBOSE;
+ return (strcmp(get_result_value(), "t") == 0);
+}
+
+
+int PqColumnDataSource::fetch_int() const {
+ LOG_VERBOSE;
+ return atoi(get_result_value());
+}
+
+int64_t PqColumnDataSource::fetch_int64() const {
+ LOG_VERBOSE;
+ return boost::lexical_cast(get_result_value());
+}
+
+double PqColumnDataSource::fetch_real() const {
+ LOG_VERBOSE;
+ return atof(get_result_value());
+}
+
+SEXP PqColumnDataSource::fetch_string() const {
+ LOG_VERBOSE;
+ return Rf_mkCharCE(get_result_value(), CE_UTF8);
+}
+
+SEXP PqColumnDataSource::fetch_blob() const {
+ LOG_VERBOSE;
+ const void* val = get_result_value();
+
+ size_t to_length = 0;
+ unsigned char* unescaped_blob = PQunescapeBytea(static_cast(val), &to_length);
+
+ SEXP bytes = Rf_allocVector(RAWSXP, static_cast(to_length));
+ memcpy(RAW(bytes), unescaped_blob, to_length);
+
+ PQfreemem(unescaped_blob);
+
+ return bytes;
+}
+
+double PqColumnDataSource::fetch_date() const {
+ LOG_VERBOSE;
+ const char* val = get_result_value();
+ struct tm date = tm();
+ date.tm_isdst = -1;
+ date.tm_year = *val - 0x30;
+ date.tm_year *= 10;
+ date.tm_year += (*(++val) - 0x30);
+ date.tm_year *= 10;
+ date.tm_year += (*(++val) - 0x30);
+ date.tm_year *= 10;
+ date.tm_year += (*(++val) - 0x30) - 1900;
+ val++;
+ date.tm_mon = 10 * (*(++val) - 0x30);
+ date.tm_mon += (*(++val) - 0x30) - 1;
+ val++;
+ date.tm_mday = (*(++val) - 0x30) * 10;
+ date.tm_mday += (*(++val) - 0x30);
+ return static_cast(timegm(&date)) / (24.0 * 60 * 60);
+}
+
+double PqColumnDataSource::fetch_datetime_local() const {
+ LOG_VERBOSE;
+ return convert_datetime(get_result_value(), true);
+}
+
+double PqColumnDataSource::fetch_datetime() const {
+ LOG_VERBOSE;
+ return convert_datetime(get_result_value(), false);
+}
+
+double PqColumnDataSource::fetch_time() const {
+ LOG_VERBOSE;
+ const char* val = get_result_value();
+ int hour = (*val - 0x30) * 10;
+ hour += (*(++val) - 0x30);
+ val++;
+ int min = (*(++val) - 0x30) * 10;
+ min += (*(++val) - 0x30);
+ val++;
+ double sec = strtod(++val, NULL);
+ return static_cast(hour * 3600 + min * 60) + sec;
+}
+
+double PqColumnDataSource::convert_datetime(const char* val, bool use_local) {
+ char* end;
+ struct tm date;
+ date.tm_isdst = -1;
+ date.tm_year = *val - 0x30;
+ date.tm_year *= 10;
+ date.tm_year += (*(++val) - 0x30);
+ date.tm_year *= 10;
+ date.tm_year += (*(++val) - 0x30);
+ date.tm_year *= 10;
+ date.tm_year += (*(++val) - 0x30) - 1900;
+ LOG_VERBOSE << date.tm_year;
+
+ val++;
+ date.tm_mon = (*(++val) - 0x30) * 10;
+ date.tm_mon += (*(++val) - 0x30) - 1;
+ LOG_VERBOSE << date.tm_mon;
+
+ val++;
+ date.tm_mday = (*(++val) - 0x30) * 10;
+ date.tm_mday += (*(++val) - 0x30);
+ LOG_VERBOSE << date.tm_mday;
+
+ val++;
+ date.tm_hour = (*(++val) - 0x30) * 10;
+ date.tm_hour += (*(++val) - 0x30);
+ LOG_VERBOSE << date.tm_hour;
+
+ val++;
+ date.tm_min = (*(++val) - 0x30) * 10;
+ date.tm_min += (*(++val) - 0x30);
+ LOG_VERBOSE << date.tm_min;
+
+ val++;
+ double sec = strtod(++val, &end);
+ LOG_VERBOSE << sec;
+
+ date.tm_sec = static_cast(sec);
+ LOG_VERBOSE << date.tm_sec;
+
+ time_t time = use_local ? mktime(&date) : timegm(&date);
+ LOG_VERBOSE << time;
+
+ double ret = static_cast(time) + (sec - date.tm_sec);
+ LOG_VERBOSE << ret;
+
+ return ret;
+}
+
+PGresult* PqColumnDataSource::get_result() const {
+ return result_source->get_result();
+}
+
+const char* PqColumnDataSource::get_result_value() const {
+ const char* val = PQgetvalue(get_result(), 0, get_j());
+ LOG_VERBOSE << val;
+ return val;
+}
diff --git a/src/PqColumnDataSource.h b/src/PqColumnDataSource.h
new file mode 100644
index 0000000..69eecde
--- /dev/null
+++ b/src/PqColumnDataSource.h
@@ -0,0 +1,40 @@
+#ifndef RPOSTGRES_PQCOLUMNDATASOURCE_H
+#define RPOSTGRES_PQCOLUMNDATASOURCE_H
+
+#include "DbColumnDataSource.h"
+
+class PqResultSource;
+class PqColumnDataSourceFactory;
+
+class PqColumnDataSource : public DbColumnDataSource {
+ PqResultSource* result_source;
+ const DATA_TYPE dt;
+
+public:
+ PqColumnDataSource(PqResultSource* result_source_, const DATA_TYPE dt_, const int j);
+ virtual ~PqColumnDataSource();
+
+public:
+ virtual DATA_TYPE get_data_type() const;
+ virtual DATA_TYPE get_decl_data_type() const;
+
+ virtual bool is_null() const;
+
+ virtual int fetch_bool() const;
+ virtual int fetch_int() const;
+ virtual int64_t fetch_int64() const;
+ virtual double fetch_real() const;
+ virtual SEXP fetch_string() const;
+ virtual SEXP fetch_blob() const;
+ virtual double fetch_date() const;
+ virtual double fetch_datetime_local() const;
+ virtual double fetch_datetime() const;
+ virtual double fetch_time() const;
+
+private:
+ static double convert_datetime(const char* val, bool use_local);
+ PGresult* get_result() const;
+ const char* get_result_value() const;
+};
+
+#endif //RPOSTGRES_PQCOLUMNDATASOURCE_H
diff --git a/src/PqColumnDataSourceFactory.cpp b/src/PqColumnDataSourceFactory.cpp
new file mode 100644
index 0000000..16831d3
--- /dev/null
+++ b/src/PqColumnDataSourceFactory.cpp
@@ -0,0 +1,16 @@
+#include "pch.h"
+#include "PqColumnDataSourceFactory.h"
+#include "PqColumnDataSource.h"
+
+PqColumnDataSourceFactory::PqColumnDataSourceFactory(PqResultSource* result_source_, const std::vector& types_) :
+result_source(result_source_),
+types(types_)
+{
+}
+
+PqColumnDataSourceFactory::~PqColumnDataSourceFactory() {
+}
+
+DbColumnDataSource* PqColumnDataSourceFactory::create(const int j) {
+ return new PqColumnDataSource(result_source, types[j], j);
+}
diff --git a/src/PqColumnDataSourceFactory.h b/src/PqColumnDataSourceFactory.h
new file mode 100644
index 0000000..d33b2c4
--- /dev/null
+++ b/src/PqColumnDataSourceFactory.h
@@ -0,0 +1,21 @@
+#ifndef RPOSTGRES_PQCOLUMNDATASOURCEFACTORY_H
+#define RPOSTGRES_PQCOLUMNDATASOURCEFACTORY_H
+
+#include "DbColumnDataSourceFactory.h"
+#include "DbColumnDataType.h"
+
+class PqResultSource;
+
+class PqColumnDataSourceFactory : public DbColumnDataSourceFactory {
+ PqResultSource* result_source;
+ const std::vector types;
+
+public:
+ PqColumnDataSourceFactory(PqResultSource* result_source_, const std::vector& types_);
+ virtual ~PqColumnDataSourceFactory();
+
+public:
+ virtual DbColumnDataSource* create(const int j);
+};
+
+#endif //RPOSTGRES_PQCOLUMNDATASOURCEFACTORY_H
diff --git a/src/PqDataFrame.cpp b/src/PqDataFrame.cpp
new file mode 100644
index 0000000..bf2101a
--- /dev/null
+++ b/src/PqDataFrame.cpp
@@ -0,0 +1,13 @@
+#include "pch.h"
+#include "PqDataFrame.h"
+#include "PqColumnDataSourceFactory.h"
+
+
+PqDataFrame::PqDataFrame(PqResultSource* result_source, const std::vector& names, const int n_max_,
+ const std::vector& types) :
+DbDataFrame(new PqColumnDataSourceFactory(result_source, types), names, n_max_, types)
+{
+}
+
+PqDataFrame::~PqDataFrame() {
+}
diff --git a/src/PqDataFrame.h b/src/PqDataFrame.h
new file mode 100644
index 0000000..09ac18e
--- /dev/null
+++ b/src/PqDataFrame.h
@@ -0,0 +1,15 @@
+#ifndef RPOSTGRES_PQDATAFRAME_H
+#define RPOSTGRES_PQDATAFRAME_H
+
+#include "DbDataFrame.h"
+
+class PqResultSource;
+
+class PqDataFrame : public DbDataFrame {
+public:
+ PqDataFrame(PqResultSource* result_source, const std::vector& names, const int n_max_,
+ const std::vector& types);
+ ~PqDataFrame();
+};
+
+#endif //RPOSTGRES_PQDATAFRAME_H
diff --git a/src/PqResultImpl.cpp b/src/PqResultImpl.cpp
new file mode 100644
index 0000000..3b220af
--- /dev/null
+++ b/src/PqResultImpl.cpp
@@ -0,0 +1,402 @@
+#include "pch.h"
+#include "PqResultImpl.h"
+#include "DbConnection.h"
+#include "DbResult.h"
+#include "DbColumnStorage.h"
+#include "PqDataFrame.h"
+
+PqResultImpl::PqResultImpl(DbResult* pRes, PGconn* pConn, const std::string& sql) :
+res(pRes),
+pConn_(pConn),
+pSpec_(prepare(pConn, sql)),
+cache(pSpec_),
+complete_(false),
+ready_(false),
+nrows_(0),
+rows_affected_(0),
+group_(0),
+groups_(0),
+pRes_(NULL)
+{
+
+ LOG_DEBUG << sql;
+
+ try {
+ if (cache.nparams_ == 0) {
+ bind();
+ }
+ } catch (...) {
+ PQclear(pSpec_);
+ pSpec_ = NULL;
+ throw;
+ }
+}
+
+PqResultImpl::~PqResultImpl() {
+ try {
+ PQclear(pSpec_);
+ } catch (...) {}
+}
+
+
+
+// Cache ///////////////////////////////////////////////////////////////////////
+
+PqResultImpl::_cache::_cache(PGresult* spec) :
+names_(get_column_names(spec)),
+types_(get_column_types(spec)),
+ncols_(names_.size()),
+nparams_(PQnparams(spec))
+{
+ for (int i = 0; i < nparams_; ++i)
+ LOG_VERBOSE << PQparamtype(spec, i);
+}
+
+
+std::vector PqResultImpl::_cache::get_column_names(PGresult* spec) {
+ std::vector names;
+ int ncols_ = PQnfields(spec);
+ names.reserve(ncols_);
+
+ for (int i = 0; i < ncols_; ++i) {
+ names.push_back(std::string(PQfname(spec, i)));
+ }
+
+ return names;
+}
+
+std::vector PqResultImpl::_cache::get_column_types(PGresult* spec) {
+ std::vector types;
+ int ncols_ = PQnfields(spec);
+ types.reserve(ncols_);
+
+ for (int i = 0; i < ncols_; ++i) {
+ Oid type = PQftype(spec, i);
+ // SELECT oid, typname FROM pg_type WHERE typtype = 'b'
+ switch (type) {
+ case 20: // BIGINT
+ types.push_back(DT_INT64);
+ break;
+
+ case 21: // SMALLINT
+ case 23: // INTEGER
+ case 26: // OID
+ types.push_back(DT_INT);
+ break;
+
+ case 1700: // DECIMAL
+ case 701: // FLOAT8
+ case 700: // FLOAT
+ case 790: // MONEY
+ types.push_back(DT_REAL);
+ break;
+
+ case 18: // CHAR
+ case 19: // NAME
+ case 25: // TEXT
+ case 114: // JSON
+ case 1042: // CHAR
+ case 1043: // VARCHAR
+ types.push_back(DT_STRING);
+ break;
+ case 1082: // DATE
+ types.push_back(DT_DATE);
+ break;
+ case 1083: // TIME
+ case 1266: // TIMETZOID
+ types.push_back(DT_TIME);
+ break;
+ case 1114: // TIMESTAMP
+ types.push_back(DT_DATETIME);
+ break;
+ case 1184: // TIMESTAMPTZOID
+ types.push_back(DT_DATETIMETZ);
+ break;
+ case 1186: // INTERVAL
+ case 3802: // JSONB
+ case 2950: // UUID
+ types.push_back(DT_STRING);
+ break;
+
+ case 16: // BOOL
+ types.push_back(DT_BOOL);
+ break;
+
+ case 17: // BYTEA
+ case 2278: // NULL
+ types.push_back(DT_BLOB);
+ break;
+
+ case 705: // UNKNOWN
+ types.push_back(DT_STRING);
+ break;
+
+ default:
+ types.push_back(DT_STRING);
+ warning("Unknown field type (%d) in column %s", type, PQfname(spec, i));
+ }
+ }
+
+ return types;
+}
+
+PGresult* PqResultImpl::prepare(PGconn* conn, const std::string& sql) {
+ // Prepare query
+ PGresult* prep = PQprepare(conn, "", sql.c_str(), 0, NULL);
+ if (PQresultStatus(prep) != PGRES_COMMAND_OK) {
+ PQclear(prep);
+ DbConnection::conn_stop(conn, "Failed to prepare query");
+ }
+ PQclear(prep);
+
+ // Retrieve query specification
+ PGresult* spec = PQdescribePrepared(conn, "");
+ if (PQresultStatus(spec) != PGRES_COMMAND_OK) {
+ PQclear(spec);
+ DbConnection::conn_stop(conn, "Failed to retrieve query result metadata");
+ }
+
+ return spec;
+}
+
+void PqResultImpl::init(bool params_have_rows) {
+ ready_ = true;
+ nrows_ = 0;
+ complete_ = !params_have_rows;
+}
+
+
+
+// Publics /////////////////////////////////////////////////////////////////////
+
+bool PqResultImpl::complete() {
+ return complete_;
+}
+
+int PqResultImpl::n_rows_fetched() {
+ return nrows_;
+}
+
+int PqResultImpl::n_rows_affected() {
+ if (!ready_) return NA_INTEGER;
+ if (cache.ncols_ > 0) return 0;
+ return rows_affected_;
+}
+
+void PqResultImpl::bind(const List& params) {
+ if (params.size() != cache.nparams_) {
+ stop("Query requires %i params; %i supplied.",
+ cache.nparams_, params.size());
+ }
+
+ if (params.size() == 0 && ready_) {
+ stop("Query does not require parameters.");
+ }
+
+ set_params(params);
+
+ if (params.length() > 0) {
+ SEXP first_col = params[0];
+ groups_ = Rf_length(first_col);
+ }
+ else {
+ groups_ = 1;
+ }
+ group_ = 0;
+
+ rows_affected_ = 0;
+
+ bool has_params = bind_row();
+ after_bind(has_params);
+}
+
+List PqResultImpl::fetch(const int n_max) {
+ if (!ready_)
+ stop("Query needs to be bound before fetching");
+
+ int n = 0;
+ List out;
+
+ if (n_max != 0)
+ out = fetch_rows(n_max, n);
+ else
+ out = peek_first_row();
+
+ return out;
+}
+
+List PqResultImpl::get_column_info() {
+ peek_first_row();
+
+ CharacterVector names(cache.names_.begin(), cache.names_.end());
+
+ CharacterVector types(cache.ncols_);
+ for (size_t i = 0; i < cache.ncols_; i++) {
+ types[i] = Rf_type2char(DbColumnStorage::sexptype_from_datatype(cache.types_[i]));
+ }
+
+ List out = Rcpp::List::create(names, types);
+ out.attr("row.names") = IntegerVector::create(NA_INTEGER, -cache.ncols_);
+ out.attr("class") = "data.frame";
+ out.attr("names") = CharacterVector::create("name", "type");
+
+ return out;
+}
+
+
+
+// Publics (custom) ////////////////////////////////////////////////////////////
+
+
+
+
+// Privates ////////////////////////////////////////////////////////////////////
+
+void PqResultImpl::set_params(const List& params) {
+ params_ = params;
+}
+
+bool PqResultImpl::bind_row() {
+ LOG_VERBOSE << "groups: " << group_ << "/" << groups_;
+
+ if (group_ >= groups_)
+ return false;
+
+ if (ready_ || group_ > 0)
+ res->finish_query();
+
+ std::vector c_params(cache.nparams_);
+ std::vector formats(cache.nparams_);
+ std::vector lengths(cache.nparams_);
+ for (int i = 0; i < cache.nparams_; ++i) {
+ if (TYPEOF(params_[i]) == VECSXP) {
+ List param(params_[i]);
+ if (!Rf_isNull(param[group_])) {
+ Rbyte* param_value = RAW(param[group_]);
+ c_params[i] = reinterpret_cast(param_value);
+ formats[i] = 1;
+ lengths[i] = Rf_length(param[group_]);
+ }
+ }
+ else {
+ CharacterVector param(params_[i]);
+ if (param[group_] != NA_STRING) {
+ c_params[i] = CHAR(param[group_]);
+ }
+ }
+ }
+
+ if (!PQsendQueryPrepared(pConn_, "", cache.nparams_, &c_params[0],
+ &lengths[0], &formats[0], 0))
+ conn_stop("Failed to send query");
+
+ if (!PQsetSingleRowMode(pConn_))
+ conn_stop("Failed to set single row mode");
+
+ return true;
+}
+
+void PqResultImpl::after_bind(bool params_have_rows) {
+ init(params_have_rows);
+ if (params_have_rows)
+ step();
+}
+
+List PqResultImpl::fetch_rows(const int n_max, int& n) {
+ n = (n_max < 0) ? 100 : n_max;
+
+ PqDataFrame data(this, cache.names_, n_max, cache.types_);
+
+ if (complete_ && data.get_ncols() == 0) {
+ warning("Don't need to call dbFetch() for statements, only for queries");
+ }
+
+ while (!complete_) {
+ LOG_VERBOSE << nrows_ << "/" << n;
+
+ data.set_col_values();
+ step();
+ nrows_++;
+ if (!data.advance())
+ break;
+ }
+
+ LOG_VERBOSE << nrows_;
+ return data.get_data();
+}
+
+void PqResultImpl::step() {
+ while (step_run())
+ ;
+}
+
+bool PqResultImpl::step_run() {
+ LOG_VERBOSE;
+
+ pRes_ = PQgetResult(pConn_);
+
+ // We're done, but we need to call PQgetResult until it returns NULL
+ if (PQresultStatus(pRes_) == PGRES_TUPLES_OK) {
+ PGresult* next = PQgetResult(pConn_);
+ while (next != NULL) {
+ PQclear(next);
+ next = PQgetResult(pConn_);
+ }
+ }
+
+ if (pRes_ == NULL) {
+ PQclear(pRes_);
+ stop("No active query");
+ }
+
+ ExecStatusType status = PQresultStatus(pRes_);
+
+ switch (status) {
+ case PGRES_FATAL_ERROR:
+ {
+ PQclear(pRes_);
+ conn_stop("Failed to fetch row");
+ return false;
+ }
+ case PGRES_SINGLE_TUPLE:
+ return false;
+ default:
+ return step_done();
+ }
+}
+
+bool PqResultImpl::step_done() {
+ char* tuples = PQcmdTuples(pRes_);
+ rows_affected_ += atoi(tuples);
+
+ ++group_;
+ bool more_params = bind_row();
+
+ if (!more_params)
+ complete_ = true;
+
+ LOG_VERBOSE << "group: " << group_ << ", more_params: " << more_params;
+ return more_params;
+}
+
+List PqResultImpl::peek_first_row() {
+ PqDataFrame data(this, cache.names_, 1, cache.types_);
+
+ if (!complete_)
+ data.set_col_values();
+ // Not calling data.advance(), remains a zero-row data frame
+
+ return data.get_data();
+}
+
+void PqResultImpl::conn_stop(const char* msg) const {
+ DbConnection::conn_stop(pConn_, msg);
+}
+
+void PqResultImpl::bind() {
+ bind(List());
+}
+
+PGresult* PqResultImpl::get_result() {
+ return pRes_;
+}
diff --git a/src/PqResultImpl.h b/src/PqResultImpl.h
new file mode 100644
index 0000000..070546d
--- /dev/null
+++ b/src/PqResultImpl.h
@@ -0,0 +1,79 @@
+#ifndef RPOSTGRES_PQRESULTIMPL_H
+#define RPOSTGRES_PQRESULTIMPL_H
+
+#include
+#include
+#include "DbColumnDataType.h"
+#include "PqResultSource.h"
+
+class DbResult;
+
+class PqResultImpl : boost::noncopyable, public PqResultSource {
+ // Back pointer for query cancellation
+ DbResult* res;
+
+ // Wrapped pointer
+ PGconn* pConn_;
+ PGresult* pSpec_;
+
+ // Cache
+ struct _cache {
+ const std::vector names_;
+ const std::vector types_;
+ const size_t ncols_;
+ const int nparams_;
+
+ _cache(PGresult* spec);
+
+ static std::vector get_column_names(PGresult* spec);
+ static std::vector get_column_types(PGresult* spec);
+ } cache;
+
+ // State
+ bool complete_;
+ bool ready_;
+ int nrows_;
+ int rows_affected_;
+ List params_;
+ int group_, groups_;
+ PGresult* pRes_;
+
+public:
+ PqResultImpl(DbResult* pRes, PGconn* pConn, const std::string& sql);
+ ~PqResultImpl();
+
+private:
+ static PGresult* prepare(PGconn* conn, const std::string& sql);
+ void init(bool params_have_rows);
+
+public:
+ bool complete();
+ int n_rows_fetched();
+ int n_rows_affected();
+ void bind(const List& params);
+ List fetch(const int n_max);
+
+ List get_column_info();
+
+private:
+ void set_params(const List& params);
+ bool bind_row();
+ void after_bind(bool params_have_rows);
+
+ List fetch_rows(int n_max, int& n);
+ void step();
+ bool step_run();
+ bool step_done();
+ List peek_first_row();
+
+private:
+ void conn_stop(const char* msg) const;
+
+ void bind();
+
+public:
+ // PqResultSource
+ PGresult* get_result();
+};
+
+#endif //RPOSTGRES_PQRESULTIMPL_H
diff --git a/src/PqResultSource.cpp b/src/PqResultSource.cpp
new file mode 100644
index 0000000..7c34513
--- /dev/null
+++ b/src/PqResultSource.cpp
@@ -0,0 +1,8 @@
+#include "pch.h"
+#include "PqResultSource.h"
+
+PqResultSource::PqResultSource() {
+}
+
+PqResultSource::~PqResultSource() {
+}
diff --git a/src/PqResultSource.h b/src/PqResultSource.h
new file mode 100644
index 0000000..ba31b31
--- /dev/null
+++ b/src/PqResultSource.h
@@ -0,0 +1,13 @@
+#ifndef RPOSTGRES_PQRESULTSOURCE_H
+#define RPOSTGRES_PQRESULTSOURCE_H
+
+class PqResultSource {
+public:
+ PqResultSource();
+ virtual ~PqResultSource();
+
+public:
+ virtual PGresult* get_result() = 0;
+};
+
+#endif //RPOSTGRES_PQRESULTSOURCE_H
diff --git a/src/RPostgres-init.c b/src/RPostgres-init.c
new file mode 100644
index 0000000..1620e89
--- /dev/null
+++ b/src/RPostgres-init.c
@@ -0,0 +1,28 @@
+#include
+
+#ifdef _WIN32
+#include
+#endif
+
+// From http://www.postgresql.org/docs/9.4/static/libpq-connect.html:
+// On Windows, there is a way to improve performance if a single database
+// connection is repeatedly started and shutdown. Internally, libpq calls
+// WSAStartup() and WSACleanup() for connection startup and shutdown,
+// respectively. WSAStartup() increments an internal Windows library reference
+// count which is decremented by WSACleanup(). When the reference count is just
+// one, calling WSACleanup() frees all resources and all DLLs are unloaded.
+// This is an expensive operation. To avoid this, an application can manually
+// call WSAStartup() so resources will not be freed when the last database
+// connection is closed.
+
+void R_init_mypackage(DllInfo *info) {
+#ifdef _WIN32
+ WSAStartup(MAKEWORD(1, 0), NULL);
+#endif
+}
+
+void R_unload_mylib(DllInfo *info) {
+#ifdef _WIN32
+ WSACleanup();
+#endif
+}
diff --git a/src/RPostgres_types.h b/src/RPostgres_types.h
new file mode 100644
index 0000000..57c4af7
--- /dev/null
+++ b/src/RPostgres_types.h
@@ -0,0 +1,19 @@
+#include "pch.h"
+
+#ifndef __RPOSTGRES_TYPES__
+#define __RPOSTGRES_TYPES__
+
+#include "DbConnection.h"
+#include "DbResult.h"
+
+namespace Rcpp {
+
+template<>
+DbConnection* as(SEXP x);
+
+template<>
+DbResult* as(SEXP x);
+
+}
+
+#endif
diff --git a/src/RcppExports.cpp b/src/RcppExports.cpp
new file mode 100644
index 0000000..72dd868
--- /dev/null
+++ b/src/RcppExports.cpp
@@ -0,0 +1,286 @@
+// Generated by using Rcpp::compileAttributes() -> do not edit by hand
+// Generator token: 10BE3573-1514-4C36-9D1C-5A225CD40393
+
+#include "RPostgres_types.h"
+#include
+
+using namespace Rcpp;
+
+// connection_create
+XPtr connection_create(std::vector keys, std::vector values);
+RcppExport SEXP _RPostgres_connection_create(SEXP keysSEXP, SEXP valuesSEXP) {
+BEGIN_RCPP
+ Rcpp::RObject rcpp_result_gen;
+ Rcpp::RNGScope rcpp_rngScope_gen;
+ Rcpp::traits::input_parameter< std::vector >::type keys(keysSEXP);
+ Rcpp::traits::input_parameter< std::vector >::type values(valuesSEXP);
+ rcpp_result_gen = Rcpp::wrap(connection_create(keys, values));
+ return rcpp_result_gen;
+END_RCPP
+}
+// connection_valid
+bool connection_valid(XPtr con_);
+RcppExport SEXP _RPostgres_connection_valid(SEXP con_SEXP) {
+BEGIN_RCPP
+ Rcpp::RObject rcpp_result_gen;
+ Rcpp::RNGScope rcpp_rngScope_gen;
+ Rcpp::traits::input_parameter< XPtr >::type con_(con_SEXP);
+ rcpp_result_gen = Rcpp::wrap(connection_valid(con_));
+ return rcpp_result_gen;
+END_RCPP
+}
+// connection_release
+void connection_release(XPtr con_);
+RcppExport SEXP _RPostgres_connection_release(SEXP con_SEXP) {
+BEGIN_RCPP
+ Rcpp::RNGScope rcpp_rngScope_gen;
+ Rcpp::traits::input_parameter< XPtr >::type con_(con_SEXP);
+ connection_release(con_);
+ return R_NilValue;
+END_RCPP
+}
+// connection_info
+List connection_info(DbConnection* con);
+RcppExport SEXP _RPostgres_connection_info(SEXP conSEXP) {
+BEGIN_RCPP
+ Rcpp::RObject rcpp_result_gen;
+ Rcpp::RNGScope rcpp_rngScope_gen;
+ Rcpp::traits::input_parameter< DbConnection* >::type con(conSEXP);
+ rcpp_result_gen = Rcpp::wrap(connection_info(con));
+ return rcpp_result_gen;
+END_RCPP
+}
+// connection_quote_string
+CharacterVector connection_quote_string(DbConnection* con, CharacterVector xs);
+RcppExport SEXP _RPostgres_connection_quote_string(SEXP conSEXP, SEXP xsSEXP) {
+BEGIN_RCPP
+ Rcpp::RObject rcpp_result_gen;
+ Rcpp::RNGScope rcpp_rngScope_gen;
+ Rcpp::traits::input_parameter< DbConnection* >::type con(conSEXP);
+ Rcpp::traits::input_parameter< CharacterVector >::type xs(xsSEXP);
+ rcpp_result_gen = Rcpp::wrap(connection_quote_string(con, xs));
+ return rcpp_result_gen;
+END_RCPP
+}
+// connection_quote_identifier
+CharacterVector connection_quote_identifier(DbConnection* con, CharacterVector xs);
+RcppExport SEXP _RPostgres_connection_quote_identifier(SEXP conSEXP, SEXP xsSEXP) {
+BEGIN_RCPP
+ Rcpp::RObject rcpp_result_gen;
+ Rcpp::RNGScope rcpp_rngScope_gen;
+ Rcpp::traits::input_parameter< DbConnection* >::type con(conSEXP);
+ Rcpp::traits::input_parameter< CharacterVector >::type xs(xsSEXP);
+ rcpp_result_gen = Rcpp::wrap(connection_quote_identifier(con, xs));
+ return rcpp_result_gen;
+END_RCPP
+}
+// connection_is_transacting
+bool connection_is_transacting(DbConnection* con);
+RcppExport SEXP _RPostgres_connection_is_transacting(SEXP conSEXP) {
+BEGIN_RCPP
+ Rcpp::RObject rcpp_result_gen;
+ Rcpp::RNGScope rcpp_rngScope_gen;
+ Rcpp::traits::input_parameter< DbConnection* >::type con(conSEXP);
+ rcpp_result_gen = Rcpp::wrap(connection_is_transacting(con));
+ return rcpp_result_gen;
+END_RCPP
+}
+// connection_set_transacting
+void connection_set_transacting(DbConnection* con, bool transacting);
+RcppExport SEXP _RPostgres_connection_set_transacting(SEXP conSEXP, SEXP transactingSEXP) {
+BEGIN_RCPP
+ Rcpp::RNGScope rcpp_rngScope_gen;
+ Rcpp::traits::input_parameter< DbConnection* >::type con(conSEXP);
+ Rcpp::traits::input_parameter< bool >::type transacting(transactingSEXP);
+ connection_set_transacting(con, transacting);
+ return R_NilValue;
+END_RCPP
+}
+// connection_copy_data
+void connection_copy_data(DbConnection* con, std::string sql, List df);
+RcppExport SEXP _RPostgres_connection_copy_data(SEXP conSEXP, SEXP sqlSEXP, SEXP dfSEXP) {
+BEGIN_RCPP
+ Rcpp::RNGScope rcpp_rngScope_gen;
+ Rcpp::traits::input_parameter< DbConnection* >::type con(conSEXP);
+ Rcpp::traits::input_parameter< std::string >::type sql(sqlSEXP);
+ Rcpp::traits::input_parameter< List >::type df(dfSEXP);
+ connection_copy_data(con, sql, df);
+ return R_NilValue;
+END_RCPP
+}
+// encode_vector
+std::string encode_vector(RObject x);
+RcppExport SEXP _RPostgres_encode_vector(SEXP xSEXP) {
+BEGIN_RCPP
+ Rcpp::RObject rcpp_result_gen;
+ Rcpp::RNGScope rcpp_rngScope_gen;
+ Rcpp::traits::input_parameter< RObject >::type x(xSEXP);
+ rcpp_result_gen = Rcpp::wrap(encode_vector(x));
+ return rcpp_result_gen;
+END_RCPP
+}
+// encode_data_frame
+std::string encode_data_frame(List x);
+RcppExport SEXP _RPostgres_encode_data_frame(SEXP xSEXP) {
+BEGIN_RCPP
+ Rcpp::RObject rcpp_result_gen;
+ Rcpp::RNGScope rcpp_rngScope_gen;
+ Rcpp::traits::input_parameter< List >::type x(xSEXP);
+ rcpp_result_gen = Rcpp::wrap(encode_data_frame(x));
+ return rcpp_result_gen;
+END_RCPP
+}
+// encrypt_password
+String encrypt_password(String password, String user);
+RcppExport SEXP _RPostgres_encrypt_password(SEXP passwordSEXP, SEXP userSEXP) {
+BEGIN_RCPP
+ Rcpp::RObject rcpp_result_gen;
+ Rcpp::RNGScope rcpp_rngScope_gen;
+ Rcpp::traits::input_parameter< String >::type password(passwordSEXP);
+ Rcpp::traits::input_parameter< String >::type user(userSEXP);
+ rcpp_result_gen = Rcpp::wrap(encrypt_password(password, user));
+ return rcpp_result_gen;
+END_RCPP
+}
+// init_logging
+void init_logging(const std::string& log_level);
+RcppExport SEXP _RPostgres_init_logging(SEXP log_levelSEXP) {
+BEGIN_RCPP
+ Rcpp::RNGScope rcpp_rngScope_gen;
+ Rcpp::traits::input_parameter< const std::string& >::type log_level(log_levelSEXP);
+ init_logging(log_level);
+ return R_NilValue;
+END_RCPP
+}
+// result_create
+XPtr result_create(XPtr con, std::string sql, bool is_statement);
+RcppExport SEXP _RPostgres_result_create(SEXP conSEXP, SEXP sqlSEXP, SEXP is_statementSEXP) {
+BEGIN_RCPP
+ Rcpp::RObject rcpp_result_gen;
+ Rcpp::RNGScope rcpp_rngScope_gen;
+ Rcpp::traits::input_parameter< XPtr >::type con(conSEXP);
+ Rcpp::traits::input_parameter< std::string >::type sql(sqlSEXP);
+ Rcpp::traits::input_parameter< bool >::type is_statement(is_statementSEXP);
+ rcpp_result_gen = Rcpp::wrap(result_create(con, sql, is_statement));
+ return rcpp_result_gen;
+END_RCPP
+}
+// result_release
+void result_release(XPtr res);
+RcppExport SEXP _RPostgres_result_release(SEXP resSEXP) {
+BEGIN_RCPP
+ Rcpp::RNGScope rcpp_rngScope_gen;
+ Rcpp::traits::input_parameter< XPtr >::type res(resSEXP);
+ result_release(res);
+ return R_NilValue;
+END_RCPP
+}
+// result_valid
+bool result_valid(XPtr res_);
+RcppExport SEXP _RPostgres_result_valid(SEXP res_SEXP) {
+BEGIN_RCPP
+ Rcpp::RObject rcpp_result_gen;
+ Rcpp::RNGScope rcpp_rngScope_gen;
+ Rcpp::traits::input_parameter< XPtr >::type res_(res_SEXP);
+ rcpp_result_gen = Rcpp::wrap(result_valid(res_));
+ return rcpp_result_gen;
+END_RCPP
+}
+// result_fetch
+List result_fetch(DbResult* res, const int n);
+RcppExport SEXP _RPostgres_result_fetch(SEXP resSEXP, SEXP nSEXP) {
+BEGIN_RCPP
+ Rcpp::RObject rcpp_result_gen;
+ Rcpp::RNGScope rcpp_rngScope_gen;
+ Rcpp::traits::input_parameter< DbResult* >::type res(resSEXP);
+ Rcpp::traits::input_parameter< const int >::type n(nSEXP);
+ rcpp_result_gen = Rcpp::wrap(result_fetch(res, n));
+ return rcpp_result_gen;
+END_RCPP
+}
+// result_bind
+void result_bind(DbResult* res, List params);
+RcppExport SEXP _RPostgres_result_bind(SEXP resSEXP, SEXP paramsSEXP) {
+BEGIN_RCPP
+ Rcpp::RNGScope rcpp_rngScope_gen;
+ Rcpp::traits::input_parameter< DbResult* >::type res(resSEXP);
+ Rcpp::traits::input_parameter< List >::type params(paramsSEXP);
+ result_bind(res, params);
+ return R_NilValue;
+END_RCPP
+}
+// result_has_completed
+bool result_has_completed(DbResult* res);
+RcppExport SEXP _RPostgres_result_has_completed(SEXP resSEXP) {
+BEGIN_RCPP
+ Rcpp::RObject rcpp_result_gen;
+ Rcpp::RNGScope rcpp_rngScope_gen;
+ Rcpp::traits::input_parameter< DbResult* >::type res(resSEXP);
+ rcpp_result_gen = Rcpp::wrap(result_has_completed(res));
+ return rcpp_result_gen;
+END_RCPP
+}
+// result_rows_fetched
+int result_rows_fetched(DbResult* res);
+RcppExport SEXP _RPostgres_result_rows_fetched(SEXP resSEXP) {
+BEGIN_RCPP
+ Rcpp::RObject rcpp_result_gen;
+ Rcpp::RNGScope rcpp_rngScope_gen;
+ Rcpp::traits::input_parameter< DbResult* >::type res(resSEXP);
+ rcpp_result_gen = Rcpp::wrap(result_rows_fetched(res));
+ return rcpp_result_gen;
+END_RCPP
+}
+// result_rows_affected
+int result_rows_affected(DbResult* res);
+RcppExport SEXP _RPostgres_result_rows_affected(SEXP resSEXP) {
+BEGIN_RCPP
+ Rcpp::RObject rcpp_result_gen;
+ Rcpp::RNGScope rcpp_rngScope_gen;
+ Rcpp::traits::input_parameter< DbResult* >::type res(resSEXP);
+ rcpp_result_gen = Rcpp::wrap(result_rows_affected(res));
+ return rcpp_result_gen;
+END_RCPP
+}
+// result_column_info
+List result_column_info(DbResult* res);
+RcppExport SEXP _RPostgres_result_column_info(SEXP resSEXP) {
+BEGIN_RCPP
+ Rcpp::RObject rcpp_result_gen;
+ Rcpp::RNGScope rcpp_rngScope_gen;
+ Rcpp::traits::input_parameter< DbResult* >::type res(resSEXP);
+ rcpp_result_gen = Rcpp::wrap(result_column_info(res));
+ return rcpp_result_gen;
+END_RCPP
+}
+
+static const R_CallMethodDef CallEntries[] = {
+ {"_RPostgres_connection_create", (DL_FUNC) &_RPostgres_connection_create, 2},
+ {"_RPostgres_connection_valid", (DL_FUNC) &_RPostgres_connection_valid, 1},
+ {"_RPostgres_connection_release", (DL_FUNC) &_RPostgres_connection_release, 1},
+ {"_RPostgres_connection_info", (DL_FUNC) &_RPostgres_connection_info, 1},
+ {"_RPostgres_connection_quote_string", (DL_FUNC) &_RPostgres_connection_quote_string, 2},
+ {"_RPostgres_connection_quote_identifier", (DL_FUNC) &_RPostgres_connection_quote_identifier, 2},
+ {"_RPostgres_connection_is_transacting", (DL_FUNC) &_RPostgres_connection_is_transacting, 1},
+ {"_RPostgres_connection_set_transacting", (DL_FUNC) &_RPostgres_connection_set_transacting, 2},
+ {"_RPostgres_connection_copy_data", (DL_FUNC) &_RPostgres_connection_copy_data, 3},
+ {"_RPostgres_encode_vector", (DL_FUNC) &_RPostgres_encode_vector, 1},
+ {"_RPostgres_encode_data_frame", (DL_FUNC) &_RPostgres_encode_data_frame, 1},
+ {"_RPostgres_encrypt_password", (DL_FUNC) &_RPostgres_encrypt_password, 2},
+ {"_RPostgres_init_logging", (DL_FUNC) &_RPostgres_init_logging, 1},
+ {"_RPostgres_result_create", (DL_FUNC) &_RPostgres_result_create, 3},
+ {"_RPostgres_result_release", (DL_FUNC) &_RPostgres_result_release, 1},
+ {"_RPostgres_result_valid", (DL_FUNC) &_RPostgres_result_valid, 1},
+ {"_RPostgres_result_fetch", (DL_FUNC) &_RPostgres_result_fetch, 2},
+ {"_RPostgres_result_bind", (DL_FUNC) &_RPostgres_result_bind, 2},
+ {"_RPostgres_result_has_completed", (DL_FUNC) &_RPostgres_result_has_completed, 1},
+ {"_RPostgres_result_rows_fetched", (DL_FUNC) &_RPostgres_result_rows_fetched, 1},
+ {"_RPostgres_result_rows_affected", (DL_FUNC) &_RPostgres_result_rows_affected, 1},
+ {"_RPostgres_result_column_info", (DL_FUNC) &_RPostgres_result_column_info, 1},
+ {NULL, NULL, 0}
+};
+
+RcppExport void R_init_RPostgres(DllInfo *dll) {
+ R_registerRoutines(dll, NULL, CallEntries, NULL, NULL);
+ R_useDynamicSymbols(dll, FALSE);
+}
diff --git a/src/connection.cpp b/src/connection.cpp
new file mode 100644
index 0000000..e94ecff
--- /dev/null
+++ b/src/connection.cpp
@@ -0,0 +1,109 @@
+#include "pch.h"
+#include "RPostgres_types.h"
+
+
+// [[Rcpp::export]]
+XPtr connection_create(
+ std::vector keys,
+ std::vector values
+) {
+ LOG_VERBOSE;
+
+ DbConnectionPtr* pConn = new DbConnectionPtr(
+ new DbConnection(keys, values)
+ );
+
+ return XPtr(pConn, true);
+}
+
+// [[Rcpp::export]]
+bool connection_valid(XPtr con_) {
+ DbConnectionPtr* con = con_.get();
+ return con;
+}
+
+// [[Rcpp::export]]
+void connection_release(XPtr con_) {
+ if (!connection_valid(con_)) {
+ warning("Already disconnected");
+ return;
+ }
+
+ DbConnectionPtr* con = con_.get();
+ if (con->get()->has_query()) {
+ warning("%s\n%s",
+ "There is a result object still in use.",
+ "The connection will be automatically released when it is closed"
+ );
+ }
+
+ con->get()->disconnect();
+ con_.release();
+}
+
+// [[Rcpp::export]]
+List connection_info(DbConnection* con) {
+ return con->info();
+}
+
+// Quoting
+
+// [[Rcpp::export]]
+CharacterVector connection_quote_string(DbConnection* con, CharacterVector xs) {
+ R_xlen_t n = xs.size();
+ CharacterVector output(n);
+
+ for (R_xlen_t i = 0; i < n; ++i) {
+ String x = xs[i];
+ output[i] = con->quote_string(x);
+ }
+
+ return output;
+}
+
+// [[Rcpp::export]]
+CharacterVector connection_quote_identifier(DbConnection* con, CharacterVector xs) {
+ R_xlen_t n = xs.size();
+ CharacterVector output(n);
+
+ for (R_xlen_t i = 0; i < n; ++i) {
+ String x = xs[i];
+ output[i] = con->quote_identifier(x);
+ }
+
+ return output;
+}
+
+// Transactions
+
+// [[Rcpp::export]]
+bool connection_is_transacting(DbConnection* con) {
+ return con->is_transacting();
+}
+
+// [[Rcpp::export]]
+void connection_set_transacting(DbConnection* con, bool transacting) {
+ con->set_transacting(transacting);
+}
+
+// Specific functions
+
+// [[Rcpp::export]]
+void connection_copy_data(DbConnection* con, std::string sql, List df) {
+ return con->copy_data(sql, df);
+}
+
+
+// as() override
+
+namespace Rcpp {
+
+template<>
+DbConnection* as(SEXP x) {
+ DbConnectionPtr* connection = (DbConnectionPtr*)(R_ExternalPtrAddr(x));
+ if (!connection)
+ stop("Invalid connection");
+ return connection->get();
+}
+
+}
diff --git a/src/encode.cpp b/src/encode.cpp
new file mode 100644
index 0000000..f5c4ad5
--- /dev/null
+++ b/src/encode.cpp
@@ -0,0 +1,143 @@
+#include "pch.h"
+#include "encode.h"
+
+
+// [[Rcpp::export]]
+std::string encode_vector(RObject x) {
+ std::string buffer;
+
+ int n = Rf_length(x);
+ for (int i = 0; i < n; ++i) {
+ encode_in_buffer(x, i, buffer);
+ if (i != n - 1)
+ buffer.push_back('\n');
+ }
+
+ return buffer;
+}
+
+void encode_row_in_buffer(List x, int i, std::string& buffer,
+ std::string fieldDelim,
+ std::string lineDelim) {
+ int p = Rf_length(x);
+ for (int j = 0; j < p; ++j) {
+ RObject xj(x[j]);
+ encode_in_buffer(xj, i, buffer);
+ if (j != p - 1)
+ buffer.append(fieldDelim);
+ }
+ buffer.append(lineDelim);
+}
+
+// [[Rcpp::export]]
+std::string encode_data_frame(List x) {
+ if (Rf_length(x) == 0)
+ return ("");
+ int n = Rf_length(x[0]);
+
+ std::string buffer;
+ for (int i = 0; i < n; ++i) {
+ encode_row_in_buffer(x, i, buffer);
+ }
+
+ return buffer;
+}
+
+// =============================================================================
+// Derived from EncodeElementS in RPostgreSQL
+// Written by: tomoakin@kenroku.kanazawa-u.ac.jp
+// License: GPL-2
+
+void encode_in_buffer(RObject x, int i, std::string& buffer) {
+ switch (TYPEOF(x)) {
+ case LGLSXP: {
+ int value = LOGICAL(x)[i];
+ if (value == TRUE) {
+ buffer.append("true");
+ } else if (value == FALSE) {
+ buffer.append("false");
+ } else {
+ buffer.append("\\N");
+ }
+ break;
+ }
+ case INTSXP: {
+ int value = INTEGER(x)[i];
+ if (value == NA_INTEGER) {
+ buffer.append("\\N");
+ } else {
+ char buf[32];
+ snprintf(buf, 32, "%d", value);
+ buffer.append(buf);
+ }
+ break;
+ }
+ case REALSXP: {
+ double value = REAL(x)[i];
+ if (!R_FINITE(value)) {
+ if (ISNA(value)) {
+ buffer.append("\\N");
+ } else if (ISNAN(value)) {
+ buffer.append("NaN");
+ } else if (value > 0) {
+ buffer.append("Infinity");
+ } else {
+ buffer.append("-Infinity");
+ }
+ } else {
+ char buf[15 + 1 + 1 + 4 + 1]; // minus + decimal + exponent + \0
+ snprintf(buf, 22, "%.15g", value);
+ buffer.append(buf);
+ }
+ break;
+ }
+ case STRSXP: {
+ RObject value = STRING_ELT(x, i);
+ if (value == NA_STRING) {
+ buffer.append("\\N");
+ } else {
+ const char* s = Rf_translateCharUTF8(STRING_ELT(x, i));
+ escape_in_buffer(s, buffer);
+ }
+ break;
+ }
+ default:
+ stop("Don't know how to handle vector of type %s.",
+ Rf_type2char(TYPEOF(x)));
+ }
+}
+
+
+// Escape postgresql special characters
+// http://www.postgresql.org/docs/9.4/static/sql-copy.html#AEN71914
+void escape_in_buffer(const char* string, std::string& buffer) {
+ size_t len = strlen(string);
+
+ for (size_t i = 0; i < len; ++i) {
+ switch (string[i]) {
+ case '\b':
+ buffer.append("\\b");
+ break;
+ case '\f':
+ buffer.append("\\f");
+ break;
+ case '\n':
+ buffer.append("\\n");
+ break;
+ case '\r':
+ buffer.append("\\r");
+ break;
+ case '\t':
+ buffer.append("\\t");
+ break;
+ case '\v':
+ buffer.append("\\v");
+ break;
+ case '\\':
+ buffer.append("\\\\");
+ break;
+ default:
+ buffer.push_back(string[i]);
+ }
+ }
+}
diff --git a/src/encode.h b/src/encode.h
new file mode 100644
index 0000000..3c489ab
--- /dev/null
+++ b/src/encode.h
@@ -0,0 +1,13 @@
+#ifndef __RPOSTGRES_ENCODE__
+#define __RPOSTGRES_ENCODE__
+
+// Defined in encode.cpp -------------------------------------------------------
+
+void escape_in_buffer(const char* string, std::string& buffer);
+void encode_in_buffer(RObject x, int i, std::string& buffer);
+void encode_row_in_buffer(List x, int i, std::string& buffer,
+ std::string fieldDelim = "\t",
+ std::string lineDelim = "\n");
+std::string encode_data_frame(List x);
+
+#endif
diff --git a/src/encrypt.cpp b/src/encrypt.cpp
new file mode 100644
index 0000000..2b97057
--- /dev/null
+++ b/src/encrypt.cpp
@@ -0,0 +1,12 @@
+#include "pch.h"
+
+
+// [[Rcpp::export]]
+String encrypt_password(String password, String user) {
+ char* encrypted = PQencryptPassword(password.get_cstring(), user.get_cstring());
+
+ String copy(encrypted);
+ PQfreemem(encrypted);
+
+ return copy;
+}
diff --git a/src/integer64.h b/src/integer64.h
new file mode 100644
index 0000000..47f9b23
--- /dev/null
+++ b/src/integer64.h
@@ -0,0 +1,12 @@
+#ifndef RPOSTGRES_INTEGER64_H
+#define RPOSTGRES_INTEGER64_H
+
+#define INT64SXP REALSXP
+
+#define NA_INTEGER64 (0x8000000000000000)
+
+inline int64_t* INTEGER64(SEXP x) {
+ return reinterpret_cast(REAL(x));
+}
+
+#endif // RPOSTGRES_INTEGER64_H
diff --git a/src/logging.cpp b/src/logging.cpp
new file mode 100644
index 0000000..672619f
--- /dev/null
+++ b/src/logging.cpp
@@ -0,0 +1,8 @@
+#include "pch.h"
+#include
+
+
+// [[Rcpp::export]]
+void init_logging(const std::string& log_level) {
+ plog::init_r(log_level);
+}
diff --git a/src/pch.h b/src/pch.h
new file mode 100644
index 0000000..9b640c2
--- /dev/null
+++ b/src/pch.h
@@ -0,0 +1,6 @@
+#include
+#include
+
+#include
+
+using namespace Rcpp;
diff --git a/src/result.cpp b/src/result.cpp
new file mode 100644
index 0000000..b8624a9
--- /dev/null
+++ b/src/result.cpp
@@ -0,0 +1,63 @@
+#include "pch.h"
+#include "RPostgres_types.h"
+
+
+// [[Rcpp::export]]
+XPtr result_create(XPtr con, std::string sql, bool is_statement = false) {
+ (void)is_statement;
+ DbResult* res = new DbResult(*con, sql);
+ return XPtr(res, true);
+}
+
+// [[Rcpp::export]]
+void result_release(XPtr res) {
+ res.release();
+}
+
+// [[Rcpp::export]]
+bool result_valid(XPtr res_) {
+ DbResult* res = res_.get();
+ return res != NULL && res->active();
+}
+
+// [[Rcpp::export]]
+List result_fetch(DbResult* res, const int n) {
+ return res->fetch(n);
+}
+
+// [[Rcpp::export]]
+void result_bind(DbResult* res, List params) {
+ res->bind(params);
+}
+
+// [[Rcpp::export]]
+bool result_has_completed(DbResult* res) {
+ return res->complete();
+}
+
+// [[Rcpp::export]]
+int result_rows_fetched(DbResult* res) {
+ return res->n_rows_fetched();
+}
+
+// [[Rcpp::export]]
+int result_rows_affected(DbResult* res) {
+ return res->n_rows_affected();
+}
+
+// [[Rcpp::export]]
+List result_column_info(DbResult* res) {
+ return res->get_column_info();
+}
+
+namespace Rcpp {
+
+template<>
+DbResult* as(SEXP x) {
+ DbResult* result = (DbResult*)(R_ExternalPtrAddr(x));
+ if (!result)
+ stop("Invalid result set");
+ return result;
+}
+
+}
diff --git a/src/win32/timegm.c b/src/win32/timegm.c
new file mode 100644
index 0000000..3bdbe13
--- /dev/null
+++ b/src/win32/timegm.c
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 1997 Kungliga Tekniska Högskolan
+ * (Royal Institute of Technology, Stockholm, Sweden).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the Institute nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+
+
+#include
+
+#if defined(WIN32) && !defined(_WIN64)
+
+static int
+is_leap(unsigned y) {
+ y += 1900;
+ return (y % 4) == 0 && ((y % 100) != 0 || (y % 400) == 0);
+}
+
+time_t
+ _mkgmtime32(struct tm *tm) {
+ static const unsigned ndays[2][12] = {
+ {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
+ {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
+ };
+ time_t res = 0;
+ int i;
+
+ for (i = 70; i < tm->tm_year; ++i)
+ res += is_leap(i) ? 366 : 365;
+
+ for (i = 0; i < tm->tm_mon; ++i)
+ res += ndays[is_leap(tm->tm_year)][i];
+ res += tm->tm_mday - 1;
+ res *= 24;
+ res += tm->tm_hour;
+ res *= 60;
+ res += tm->tm_min;
+ res *= 60;
+ res += tm->tm_sec;
+ return res;
+}
+
+#else
+
+void dummy_to_prevent_empty_unit_warning(){}
+
+#endif /* WIN32 */
+
+
diff --git a/tests/testthat.R b/tests/testthat.R
new file mode 100644
index 0000000..b5bf92a
--- /dev/null
+++ b/tests/testthat.R
@@ -0,0 +1,4 @@
+library(testthat)
+library(RPostgres)
+
+test_check("RPostgres")
diff --git a/tests/testthat/helper-DBItest.R b/tests/testthat/helper-DBItest.R
new file mode 100644
index 0000000..334d8ad
--- /dev/null
+++ b/tests/testthat/helper-DBItest.R
@@ -0,0 +1,15 @@
+library(DBI)
+
+DBItest::make_context(
+ Postgres(),
+ NULL,
+ name = "RPostgres",
+ tweaks = DBItest::tweaks(
+ placeholder_pattern = "$1",
+ date_cast = function(x) paste0("date '", x, "'"),
+ time_cast = function(x) paste0("time '", x, "'"),
+ timestamp_cast = function(x) paste0("timestamp '", x, "'"),
+ is_null_check = function(x) paste0("(", x, "::text IS NULL)"),
+ blob_cast = function(x) paste0("(", x, "::bytea)")
+ )
+)
diff --git a/tests/testthat/helper-astyle.R b/tests/testthat/helper-astyle.R
new file mode 100644
index 0000000..17c0bd1
--- /dev/null
+++ b/tests/testthat/helper-astyle.R
@@ -0,0 +1,33 @@
+vcapply <- function(X, FUN, ..., USE.NAMES = TRUE) {
+ vapply(X = X, FUN = FUN, FUN.VALUE = character(1L), ..., USE.NAMES = USE.NAMES)
+}
+
+astyle <- function(extra_args = character()) {
+ astyle_cmd <- "astyle"
+ if (Sys.which(astyle_cmd) == "") {
+ skip("astyle not found")
+ }
+ if (!requireNamespace("rprojroot", quietly = TRUE)) {
+ skip("rprojroot not installed")
+ }
+
+ astyle_args <- c(
+ "-n",
+ "--indent=spaces=2",
+ "--unpad-paren",
+ "--pad-header",
+ "--pad-oper",
+ "--min-conditional-indent=0",
+ "--align-pointer=type",
+ "--align-reference=type"
+ )
+
+ src_path <- rprojroot::find_package_root_file("src")
+ src_files <- dir(src_path, "[.](?:cpp|h)$", recursive = TRUE, full.names = TRUE)
+ astyle_files <- grep("(?:RcppExports[.](?:cpp|h)|static_assert[.]h)", src_files, value = TRUE, invert = TRUE)
+ output <- system2(astyle_cmd, c(astyle_args, astyle_files, extra_args), stdout = TRUE, stderr = TRUE)
+ unchanged <- grepl("^Unchanged", output)
+ if (any(!unchanged)) {
+ warning(paste(output[!unchanged], collapse = "\n"))
+ }
+}
diff --git a/tests/testthat/helper-with_database_connection.R b/tests/testthat/helper-with_database_connection.R
new file mode 100644
index 0000000..d5ac8f3
--- /dev/null
+++ b/tests/testthat/helper-with_database_connection.R
@@ -0,0 +1,10 @@
+#' Execute an R expression with access to a database connection.
+#'
+#' @param expr expression. Any R expression.
+#' @param con PqConnection. A database connection, by default.
+#' \code{dbConnect(RPostgres::Postgres())}.
+#' @return the return value of the evaluated \code{expr}.
+with_database_connection <- function(expr, con = postgresDefault()) {
+ context <- list2env(list(con = con), parent = parent.frame())
+ eval(substitute(expr), envir = context)
+}
diff --git a/tests/testthat/helper-with_table.R b/tests/testthat/helper-with_table.R
new file mode 100644
index 0000000..fa22915
--- /dev/null
+++ b/tests/testthat/helper-with_table.R
@@ -0,0 +1,10 @@
+#' Run an expression that creates and touches a table, then clean up.
+#'
+#' @param con PqConnection. The database connection.
+#' @param tbl character. The table name.
+#' @param expr expression. The R expression to execute.
+#' @return the return value of the \code{expr}.
+with_table <- function(con, tbl, expr) {
+ on.exit(DBI::dbRemoveTable(con, tbl), add = TRUE)
+ force(expr)
+}
diff --git a/tests/testthat/helper-without_rownames.R b/tests/testthat/helper-without_rownames.R
new file mode 100644
index 0000000..93bb356
--- /dev/null
+++ b/tests/testthat/helper-without_rownames.R
@@ -0,0 +1,4 @@
+without_rownames <- function(df) {
+ row.names(df) <- NULL
+ df
+}
diff --git a/tests/testthat/test-DBItest.R b/tests/testthat/test-DBItest.R
new file mode 100644
index 0000000..b0a4b17
--- /dev/null
+++ b/tests/testthat/test-DBItest.R
@@ -0,0 +1,12 @@
+if (postgresHasDefault() && identical(Sys.getenv("NOT_CRAN"), "true")) {
+
+DBItest::test_all(c(
+ # deliberately skipped, not required with upcoming version of DBI
+ "get_info_driver",
+ "get_info_connection",
+ "get_info_result",
+
+ NULL
+))
+
+}
diff --git a/tests/testthat/test-bigint.R b/tests/testthat/test-bigint.R
new file mode 100644
index 0000000..63b51f4
--- /dev/null
+++ b/tests/testthat/test-bigint.R
@@ -0,0 +1,22 @@
+context("bigint")
+
+test_that("integer", {
+ con <- postgresDefault(bigint = "integer")
+ on.exit(dbDisconnect(con))
+
+ expect_identical(dbGetQuery(con, "SELECT COUNT(*) FROM (SELECT 1) A")[[1]], 1L)
+})
+
+test_that("numeric", {
+ con <- postgresDefault(bigint = "numeric")
+ on.exit(dbDisconnect(con))
+
+ expect_identical(dbGetQuery(con, "SELECT COUNT(*) FROM (SELECT 1) A")[[1]], 1.0)
+})
+
+test_that("character", {
+ con <- postgresDefault(bigint = "character")
+ on.exit(dbDisconnect(con))
+
+ expect_identical(dbGetQuery(con, "SELECT COUNT(*) FROM (SELECT 1) A")[[1]], "1")
+})
diff --git a/tests/testthat/test-data-type.R b/tests/testthat/test-data-type.R
new file mode 100644
index 0000000..2b983db
--- /dev/null
+++ b/tests/testthat/test-data-type.R
@@ -0,0 +1,12 @@
+context("dbDataType")
+
+# Taken from DBI
+test_that("dbDataType works on a data frame", {
+ con <- postgresDefault()
+ on.exit(dbDisconnect(con))
+
+ df <- data.frame(x = 1:10, y = 1:10 / 2)
+ types <- dbDataType(con, df)
+
+ expect_equal(types, c(x = "INTEGER", y = "REAL"))
+})
diff --git a/tests/testthat/test-dbConnect.R b/tests/testthat/test-dbConnect.R
new file mode 100644
index 0000000..b210cb0
--- /dev/null
+++ b/tests/testthat/test-dbConnect.R
@@ -0,0 +1,53 @@
+context("Connection")
+
+test_that("querying closed connection throws error", {
+ db <- postgresDefault()
+ dbDisconnect(db)
+ expect_error(dbSendQuery(db, "select * from foo"), "not valid")
+})
+
+test_that("warn if previous result set is invalidated", {
+ con <- postgresDefault()
+ on.exit(dbDisconnect(con))
+
+ rs1 <- dbSendQuery(con, "SELECT 1 + 1")
+
+ expect_warning(rs2 <- dbSendQuery(con, "SELECT 1 + 1"), "Cancelling previous query")
+ expect_false(dbIsValid(rs1))
+
+ dbClearResult(rs2)
+})
+
+test_that("no warning if previous result set is closed", {
+ con <- postgresDefault()
+ on.exit(dbDisconnect(con))
+
+ rs1 <- dbSendQuery(con, "SELECT 1 + 1")
+ dbClearResult(rs1)
+
+ expect_warning(rs2 <- dbSendQuery(con, "SELECT 1 + 1"), NA)
+ dbClearResult(rs2)
+})
+
+test_that("warning if close connection with open results", {
+ con <- postgresDefault()
+
+ rs1 <- dbSendQuery(con, "SELECT 1 + 1")
+
+ expect_warning(dbDisconnect(con), "still in use")
+})
+
+test_that("passing other options parameters", {
+ con <- postgresDefault(application_name = "apple")
+ on.exit(dbDisconnect(con))
+
+ pid <- dbGetInfo(con)$pid
+ r <- dbGetQuery(con, "SELECT application_name FROM pg_stat_activity WHERE pid=$1",
+ list(pid))
+ expect_identical(r$application_name, "apple")
+})
+
+test_that("error if passing unkown parameters", {
+ skip_on_cran()
+ expect_error(dbConnect(Postgres(), fruit = "apple"), 'invalid connection option "fruit"')
+})
diff --git a/tests/testthat/test-dbGetQuery.R b/tests/testthat/test-dbGetQuery.R
new file mode 100644
index 0000000..873d9c0
--- /dev/null
+++ b/tests/testthat/test-dbGetQuery.R
@@ -0,0 +1,57 @@
+context("dbGetQuery")
+
+test_that("special characters work", {
+ con <- postgresDefault()
+
+ angstrom <- enc2utf8("\\u00e5")
+
+ dbExecute(con, "CREATE TEMPORARY TABLE test1 (x TEXT)")
+ dbExecute(con, "INSERT INTO test1 VALUES ('\\u00e5')")
+
+ expect_equal(dbGetQuery(con, "SELECT * FROM test1")$x, angstrom)
+ expect_equal(dbGetQuery(con, "SELECT * FROM test1 WHERE x = '\\u00e5'")$x,
+ angstrom)
+})
+
+
+# Not generic enough for DBItest
+test_that("JSONB format is recognized", {
+ con <- postgresDefault()
+
+ n_json <- dbGetQuery(con, "SELECT count(*) FROM pg_type WHERE typname = 'jsonb' AND typtype = 'b'")[[1]]
+ if (as.integer(n_json) == 0) skip("No jsonb type installed")
+
+ jsonb <- '{\"name\": \"mike\"}'
+
+ dbExecute(con, "CREATE TEMPORARY TABLE test2 (data JSONB)")
+ dbExecute(con, paste0("INSERT INTO test2(data) values ('", jsonb, "');"))
+
+ expect_warning(
+ expect_equal(dbGetQuery(con, "SELECT * FROM test2")$data, jsonb),
+ NA
+ )
+
+ dbDisconnect(con)
+})
+
+
+test_that("uuid format is recognized", {
+ con <- postgresDefault()
+
+ dbExecute(con, "CREATE TEMPORARY TABLE fuutab
+ (
+ fuu UUID,
+ name VARCHAR(255) NOT NULL
+ );")
+
+ uuid <- "c44352c0-72bd-11e5-a7f3-0002a5d5c51b"
+
+ dbExecute(con, paste0("INSERT INTO fuutab(fuu, name) values ('", uuid, "', 'bob');"))
+
+ expect_warning(
+ expect_equal(dbGetQuery(con, "SELECT * FROM fuutab")$fuu, uuid),
+ NA
+ )
+
+ dbDisconnect(con)
+})
diff --git a/tests/testthat/test-dbWriteTable.R b/tests/testthat/test-dbWriteTable.R
new file mode 100644
index 0000000..fe645a7
--- /dev/null
+++ b/tests/testthat/test-dbWriteTable.R
@@ -0,0 +1,89 @@
+context("dbWriteTable")
+
+if (postgresHasDefault()) {
+
+with_database_connection({
+ describe("Writing to the database", {
+ test_that("writing to a database table is successful", {
+ with_table(con, "beaver2", {
+ dbWriteTable(con, "beaver2", beaver2, temporary = TRUE)
+ expect_equal(dbReadTable(con, "beaver2"), beaver2)
+ })
+ })
+
+ test_that("writing to a database table with character features is successful", {
+ with_table(con, "iris", {
+ iris2 <- transform(iris, Species = as.character(Species))
+ dbWriteTable(con, "iris", iris2, temporary = TRUE)
+ expect_equal(dbReadTable(con, "iris"), iris2)
+ })
+ })
+ })
+
+ describe("Appending to the database", {
+ test_that("append to a database table is successful", {
+ with_table(con, "beaver2", {
+ dbWriteTable(con, "beaver2", beaver2, temporary = TRUE)
+ dbWriteTable(con, "beaver2", beaver2, append = TRUE, temporary = TRUE)
+ expect_equal(dbReadTable(con, "beaver2"), rbind(beaver2, beaver2))
+ })
+ })
+
+ test_that("append to a database table with character features is successful", {
+ with_table(con, "iris", {
+ iris2 <- transform(iris, Species = as.character(Species))
+ dbWriteTable(con, "iris", iris2, temporary = TRUE)
+ dbWriteTable(con, "iris", iris2, append = TRUE, temporary = TRUE)
+ expect_equal(dbReadTable(con, "iris"), rbind(iris2, iris2))
+ })
+ })
+ })
+
+ describe("Usage of the field.types argument", {
+ test_that("New table creation respects the field.types argument", {
+ with_table(con, "iris", {
+ iris2 <- transform(
+ iris,
+ Petal.Width = as.integer(Petal.Width),
+ Species = as.character(Species)
+ )
+ field.types <- c("real", "double precision", "numeric", "bigint", "text")
+
+ dbWriteTable(con, "iris", iris2, field.types = field.types, temporary = TRUE)
+
+ iris3 <- transform(
+ iris2,
+ Petal.Width = bit64::as.integer64(Petal.Width)
+ )
+ expect_equal(dbReadTable(con, "iris"), iris3)
+
+ # http://stackoverflow.com/questions/2146705/select-datatype-of-the-field-in-postgres
+ types <- DBI::dbGetQuery(con,
+ paste("select column_name, data_type from information_schema.columns ",
+ "where table_name = 'iris'"))
+ expected <- data.frame(column_name = colnames(iris2),
+ data_type = field.types, stringsAsFactors = FALSE)
+ types <- without_rownames(types[order(types$column_name), ])
+ expected <- without_rownames(expected[order(expected$column_name), ])
+
+ expect_equal(types, expected)
+ })
+ })
+
+ test_that("Appending fails when using the field.types argument", {
+ with_table(con, "iris", {
+ iris2 <- transform(iris, Petal.Width = as.integer(Petal.Width),
+ Species = as.character(Species))
+ field.types <- c("real", "double precision", "numeric", "bigint", "text")
+
+ dbWriteTable(con, "iris", iris2, field.types = field.types, temporary = TRUE)
+ expect_error(
+ dbWriteTable(con, "iris", iris2, field.types = field.types, append = TRUE, temporary = TRUE),
+ "field[.]types"
+ )
+ })
+ })
+ })
+})
+
+}
diff --git a/tests/testthat/test-encoding.R b/tests/testthat/test-encoding.R
new file mode 100644
index 0000000..ef8f987
--- /dev/null
+++ b/tests/testthat/test-encoding.R
@@ -0,0 +1,23 @@
+context("Encoding")
+
+# Specific to RPostgres
+test_that("NAs encoded as NULLs", {
+ expect_equal(encode_vector(NA), "\\N")
+ expect_equal(encode_vector(NA_integer_), "\\N")
+ expect_equal(encode_vector(NA_real_), "\\N")
+ expect_equal(encode_vector(NA_character_), "\\N")
+})
+
+# Specific to RPostgres
+test_that("special floating point values encoded correctly", {
+ expect_equal(encode_vector(NaN), "NaN")
+ expect_equal(encode_vector(Inf), "Infinity")
+ expect_equal(encode_vector(-Inf), "-Infinity")
+})
+
+# Specific to RPostgres
+test_that("special string values are escaped", {
+ expect_equal(encode_vector("\n"), "\\n")
+ expect_equal(encode_vector("\r"), "\\r")
+ expect_equal(encode_vector("\b"), "\\b")
+})
diff --git a/tools/winlibs.R b/tools/winlibs.R
new file mode 100644
index 0000000..7f29ebf
--- /dev/null
+++ b/tools/winlibs.R
@@ -0,0 +1,8 @@
+# Build against static libpq
+if(!file.exists("../windows/libpq-9.5.2/include/libpq-fe.h")){
+ if(getRversion() < "3.3.0") setInternet2()
+ download.file("https://github.com/rwinlib/libpq/archive/v9.5.2.zip", "lib.zip", quiet = TRUE)
+ dir.create("../windows", showWarnings = FALSE)
+ unzip("lib.zip", exdir = "../windows")
+ unlink("lib.zip")
+}