Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Date, POSIXct and hms types #333

Merged
merged 16 commits into from
Jan 19, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,12 @@ Suggests:
gh,
knitr,
rmarkdown,
hms,
rvest,
testthat,
xml2
LinkingTo:
BH,
BH (>= 1.75.0-0),
plogr (>= 0.2.0),
Rcpp
VignetteBuilder:
Expand Down
4 changes: 2 additions & 2 deletions R/RcppExports.R
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# Generated by using Rcpp::compileAttributes() -> do not edit by hand
# Generator token: 10BE3573-1514-4C36-9D1C-5A225CD40393

connection_connect <- function(path, allow_ext, flags, vfs = "") {
.Call(`_RSQLite_connection_connect`, path, allow_ext, flags, vfs)
connection_connect <- function(path, allow_ext, flags, vfs = "", with_alt_types = FALSE) {
.Call(`_RSQLite_connection_connect`, path, allow_ext, flags, vfs, with_alt_types)
}

connection_valid <- function(con_) {
Expand Down
3 changes: 2 additions & 1 deletion R/SQLiteConnection.R
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ setClass("SQLiteConnection",
flags = "integer",
vfs = "character",
ref = "environment",
bigint = "character"
bigint = "character",
extended_types = "logical"
)
)

Expand Down
31 changes: 28 additions & 3 deletions R/connect.R
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,25 @@ SQLITE_RWC <- bitwOr(bitwOr(0x00000004L, 0x00000002L), 0x00000040L)
#' @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 extended_types When `TRUE` columns of type `DATE`, `DATETIME` /
#' `TIMESTAMP`, and `TIME` are mapped to corresponding R-classes, c.f. below
#' for details. Defaults to `FALSE`.
#'
#' @return `dbConnect()` returns an object of class [SQLiteConnection-class].
#'
#' @section Extended Types:
#' When parameter `extended_types = TRUE` date and time columns are directly
#' mapped to corresponding R-types. How exactly depends on whether the actual
#' value is a number or a string:
#'
#' | *Column type* | *Value is numeric* | *Value is Text* | *R-class* |
#' | ------------- | ------------------ | --------------- | --------- |
#' | DATE | Count of days since 1970-01-01 | YMD formatted string (e.g. 2020-01-23) | `Date` |
#' | TIME | Count of (fractional) seconds | HMS formatted string (e.g. 12:34:56) | `hms` (and `difftime`) |
#' | DATETIME / TIMESTAMP | Count of (fractional) seconds since midnight 1970-01-01 UTC | DATE and TIME as above separated by a space | `POSIXct` with time zone UTC |
#'
#' If a value cannot be mapped an `NA` is returned in its place with a warning.
#'
#' @aliases SQLITE_RWC SQLITE_RW SQLITE_RO
#' @export
#' @rdname SQLite
Expand Down Expand Up @@ -114,7 +131,8 @@ setMethod("dbConnect", "SQLiteDriver",
function(drv, dbname = "", ..., loadable.extensions = TRUE,
default.extensions = loadable.extensions, cache_size = NULL,
synchronous = "off", flags = SQLITE_RWC, vfs = NULL,
bigint = c("integer64", "integer", "numeric", "character")) {
bigint = c("integer64", "integer", "numeric", "character"),
extended_types = FALSE) {
stopifnot(length(dbname) == 1, !is.na(dbname))

if (!is_url_or_special_filename(dbname)) {
Expand All @@ -128,14 +146,21 @@ setMethod("dbConnect", "SQLiteDriver",

bigint <- match.arg(bigint)

extended_types <- isTRUE(extended_types)
if (extended_types) {
if (!requireNamespace("hms", quietly = TRUE)) {
stopc("Install the hms package for `extended_types = TRUE`.")
}
}
conn <- new("SQLiteConnection",
ptr = connection_connect(dbname, loadable.extensions, flags, vfs),
ptr = connection_connect(dbname, loadable.extensions, flags, vfs, extended_types),
dbname = dbname,
flags = flags,
vfs = vfs,
loadable.extensions = loadable.extensions,
ref = new.env(parent = emptyenv()),
bigint = bigint
bigint = bigint,
extended_types = extended_types
)

## experimental PRAGMAs
Expand Down
22 changes: 21 additions & 1 deletion man/SQLite.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/DbColumnStorage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,11 @@ SEXP DbColumnStorage::set_attribs_from_datatype(SEXP x, DATA_TYPE dt) {
case DT_TIME:
return new_hms(x);

case DT_DATETIME: {
Rcpp::RObject ro = Rcpp::RObject(x);
ro.attr("tzone") = "UTC";
return ro;
}
default:
return x;
}
Expand Down
10 changes: 8 additions & 2 deletions src/DbConnection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
#include "DbConnection.h"


DbConnection::DbConnection(const std::string& path, const bool allow_ext, const int flags, const std::string& vfs)
: pConn_(NULL) {
DbConnection::DbConnection(const std::string& path, const bool allow_ext, const int flags, const std::string& vfs, bool with_alt_types)
: pConn_(NULL),
with_alt_types_(with_alt_types) {

// Get the underlying database connection
int rc = sqlite3_open_v2(path.c_str(), &pConn_, flags, vfs.size() ? vfs.c_str() : NULL);
Expand Down Expand Up @@ -71,3 +72,8 @@ void DbConnection::disconnect() {
sqlite3_close_v2(pConn_);
pConn_ = NULL;
}


bool DbConnection::with_alt_types() const {
return with_alt_types_;
}
4 changes: 3 additions & 1 deletion src/DbConnection.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class DbConnection : boost::noncopyable {
public:
// Create a new connection handle
DbConnection(const std::string& path, bool allow_ext,
int flags, const std::string& vfs = "");
int flags, const std::string& vfs = "", bool with_alt_types = false);
~DbConnection();

public:
Expand All @@ -47,8 +47,10 @@ class DbConnection : boost::noncopyable {
// Disconnects from a database
void disconnect();

bool with_alt_types() const;
private:
sqlite3* pConn_;
const bool with_alt_types_;
};

#endif // __RSQLITE_SQLITE_CONNECTION__
9 changes: 5 additions & 4 deletions src/RcppExports.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,17 @@
using namespace Rcpp;

// connection_connect
XPtr<DbConnectionPtr> connection_connect(const std::string& path, const bool allow_ext, const int flags, const std::string& vfs);
RcppExport SEXP _RSQLite_connection_connect(SEXP pathSEXP, SEXP allow_extSEXP, SEXP flagsSEXP, SEXP vfsSEXP) {
XPtr<DbConnectionPtr> connection_connect(const std::string& path, const bool allow_ext, const int flags, const std::string& vfs, bool with_alt_types);
RcppExport SEXP _RSQLite_connection_connect(SEXP pathSEXP, SEXP allow_extSEXP, SEXP flagsSEXP, SEXP vfsSEXP, SEXP with_alt_typesSEXP) {
BEGIN_RCPP
Rcpp::RObject rcpp_result_gen;
Rcpp::RNGScope rcpp_rngScope_gen;
Rcpp::traits::input_parameter< const std::string& >::type path(pathSEXP);
Rcpp::traits::input_parameter< const bool >::type allow_ext(allow_extSEXP);
Rcpp::traits::input_parameter< const int >::type flags(flagsSEXP);
Rcpp::traits::input_parameter< const std::string& >::type vfs(vfsSEXP);
rcpp_result_gen = Rcpp::wrap(connection_connect(path, allow_ext, flags, vfs));
Rcpp::traits::input_parameter< bool >::type with_alt_types(with_alt_typesSEXP);
rcpp_result_gen = Rcpp::wrap(connection_connect(path, allow_ext, flags, vfs, with_alt_types));
return rcpp_result_gen;
END_RCPP
}
Expand Down Expand Up @@ -213,7 +214,7 @@ END_RCPP
}

static const R_CallMethodDef CallEntries[] = {
{"_RSQLite_connection_connect", (DL_FUNC) &_RSQLite_connection_connect, 4},
{"_RSQLite_connection_connect", (DL_FUNC) &_RSQLite_connection_connect, 5},
{"_RSQLite_connection_valid", (DL_FUNC) &_RSQLite_connection_valid, 1},
{"_RSQLite_connection_release", (DL_FUNC) &_RSQLite_connection_release, 1},
{"_RSQLite_connection_copy_database", (DL_FUNC) &_RSQLite_connection_copy_database, 2},
Expand Down
110 changes: 100 additions & 10 deletions src/SqliteColumnDataSource.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,27 @@
#include "integer64.h"
#include "affinity.h"
#include <boost/limits.hpp>
#include <boost/date_time/gregorian/gregorian.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/algorithm/string.hpp>

SqliteColumnDataSource::SqliteColumnDataSource(sqlite3_stmt* stmt_, const int j_) :

SqliteColumnDataSource::SqliteColumnDataSource(sqlite3_stmt* stmt_, const int j_, bool with_alt_types_) :
DbColumnDataSource(j_),
stmt(stmt_)
stmt(stmt_),
with_alt_types(with_alt_types_)
{
}

DATA_TYPE SqliteColumnDataSource::get_data_type() const {

if (with_alt_types) {
DATA_TYPE decl_dt = get_decl_data_type();
if (decl_dt == DT_DATE || decl_dt == DT_DATETIME || decl_dt == DT_TIME) {
return decl_dt;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this mean that a declared data type overrides what's stored in the columns?

}
}

const int field_type = get_column_type();
switch (field_type) {
case SQLITE_INTEGER:
Expand Down Expand Up @@ -39,7 +52,7 @@ DATA_TYPE SqliteColumnDataSource::get_data_type() const {
}

DATA_TYPE SqliteColumnDataSource::get_decl_data_type() const {
return datatype_from_decltype(sqlite3_column_decltype(get_stmt(), get_j()));
return datatype_from_decltype(sqlite3_column_decltype(get_stmt(), get_j()), with_alt_types);
}

bool SqliteColumnDataSource::is_null() const {
Expand Down Expand Up @@ -80,13 +93,59 @@ SEXP SqliteColumnDataSource::fetch_blob() const {
}

double SqliteColumnDataSource::fetch_date() const {
// No such data type
return 0.0;

namespace bg = boost::gregorian;

int dt = get_column_type();

if (dt == SQLITE_TEXT) {
const char* dtstr = reinterpret_cast<const char*>(sqlite3_column_text(get_stmt(), get_j()));
double dateval;

try {
bg::date dt(bg::from_simple_string(dtstr));
bg::date_duration delta = dt - bg::date(1970, 1, 1);
dateval = static_cast<double>(delta.days());
} catch (...) {
Rcpp::warning("Unknown string format, NA is returned.");
dateval = NA_REAL;
}
return dateval;
} else if (dt == SQLITE_BLOB) {
Rcpp::warning("Cannot convert blob, NA is returned.");
return NA_REAL;
} else {
return static_cast<double>(sqlite3_column_int(get_stmt(), get_j()));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens if the underlying data is an integer or a blob?

}
}

double SqliteColumnDataSource::fetch_datetime_local() const {
// No such data type
return 0.0;

namespace bp = boost::posix_time;
namespace bg = boost::gregorian;
namespace bd = boost::date_time;

int dt = get_column_type();

if (dt == SQLITE_TEXT) {
const char* dtstr = reinterpret_cast<const char*>(sqlite3_column_text(get_stmt(), get_j()));
double dateval;
try {
bp::ptime dttm(bd::parse_delimited_time<bp::ptime>(dtstr, ' '));
bp::time_duration delta = dttm - bp::ptime(bg::date(1970, 1, 1), bp::seconds(0));
dateval = delta.total_microseconds() * 1e-6;
} catch (...) {
Rcpp::warning("Unknown string format, NA is returned.");
dateval = NA_REAL;
}
return dateval;
} else if (dt == SQLITE_BLOB) {
Rcpp::warning("Cannot convert blob, NA is returned.");
return NA_REAL;
} else {
return sqlite3_column_double(get_stmt(), get_j());
}

}

double SqliteColumnDataSource::fetch_datetime() const {
Expand All @@ -95,15 +154,46 @@ double SqliteColumnDataSource::fetch_datetime() const {
}

double SqliteColumnDataSource::fetch_time() const {
// No such data type
return 0.0;


namespace bp = boost::posix_time;

int dt = get_column_type();

if (dt == SQLITE_TEXT) {
const char *tmstr = reinterpret_cast<const char*>(sqlite3_column_text(get_stmt(), get_j()));
double dateval;
try {
bp::time_duration secs(bp::duration_from_string(tmstr));
dateval = secs.total_microseconds() * 1e-6;
} catch (...) {
Rcpp::warning("Unknown string format, NA is returned.");
dateval = NA_REAL;
}
return dateval;
} else if (dt == SQLITE_BLOB) {
Rcpp::warning("Cannot convert blob, NA is returned.");
return NA_REAL;
} else {
return sqlite3_column_double(get_stmt(), get_j());
}
}


DATA_TYPE SqliteColumnDataSource::datatype_from_decltype(const char* decl_type) {
DATA_TYPE SqliteColumnDataSource::datatype_from_decltype(const char* decl_type, bool with_alt_types) {
if (decl_type == NULL)
return DT_BOOL;

if (with_alt_types) {
if (boost::iequals(decl_type, "datetime") || boost::iequals(decl_type, "timestamp")) {
return DT_DATETIME;
} else if (boost::iequals(decl_type, "date")) {
return DT_DATE;
} else if (boost::iequals(decl_type, "time")) {
return DT_TIME;
}
}

char affinity = sqlite3AffinityType(decl_type);

switch (affinity) {
Expand Down
7 changes: 4 additions & 3 deletions src/SqliteColumnDataSource.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@

class SqliteColumnDataSource : public DbColumnDataSource {
sqlite3_stmt* stmt;

const bool with_alt_types;

public:
SqliteColumnDataSource(sqlite3_stmt* stmt, const int j);
SqliteColumnDataSource(sqlite3_stmt* stmt, const int j, bool with_alt_types);

public:
virtual DATA_TYPE get_data_type() const;
Expand All @@ -28,7 +29,7 @@ class SqliteColumnDataSource : public DbColumnDataSource {
virtual double fetch_time() const;

private:
static DATA_TYPE datatype_from_decltype(const char* decl_type);
static DATA_TYPE datatype_from_decltype(const char* decl_type, bool with_alt_types);

private:
sqlite3_stmt* get_stmt() const;
Expand Down
Loading