From 907d8487916d23256c0fcd2da5039887d99b0519 Mon Sep 17 00:00:00 2001 From: avsdev-cw Date: Fri, 2 Oct 2020 13:12:03 +0100 Subject: [PATCH] Added R methods and their c counterparts for connection and database management --- NAMESPACE | 23 ++++ R/Defines.R | 30 +++++ R/Manage.R | 226 ++++++++++++++++++++++++++++++++++ R/RMODB.R | 20 ++++ cleanup | 2 + configure | 81 +++++++++++++ man/modb_conn_ref.Rd | 26 ++++ man/modb_connect.Rd | 42 +++++++ man/modb_connectionInfo.Rd | 47 ++++++++ man/modb_disconnect.Rd | 14 +++ man/rmodb.Rd | 16 +++ src/Makevars.in | 7 ++ src/R_list_item.c | 22 ++++ src/R_list_item.h | 8 ++ src/R_modb_manage.c | 240 +++++++++++++++++++++++++++++++++++++ src/R_modb_manage.h | 20 ++++ src/initR.c | 22 ++++ src/modb_types.c | 25 ---- src/modb_types.h | 4 +- 19 files changed, 847 insertions(+), 28 deletions(-) create mode 100644 R/Defines.R create mode 100644 R/Manage.R create mode 100644 R/RMODB.R create mode 100755 cleanup create mode 100755 configure create mode 100644 man/modb_conn_ref.Rd create mode 100644 man/modb_connect.Rd create mode 100644 man/modb_connectionInfo.Rd create mode 100644 man/modb_disconnect.Rd create mode 100644 man/rmodb.Rd create mode 100644 src/Makevars.in create mode 100644 src/R_list_item.c create mode 100644 src/R_list_item.h create mode 100644 src/R_modb_manage.c create mode 100644 src/R_modb_manage.h create mode 100644 src/initR.c diff --git a/NAMESPACE b/NAMESPACE index 6ae9268..e6a0105 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -1,2 +1,25 @@ # Generated by roxygen2: do not edit by hand +export(TYPE_BLOB) +export(TYPE_BOOL) +export(TYPE_DOUBLE) +export(TYPE_FLOAT) +export(TYPE_INT16) +export(TYPE_INT32) +export(TYPE_INT64) +export(TYPE_INT8) +export(TYPE_RAW) +export(TYPE_STRING) +export(TYPE_TIMESTAMP) +export(TYPE_UINT16) +export(TYPE_UINT32) +export(TYPE_UINT64) +export(TYPE_UINT8) +export(modb_conn_ref) +export(modb_connect) +export(modb_connectionExists) +export(modb_connectionId) +export(modb_connectionInfo) +export(modb_connectionName) +export(modb_disconnect) +useDynLib(rmodb, .registration = TRUE, .fixes = "c_") diff --git a/R/Defines.R b/R/Defines.R new file mode 100644 index 0000000..cf9b0bd --- /dev/null +++ b/R/Defines.R @@ -0,0 +1,30 @@ +#' @export +TYPE_RAW <- bitwShiftL(0, 0) +#' @export +TYPE_BOOL <- bitwShiftL(1, 0) +#' @export +TYPE_INT8 <- bitwShiftL(1, 1) +#' @export +TYPE_UINT8 <- bitwShiftL(1, 2) +#' @export +TYPE_INT16 <- bitwShiftL(1, 3) +#' @export +TYPE_UINT16 <- bitwShiftL(1, 4) +#' @export +TYPE_INT32 <- bitwShiftL(1, 5) +#' @export +TYPE_UINT32 <- bitwShiftL(1, 6) +#' @export +TYPE_INT64 <- bitwShiftL(1, 7) +#' @export +TYPE_UINT64 <- bitwShiftL(1, 8) +#' @export +TYPE_FLOAT <- bitwShiftL(1, 9) +#' @export +TYPE_DOUBLE <- bitwShiftL(1, 10) +#' @export +TYPE_STRING <- bitwShiftL(1, 11) +#' @export +TYPE_BLOB <- bitwShiftL(1, 12) +#' @export +TYPE_TIMESTAMP <- bitwShiftL(1, 13) \ No newline at end of file diff --git a/R/Manage.R b/R/Manage.R new file mode 100644 index 0000000..619668a --- /dev/null +++ b/R/Manage.R @@ -0,0 +1,226 @@ + +# Connection management -------------------------------------------------------- + +#' MODB Connection References +#' +#' \code{modb_conn_ref} checks for and/or validates parameters that may be a +#' connection reference. Connections in MODB may be named on creation or must +#' be referenced by the unique ID that is returned by the +#' \link{\code{modb_connect}} method. +#' +#' @param conn_id Integer. The ID of the connection returned by modb_connect +#' @param conn_name String. The name provided when creating a connection. +#' @param conn_ref Expected to be either a conn_id or a conn_name +#' @param args list(...) Arguments passed to a calling function in the va scope. +#' @seealso \link{\code{modb_connect}} +#' @export +modb_conn_ref <- function(conn_id, conn_name, conn_ref, args = NULL) { + if (length(args) != 0) { + if ("conn_ref" %in% ls(args)) { + conn_ref <- args$conn_ref + } + if ("conn_id" %in% ls(args)) { + conn_id <- args$conn_id + } + if ("conn_name" %in% ls(args)) { + conn_name <- args$conn_name + } + } + + if (!missing(conn_ref)) { + if (checkmate::test_string(conn_ref)) { + return(conn_ref) + } else if (checkmate::test_int(conn_ref)) { + return(as.integer(conn_ref)) + } else { + stop("invalid connection ref provided (must be string(name) or int(id))") + } + } else if (!missing(conn_id)) { + if (!checkmate::test_null(conn_id)) { + checkmate::assert_int(conn_id) + return(as.integer(conn_id)) + } + } else if (!missing(conn_name)) { + if (!checkmate::test_null(conn_name)) { + checkmate::assert_string(conn_name) + return(conn_name) + } + } + + stop("one of the arguments \"conn_ref\", \"conn_id\" or \"conn_name\" must be provided") +} + + +#' MODB Connections +#' +#' \code{modb_connect} creates a new database connection and attmepts to open a +#' connection to a database with the details provided. +#' +#' @param username String. Username to use when connecting to the database. +#' @param password String. Password to use when connecting to the database. +#' @param database String. The database to use. +#' @param host String. The hostname or IP address to connect to. +#' Either host & port or socket may be used +#' @param port Integer. The port number to connect on. +#' @param socket String. The socket path to connect to, +#' e.g. /var/run/mysqld/mysqld.sock +#' @param name String. A name for the connection which can be used to identify +#' the connection later on. Alternatively the id returned +#' can be used. +#' @return The id of the connection +#' @export +modb_connect <- function(username, password, database, + host = "localhost", port = 3306, + socket = NULL, conn_name = NULL) { + checkmate::assert_string(username) + checkmate::assert_string(password) + checkmate::assert_string(database) + + if (checkmate::test_null(socket)) { + checkmate::assert_string(host) + checkmate::assert_int(port, lower = 1, upper = 65535) + res <- .Call( + c_modb_connectToHost, + conn_name, + host, + as.integer(port), + username, + password, + database + ) + } else { + res <- .Call( + c_modb_connectToSocket, conn_name, socket, username, password, database + ) + } + + if (res < 0) { + stop("failed to create connection") + } + + return(res) +} + +#' MODB Connections +#' +#' modb_disconnect closes a database connection and cleans up the instance +#' +#' @param ... conn_id, conn_name or conn_ref required. See \link{modb_conn_ref} +#' @export +modb_disconnect <- function(...) { + res <- .Call(c_modb_disconnect, modb_conn_ref(args = list(...))) + return(invisible(res)) +} + +#' MODB Connections +#' +#' Fetches information on a connection such as the last query, insert id and +#' number of queries run +#' +#' @param ... conn_id, conn_name or conn_ref required. See \link{modb_conn_ref} +#' @return The details of the connection as a list. +#' @export +modb_connectionInfo <- function(stop_on_error = TRUE, ...) { + conn_ref <- modb_conn_ref(args = list(...)) + utils::str(conn_ref) + res <- .Call(c_modb_connectionInfo, conn_ref) + + if (length(res) == sum(is.na(res))) { + if (stop_on_error) { + stop("invalid connection reference") + } else { + warning("invalid connection reference") + return(NA) + } + } + + return(res) +} + +#' @describeIn modb_connectionInfo Determines if a connection with the name or +#' id provided exists +#' @param ... conn_id, conn_name or conn_ref required. See \link{modb_conn_ref} +#' @return TRUE if the connection exists, FALSE if not. +#' @export +modb_connectionExists <- function(...) { + res <- .Call(c_modb_connectionInfo, modb_conn_ref(args = list(...))) + return(!(length(res) == sum(is.na(res)))) +} + +#' @describeIn modb_connectionInfo Finds a connection id via the name +#' @param conn_name String. The name provided when creating a connection. +#' @return The ID of the connection. +#' @export +modb_connectionId <- function(conn_name, stop_on_error = TRUE) { + info <- modb_connectionInfo(stop_on_error, conn_ref = conn_name) + if (length(info) == sum(is.na(info))) { + return(NA) + } + return(info$id) +} +#' @describeIn modb_connectionInfo Finds a connection name via the id +#' @param conn_id Integer. The id of the connection, returned by modb_connect. +#' @return The name of the connection. +#' @export +modb_connectionName <- function(conn_id, stop_on_error = TRUE) { + info <- modb_connectionInfo(stop_on_error, conn_ref = conn_id) + if (length(info) == sum(is.na(info))) { + return(NA) + } + return(info$name) +} + + +# Database management ---------------------------------------------------------- + +#' @export +modb_exists <- function(modb_name, ...) { + conn_ref <- modb_conn_ref(args = list(...)) + res <- .Call(c_modb_exists, conn_ref, modb_name) + return(res) +} +#' @export +modb_create <- function(modb_name, extended_meta = NULL, ...) { + checkmate::assert_string(modb_name) + + # If a dataframe: data.frame(name = c(1, 2, 3), type = c('a', 'b', 'c'), nullable = c(1,0,1), stringsAsFactors=F) + # extended_meta %>% rowwise() %>% transmute(r = list(c("A" = A, "B" = B, "C" = C))) %>% first() + if (!checkmate::test_null(extended_meta)) { + for (idx in 1:length(extended_meta)) { + meta_col <- extended_meta[[idx]] + + if (!("name" %in% names(meta_col))) { + stop("missing name in extended_meta definition") + } else { + checkmate::assert_string(meta_col$name) + } + + if (!("type" %in% names(meta_col))) { + stop("missing type in extended_meta definition") + } else { + type <- meta_col$type + checkmate::assert_int(type, lower = 0, upper = TYPE_TIMESTAMP) + checkmate::assert_true(type == 1 || type %% 2 == 0) + meta_col$type <- as.integer(type) + } + + if (!("nullable" %in% names(meta_col))) { + meta_col$nullable = FALSE + } else { + checkmate::assert_logical(meta_col$nullable) + } + + extended_meta[[idx]] <- meta_col + } + } + + conn_ref <- modb_conn_ref(args = list(...)) + res <- .Call(c_modb_create, conn_ref, modb_name, extended_meta) + return(res) +} +#' @export +modb_destroy <- function(modb_name, ...) { + conn_ref <- modb_conn_ref(args = list(...)) + res <- .Call(c_modb_destroy, conn_ref, modb_name) + return(res) +} \ No newline at end of file diff --git a/R/RMODB.R b/R/RMODB.R new file mode 100644 index 0000000..d865cda --- /dev/null +++ b/R/RMODB.R @@ -0,0 +1,20 @@ +#' RMODB: Metadata-Object pair database +#' +#' RMODB implements a metadata-object database for storing R objects in +#' MySQL. R objects are serialized into a binary memory buffer which is then +#' written into a MySQL database along with metadata such as creation +#' timestamp, update timestamp, owner, title etc. The metadata fields are +#' customisable on creation of the database. the metadata fields are cleartext +#' and queryable allowing objects to be found and returned. On returning an +#' object, it is de-serialised using the reverseof the serialize to binary +#' memory process. +#' +#' @docType package +#' @name rmodb +#' @useDynLib rmodb, .registration = TRUE, .fixes = "c_" +NULL +#> NULL + +.onUnload <- function(nspath) { + # clear all connections +} \ No newline at end of file diff --git a/cleanup b/cleanup new file mode 100755 index 0000000..0e6f9c5 --- /dev/null +++ b/cleanup @@ -0,0 +1,2 @@ +#!/bin/sh +rm -f src/Makevars configure.log \ No newline at end of file diff --git a/configure b/configure new file mode 100755 index 0000000..b7b9d63 --- /dev/null +++ b/configure @@ -0,0 +1,81 @@ +#!/usr/bin/env bash +# Anticonf script by Jeroen Ooms (2020) +# The script will try 'mariadb_config' and 'mysql_config' to find required +# cflags and ldflags. Make sure this executable is in PATH when installing +# the package. Alternatively, you can set INCLUDE_DIR and LIB_DIR manually: +# R CMD INSTALL --configure-vars='INCLUDE_DIR=/.../include LIB_DIR=/.../lib' + +# Library settings +PKG_DEB_NAME="libmariadbclient-dev | libmariadb-client-lgpl-dev" +PKG_RPM_NAME="mariadb-connector-c-devel | mariadb-devel | mysql-devel" +PKG_CSW_NAME="mysql56_dev" +PKG_BREW_NAME="mariadb-connector-c" +PKG_TEST_HEADER="" +PKG_LIBS="-lmysqlclient" + +# Use mysql_config (on Solaris /opt/csw/bin must be in PATH) +if [ $(command -v mariadb_config) ]; then + PKGCONFIG_CFLAGS=$(mariadb_config --cflags) + PKGCONFIG_LIBS=$(mariadb_config --libs) +elif [ $(command -v mysql_config) ]; then + PKGCONFIG_CFLAGS=$(mysql_config --cflags) + PKGCONFIG_LIBS=$(mysql_config --libs) +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 "Found mysql_config cflags and libs!" + PKG_CFLAGS=${PKGCONFIG_CFLAGS} + PKG_LIBS=${PKGCONFIG_LIBS} + # Workaround for homebrew linkin bug + if [[ "$OSTYPE" == "darwin"* ]]; then + PKG_LIBS="-L/usr/local/opt/openssl/lib $PKG_LIBS" + fi +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 +fi + +# 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) + +# For debugging +echo "Using PKG_CFLAGS=$PKG_CFLAGS" +echo "Using PKG_LIBS=$PKG_LIBS" + +# Test configuration +echo "#include $PKG_TEST_HEADER" | ${CC} ${CPPFLAGS} ${PKG_CFLAGS} ${CFLAGS} -E -xc - >/dev/null 2> configure.log + +# Customize the error +if [ $? -ne 0 ]; then + echo "-----------------------------[ ANTICONF ]-----------------------------" + echo "Configure could not find suitable mysql/mariadb client library. Try installing:" + echo " * deb: $PKG_DEB_NAME (Debian, Ubuntu)" + echo " * rpm: $PKG_RPM_NAME (Fedora, CentOS, RHEL)" + echo " * csw: $PKG_CSW_NAME (Solaris)" + echo " * brew: $PKG_BREW_NAME (OSX)" + echo "If you already have a mysql client library installed, verify that either" + echo "mariadb_config or mysql_config is on your PATH. If these are unavailable" + echo "you can also set INCLUDE_DIR and LIB_DIR manually via:" + echo "R CMD INSTALL --configure-vars='INCLUDE_DIR=... LIB_DIR=...'" + echo "--------------------------[ ERROR MESSAGE ]----------------------------" + cat configure.log + echo "-----------------------------------------------------------------------" + exit 1 +fi + +# Write to Makevars +sed -e "s|@cflags@|$PKG_CFLAGS|" -e "s|@libs@|$PKG_LIBS|" src/Makevars.in > src/Makevars + +# Success +exit 0 diff --git a/man/modb_conn_ref.Rd b/man/modb_conn_ref.Rd new file mode 100644 index 0000000..181d63c --- /dev/null +++ b/man/modb_conn_ref.Rd @@ -0,0 +1,26 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/Manage.R +\name{modb_conn_ref} +\alias{modb_conn_ref} +\title{MODB Connection References} +\usage{ +modb_conn_ref(conn_id, conn_name, conn_ref, args = NULL) +} +\arguments{ +\item{conn_id}{Integer. The ID of the connection returned by modb_connect} + +\item{conn_name}{String. The name provided when creating a connection.} + +\item{conn_ref}{Expected to be either a conn_id or a conn_name} + +\item{args}{list(...) Arguments passed to a calling function in the va scope.} +} +\description{ +\code{modb_conn_ref} checks for and/or validates parameters that may be a +connection reference. Connections in MODB may be named on creation or must +be referenced by the unique ID that is returned by the +\link{\code{modb_connect}} method. +} +\seealso{ +\link{\code{modb_connect}} +} diff --git a/man/modb_connect.Rd b/man/modb_connect.Rd new file mode 100644 index 0000000..b4c0ed9 --- /dev/null +++ b/man/modb_connect.Rd @@ -0,0 +1,42 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/Manage.R +\name{modb_connect} +\alias{modb_connect} +\title{MODB Connections} +\usage{ +modb_connect( + username, + password, + database, + host = "localhost", + port = 3306, + socket = NULL, + conn_name = NULL +) +} +\arguments{ +\item{username}{String. Username to use when connecting to the database.} + +\item{password}{String. Password to use when connecting to the database.} + +\item{database}{String. The database to use.} + +\item{host}{String. The hostname or IP address to connect to. +Either host & port or socket may be used} + +\item{port}{Integer. The port number to connect on.} + +\item{socket}{String. The socket path to connect to, +e.g. /var/run/mysqld/mysqld.sock} + +\item{name}{String. A name for the connection which can be used to identify +the connection later on. Alternatively the id returned +can be used.} +} +\value{ +The id of the connection +} +\description{ +\code{modb_connect} creates a new database connection and attmepts to open a +connection to a database with the details provided. +} diff --git a/man/modb_connectionInfo.Rd b/man/modb_connectionInfo.Rd new file mode 100644 index 0000000..fe786cf --- /dev/null +++ b/man/modb_connectionInfo.Rd @@ -0,0 +1,47 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/Manage.R +\name{modb_connectionInfo} +\alias{modb_connectionInfo} +\alias{modb_connectionExists} +\alias{modb_connectionId} +\alias{modb_connectionName} +\title{MODB Connections} +\usage{ +modb_connectionInfo(stop_on_error = TRUE, ...) + +modb_connectionExists(...) + +modb_connectionId(conn_name, stop_on_error = TRUE) + +modb_connectionName(conn_id, stop_on_error = TRUE) +} +\arguments{ +\item{...}{conn_id, conn_name or conn_ref required. See \link{modb_conn_ref}} + +\item{conn_name}{String. The name provided when creating a connection.} + +\item{conn_id}{Integer. The id of the connection, returned by modb_connect.} +} +\value{ +The details of the connection as a list. + +TRUE if the connection exists, FALSE if not. + +The ID of the connection. + +The name of the connection. +} +\description{ +Fetches information on a connection such as the last query, insert id and +number of queries run +} +\section{Functions}{ +\itemize{ +\item \code{modb_connectionExists}: Determines if a connection with the name or +id provided exists + +\item \code{modb_connectionId}: Finds a connection id via the name + +\item \code{modb_connectionName}: Finds a connection name via the id +}} + diff --git a/man/modb_disconnect.Rd b/man/modb_disconnect.Rd new file mode 100644 index 0000000..ae2530e --- /dev/null +++ b/man/modb_disconnect.Rd @@ -0,0 +1,14 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/Manage.R +\name{modb_disconnect} +\alias{modb_disconnect} +\title{MODB Connections} +\usage{ +modb_disconnect(...) +} +\arguments{ +\item{...}{conn_id, conn_name or conn_ref required. See \link{modb_conn_ref}} +} +\description{ +modb_disconnect closes a database connection and cleans up the instance +} diff --git a/man/rmodb.Rd b/man/rmodb.Rd new file mode 100644 index 0000000..ee6ed09 --- /dev/null +++ b/man/rmodb.Rd @@ -0,0 +1,16 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/RMODB.R +\docType{package} +\name{rmodb} +\alias{rmodb} +\title{RMODB: Metadata-Object pair database} +\description{ +RMODB implements a metadata-object database for storing R objects in +MySQL. R objects are serialized into a binary memory buffer which is then +written into a MySQL database along with metadata such as creation +timestamp, update timestamp, owner, title etc. The metadata fields are +customisable on creation of the database. the metadata fields are cleartext +and queryable allowing objects to be found and returned. On returning an +object, it is de-serialised using the reverseof the serialize to binary +memory process. +} diff --git a/src/Makevars.in b/src/Makevars.in new file mode 100644 index 0000000..14e59cc --- /dev/null +++ b/src/Makevars.in @@ -0,0 +1,7 @@ +PKG_CPPFLAGS=@cflags@ +PKG_LIBS=@libs@ + +all: clean + +clean: + rm -f $(SHLIB) $(OBJECTS) diff --git a/src/R_list_item.c b/src/R_list_item.c new file mode 100644 index 0000000..5824840 --- /dev/null +++ b/src/R_list_item.c @@ -0,0 +1,22 @@ +#include + +#include "R_list_item.h" + +SEXP R_listItem(SEXP list, const char *name) +{ + SEXP r_names = Rf_getAttrib(list, R_NamesSymbol); + SEXP r_name; + + for (int i = 0; i < Rf_length(list); i++) { + if (TYPEOF(r_names) == STRSXP) { + r_name = STRING_ELT(r_names, i); + } else { + r_name = VECTOR_ELT(r_names, i); + } + if (strcmp(Rf_translateCharUTF8(r_name), name) == 0) { + return VECTOR_ELT(list, i); + } + } + + return R_NilValue; +} diff --git a/src/R_list_item.h b/src/R_list_item.h new file mode 100644 index 0000000..b7e14b4 --- /dev/null +++ b/src/R_list_item.h @@ -0,0 +1,8 @@ +#ifndef H__R_LIST_ITEM__ +#define H__R_LIST_ITEM__ + +#include + +SEXP R_listItem(SEXP list, const char *name); + +#endif // H__R_LIST_ITEM__ diff --git a/src/R_modb_manage.c b/src/R_modb_manage.c new file mode 100644 index 0000000..ec5b6a1 --- /dev/null +++ b/src/R_modb_manage.c @@ -0,0 +1,240 @@ +#include +#include + +#include "R_modb_manage.h" +#include "R_list_item.h" +#include "db_connection.h" +#include "modb.h" + + +struct stored_conn_t *getConn(SEXP r_conn_ref) +{ + struct stored_conn_t *sconn = 0; + + if (Rf_isString(r_conn_ref)) { + sconn = connectionByName(Rf_translateCharUTF8(STRING_ELT(r_conn_ref, 0))); + } else if (Rf_isInteger(r_conn_ref)) { + sconn = connectionById(Rf_asInteger(r_conn_ref)); + } else { + Rf_error("Neither name or id provided"); + } + + return sconn; +} + + +SEXP modb_connectionInfo(SEXP r_conn_ref) +{ + struct stored_conn_t *sconn; + SEXP res, conn_name, last_qry, names; + + if ((sconn = getConn(r_conn_ref)) == 0) { + return R_NilValue; + } + + conn_name = PROTECT(Rf_allocVector(STRSXP, 1)); + SET_STRING_ELT(conn_name, 0, PROTECT(Rf_mkChar(sconn->name))); + + last_qry = PROTECT(Rf_allocVector(STRSXP, 1)); + if (sconn->last_qry != 0) { + SET_STRING_ELT(last_qry, 0, PROTECT(Rf_mkChar(sconn->last_qry))); + } else { + SET_STRING_ELT(last_qry, 0, PROTECT(NA_STRING)); + } + + res = PROTECT(Rf_allocVector(VECSXP, 4)); + SET_VECTOR_ELT(res, 0, PROTECT(Rf_ScalarInteger(sconn->conn_id))); + SET_VECTOR_ELT(res, 1, conn_name); + SET_VECTOR_ELT(res, 2, last_qry); + SET_VECTOR_ELT(res, 3, PROTECT(Rf_ScalarInteger((int)sconn->num_queries))); + + names = PROTECT(Rf_allocVector(STRSXP, 4)); + SET_STRING_ELT(names, 0, PROTECT(Rf_mkChar("id"))); + SET_STRING_ELT(names, 1, PROTECT(Rf_mkChar("name"))); + SET_STRING_ELT(names, 2, PROTECT(Rf_mkChar("num_queries"))); + SET_STRING_ELT(names, 3, PROTECT(Rf_mkChar("last_query"))); + Rf_setAttrib(res, R_NamesSymbol, names); + + UNPROTECT(12); + return res; +} + +SEXP modb_connectToHost(SEXP r_name, SEXP r_host, SEXP r_port, + SEXP r_username, SEXP r_password, SEXP r_database) +{ + struct stored_conn_t *sconn; + const char *name = 0, *host, *user, *pass, *db; + unsigned int port; + + if (!Rf_isNull(r_name)) { + name = Rf_translateCharUTF8(STRING_ELT(r_name, 0)); + } + + host = Rf_translateCharUTF8(STRING_ELT(r_host, 0)); + port = (unsigned int)Rf_asInteger(r_port); + user = Rf_translateCharUTF8(STRING_ELT(r_username, 0)); + pass = Rf_translateCharUTF8(STRING_ELT(r_password, 0)); + db = Rf_translateCharUTF8(STRING_ELT(r_database, 0)); + + sconn = createStoredConnection(name); + if (sconn == 0) { + return R_NilValue; + } + + if (connectToHost(sconn, host, port, user, pass, db) < 0) { + destroyStoredConnection(sconn); + return R_NilValue; + } + + return Rf_ScalarInteger(sconn->conn_id); +} +SEXP modb_connectToSocket(SEXP r_name, SEXP r_socket, + SEXP r_username, SEXP r_password, SEXP r_database) +{ + struct stored_conn_t *sconn; + const char *name = 0, *sock, *user, *pass, *db; + + if (!Rf_isNull(r_name)) { + name = Rf_translateCharUTF8(STRING_ELT(r_name, 0)); + } + + sock = Rf_translateCharUTF8(STRING_ELT(r_socket, 0)); + user = Rf_translateCharUTF8(STRING_ELT(r_username, 0)); + pass = Rf_translateCharUTF8(STRING_ELT(r_password, 0)); + db = Rf_translateCharUTF8(STRING_ELT(r_database, 0)); + + sconn = createStoredConnection(name); + if (sconn == 0) { + return R_NilValue; + } + + if (connectToSocket(sconn, sock, user, pass, db) < 0) { + destroyStoredConnection(sconn); + return R_NilValue; + } + + return Rf_ScalarInteger(sconn->conn_id); +} + +SEXP modb_disconnect(SEXP r_conn_ref) +{ + struct stored_conn_t *sconn; + int conn_id; + + if ((sconn = getConn(r_conn_ref)) == 0) { + return R_NilValue; + } + + conn_id = sconn->conn_id; + + closeConnection(sconn); + destroyStoredConnection(sconn); + + return Rf_ScalarInteger(conn_id); +} + + +SEXP modb_exists(SEXP r_conn_ref, SEXP r_name) +{ + struct stored_conn_t *sconn; + struct modb_t modb; + + if ((sconn = getConn(r_conn_ref)) == 0) { + Rf_error("invalid connection reference\n"); + } + + modb.name = Rf_translateCharUTF8(STRING_ELT(r_name, 0)); + modb.name_len = strlen(modb.name); + + return Rf_ScalarLogical(modbExists(sconn, &modb) > 0); +} +SEXP modb_create(SEXP r_conn_ref, SEXP r_name, SEXP r_extra_meta) +{ + struct stored_conn_t *sconn; + struct modb_t modb; + struct column_data_t **cols; + size_t n_cols; + SEXP r_col, r_col_name, r_col_type, r_col_null; + + if ((sconn = getConn(r_conn_ref)) == 0) { + Rf_error("invalid connection reference\n"); + } + + modb.name = Rf_translateCharUTF8(STRING_ELT(r_name, 0)); + modb.name_len = strlen(modb.name); + + if (modbExists(sconn, &modb) == 0) { + Rf_warning("an MODB instance named '%s' already exists\n", modb.name); + return Rf_ScalarLogical(FALSE); + } + + if (modbCreate(sconn, &modb) != 0) { + modb_destroy(r_conn_ref, r_name); + Rf_warning("failed to create MODB instance"); + return Rf_ScalarLogical(FALSE); + } + if (modbAccountingCreate(sconn, &modb) != 0) { + modb_destroy(r_conn_ref, r_name); + Rf_warning("failed to create MODB instance"); + return Rf_ScalarLogical(FALSE); + } + if (!Rf_isNull(r_extra_meta)) { + n_cols = (size_t)Rf_length(r_extra_meta); + cols = (struct column_data_t **)calloc(sizeof(struct column_data_t *), n_cols); + if (cols == 0) { + modb_destroy(r_conn_ref, r_name); + Rf_warning("failed to create MODB instance"); + return Rf_ScalarLogical(FALSE); + } + + for (size_t i = 0; i < n_cols; i++) { + r_col = VECTOR_ELT(r_extra_meta, (int)i); + r_col_name = STRING_ELT(R_listItem(r_col, "name"), 0); + r_col_type = R_listItem(r_col, "type"); + r_col_null = R_listItem(r_col, "nullable"); + + *(cols + i) = initEmptyColumn( + (e_column_type)(unsigned int)Rf_asInteger(r_col_type), + Rf_asLogical(r_col_null), + Rf_translateCharUTF8(r_col_name), + 0, 0, 0); + if (*(cols + i) == 0) { + freeColumns(cols, i); + modb_destroy(r_conn_ref, r_name); + Rf_warning("failed to create MODB instance"); + return Rf_ScalarLogical(FALSE); + } + } + + if (modbMetaExtCreate(sconn, &modb, cols, n_cols) != 0) { + freeColumns(cols, n_cols); + modb_destroy(r_conn_ref, r_name); + Rf_warning("failed to create MODB instance"); + return Rf_ScalarLogical(FALSE); + } + + freeColumns(cols, n_cols); + } + + return Rf_ScalarLogical(TRUE); +} +SEXP modb_destroy(SEXP r_conn_ref, SEXP r_name) +{ + struct stored_conn_t *sconn; + struct modb_t modb; + + if ((sconn = getConn(r_conn_ref)) == 0) { + Rf_error("invalid connection reference\n"); + } + + modb.name = Rf_translateCharUTF8(STRING_ELT(r_name, 0)); + modb.name_len = strlen(modb.name); + + if (modbMetaExtExists(sconn, &modb) > 0) { + modbMetaExtDestroy(sconn, &modb); + } + modbAccountingDestroy(sconn, &modb); + modbDestroy(sconn, &modb); + + return Rf_ScalarLogical(TRUE); +} diff --git a/src/R_modb_manage.h b/src/R_modb_manage.h new file mode 100644 index 0000000..3e87e8a --- /dev/null +++ b/src/R_modb_manage.h @@ -0,0 +1,20 @@ +#ifndef H__R_MODB_MANAGE__ +#define H__R_MODB_MANAGE__ + +#include + +SEXP modb_connectionInfo(SEXP r_conn_ref); + +SEXP modb_connectToHost(SEXP r_name, SEXP r_host, SEXP r_port, + SEXP r_username, SEXP r_password, SEXP r_database); +SEXP modb_connectToSocket(SEXP r_name, SEXP r_socket, + SEXP r_username, SEXP r_password, SEXP r_database); + +SEXP modb_disconnect(SEXP r_conn_ref); + + +SEXP modb_exists(SEXP r_conn_ref, SEXP r_name); +SEXP modb_create(SEXP r_conn_ref, SEXP r_name, SEXP r_extra_meta); +SEXP modb_destroy(SEXP r_conn_ref, SEXP r_name); + +#endif // H__R_MODB_MANAGE__ diff --git a/src/initR.c b/src/initR.c new file mode 100644 index 0000000..f4d07fa --- /dev/null +++ b/src/initR.c @@ -0,0 +1,22 @@ +#include + +#include "R_modb_manage.h" + +static const R_CallMethodDef callMethods[] = { + {"modb_connectionInfo", (DL_FUNC) &modb_connectionInfo, 1}, + {"modb_connectToHost", (DL_FUNC) &modb_connectToHost, 6}, + {"modb_connectToSocket", (DL_FUNC) &modb_connectToSocket, 5}, + {"modb_disconnect", (DL_FUNC) &modb_disconnect, 1}, + + {"modb_exists", (DL_FUNC) &modb_exists, 2}, + {"modb_create", (DL_FUNC) &modb_create, 3}, + {"modb_destroy", (DL_FUNC) &modb_destroy, 2}, + {NULL, NULL, 0} +}; + +void R_init_rmodb(DllInfo *dll) +{ + R_registerRoutines(dll, NULL, callMethods, NULL, NULL); + R_useDynamicSymbols(dll, FALSE); + R_forceSymbols(dll, TRUE); +} diff --git a/src/modb_types.c b/src/modb_types.c index 7992823..4665bd9 100644 --- a/src/modb_types.c +++ b/src/modb_types.c @@ -7,28 +7,3 @@ #include "strext.h" -struct modb_t *modbAlloc(const char *name, size_t name_len) -{ - struct modb_t *modb = (struct modb_t *)malloc(sizeof(struct modb_t)); - if (modb == 0) { - fprintf(stderr, "[%d]malloc: (%d) %s\n", __LINE__, errno, strerror(errno)); - return 0; - } - - if (strmemcpy(name, name_len, &modb->name, &modb->name_len) != 0) { - modbRelease(&modb); - return 0; - } - - return modb; -} -void modbRelease(struct modb_t **modb) -{ - if ((*modb)->name != 0) { - free((*modb)->name); - (*modb)->name = 0; - } - - free(*modb); - *modb = 0; -} diff --git a/src/modb_types.h b/src/modb_types.h index 65f4257..f1ef665 100644 --- a/src/modb_types.h +++ b/src/modb_types.h @@ -5,11 +5,9 @@ #include struct modb_t { - char *name; + const char *name; size_t name_len; }; -struct modb_t *modbAlloc(const char *name, size_t name_len); -void modbRelease(struct modb_t **modb); #endif // H__MODB_TYPES__