What csdb is for
csdb puts a large surveillance dataset into a database
and takes it out again, without you writing SQL. You describe a table
once: its columns and their types, its primary key, and its indexes. You
then insert, upsert, delete and read rows through R methods. Before it
writes, it can check that your data matches an agreed column format, so
a table cannot quietly change shape between runs.
Two classes, and the split between them
DBConnection_v9 is the connection: the settings, plus
$connect(), $disconnect() and
$autoconnection. DBTable_v9 is one table: its
field types, its keys, its indexes, and every read and write method.
You do not have to build the connection first.
DBTable_v9$new() takes the same settings as a
dbconfig list, builds its own DBConnection_v9,
and keeps it at $dbconnection. Build a
DBConnection_v9 yourself when you want a connection without
a table.
Neither constructor touches the database. Both return an object that is not connected:
con <- csdb::DBConnection_v9$new(driver = "SQLite", db = tempfile(fileext = ".sqlite"))
con$is_connected()
#> [1] FALSE
tab <- csdb::DBTable_v9$new(
dbconfig = list(
driver = "PostgreSQL Unicode",
server = "localhost",
port = 5432,
db = "mydb",
schema = "public",
user = "u",
password = "p"
),
table_name = "cases",
field_types = c("location_code" = "TEXT", "date" = "DATE", "cases_n" = "INTEGER"),
keys = c("location_code", "date")
)
tab$dbconnection$is_connected()
#> [1] FALSEThe second block names a PostgreSQL server that is not running, and still returns an object. The first method that reaches the database is what connects.
The validators, and one gap
A validator is a function you hand to DBTable_v9$new().
csdb exports six:
sort(grep("^validator_", getNamespaceExports("csdb"), value = TRUE))
#> [1] "validator_field_contents_blank"
#> [2] "validator_field_contents_csfmt_rts_data_v1"
#> [3] "validator_field_contents_csfmt_rts_data_v2"
#> [4] "validator_field_types_blank"
#> [5] "validator_field_types_csfmt_rts_data_v1"
#> [6] "validator_field_types_csfmt_rts_data_v2"The _blank pair accepts anything. The other four check
two csverse surveillance formats, csfmt_rts_data_v1 and
csfmt_rts_data_v2. A _field_types validator
runs once, inside $new(). A _field_contents
validator runs inside $insert_data() and
$upsert_data().
There is no csfmt_rts_data_v3
validator. That is a current limitation, and it matters now.
cstidy marks v1 and v2 deprecated, and points new work at
set_csfmt_rts_data_v3(). csalert’s pipeline
ends in ens_collapse(heal = TRUE), which returns a
csfmt_rts_data_v3. So today’s analysis output has no
validator here that matches it. You can still store it: pass the
_blank pair, or a function of your own. csdb
then checks nothing about the columns.
The three backends
| Backend | Connects through | Needs a server |
|---|---|---|
| PostgreSQL |
odbc, driver "PostgreSQL Unicode"
|
Yes |
| Microsoft SQL Server |
odbc, driver
"ODBC Driver 17 for SQL Server"
|
Yes |
| SQLite |
RSQLite, where db is a file path |
No |
One dbconfig list picks the backend, and nothing else in
your code changes. vignette("backends", package = "csdb")
puts a PostgreSQL configuration and a SQLite configuration side by side,
and lists what differs between them.
Overview
csdb provides an abstracted database-access layer for
the Core Surveillance (csverse) ecosystem. The package exposes two R6
classes:
-
DBConnection_v9— wraps a single database connection and manages its lifecycle (connect, disconnect, reconnect). -
DBTable_v9— represents one database table. It inserts, upserts and deletes rows, manages indexes, and validates field types and contents.
csdb supports three backends. Microsoft SQL Server and PostgreSQL
connect over ODBC. Pass their server, port, user and password as
arguments to DBConnection_v9$new(). csdb never reads an
environment variable itself. To keep credentials out of scripts and
version control, read the variable yourself and pass the result in.
SQLite connects through RSQLite, where db is a
file path. csdb reads no server, port, user or password for SQLite.
This vignette runs on SQLite, in a file created by
tempfile(). Every chunk below therefore executes on a
machine with no database server. For what changes when you point the
same code at PostgreSQL instead, see
vignette("backends", package = "csdb").
DBConnection_v9
DBConnection_v9$new() stores connection parameters but
does not open a connection immediately. $connect() opens
the connection; $disconnect() closes it. The
$autoconnection field returns an active connection, and
reconnects automatically when needed.
The example below creates a connection object, connects, inspects the
connection fields, then disconnects. After you disconnect,
$connection reports DISCONNECTED, while
class() still reports the driver type, and a read of
$autoconnection opens the file again.
DBTable_v9
DBTable_v9$new() takes a dbconfig list (the
same parameters as DBConnection_v9), plus
table_name, field_types, keys,
indexes, and validators. Keys define the primary key used
for upsert operations. Validators can enforce field-type and
field-content constraints; validator_field_types_blank and
validator_field_contents_blank skip validation
entirely.
The example below creates a table object backed by the same SQLite
file. It clears any existing rows and inserts the bundled
nor_covid19_cases_by_time_location dataset. It then
connects and returns a lazy tbl() reference via dbplyr. The
file is new, so $drop_all_rows() creates the table and its
index first and then reports that it removed 0 rows.
db_file <- tempfile(fileext = ".sqlite")
dbconnection <- csdb::DBConnection_v9$new(
driver = "SQLite",
db = db_file
)
dbconnection
#> (disconnected)
#>
#> Driver: SQLite
#> File: /tmp/Rtmp3tTfJa/file3a0c741e4a2129.sqlite
dbconnection$connect()
dbconnection$connection
#> <SQLiteConnection>
#> Path: /tmp/Rtmp3tTfJa/file3a0c741e4a2129.sqlite
#> Extensions: TRUE
dbconnection$autoconnection
#> <SQLiteConnection>
#> Path: /tmp/Rtmp3tTfJa/file3a0c741e4a2129.sqlite
#> Extensions: TRUE
dbconnection
#> (connected)
#>
#> Driver: SQLite
#> File: /tmp/Rtmp3tTfJa/file3a0c741e4a2129.sqlite
dbconnection$disconnect()
dbconnection$connection
#> <SQLiteConnection>
#> DISCONNECTED
class(dbconnection$connection)
#> [1] "SQLiteConnection"
#> attr(,"package")
#> [1] "RSQLite"
class(dbconnection$autoconnection)
#> [1] "SQLiteConnection"
#> attr(,"package")
#> [1] "RSQLite"
dbtable <- csdb::DBTable_v9$new(
dbconfig = list(
driver = "SQLite",
db = db_file
),
table_name = "anon_test",
field_types = c(
"granularity_time" = "TEXT",
"granularity_geo" = "TEXT",
"country_iso3" = "TEXT",
"location_code" = "TEXT",
"border" = "INTEGER",
"age" = "TEXT",
"sex" = "TEXT",
"isoyear" = "INTEGER",
"isoweek" = "INTEGER",
"isoyearweek" = "TEXT",
"season" = "TEXT",
"seasonweek" = "DOUBLE",
"calyear" = "INTEGER",
"calmonth" = "INTEGER",
"calyearmonth" = "TEXT",
"date" = "DATE",
"covid19_cases_testdate_n" = "INTEGER",
"covid19_cases_testdate_pr100000" = "DOUBLE"
),
keys = c(
"granularity_time",
"location_code",
"date",
"age",
"sex"
),
indexes = list(
"ind1" = c("granularity_time", "granularity_geo", "country_iso3", "location_code", "border", "age", "sex", "date", "isoyear", "isoweek", "isoyearweek")
),
validator_field_types = csdb::validator_field_types_blank,
validator_field_contents = csdb::validator_field_contents_blank
)
dbtable$drop_all_rows()
#> Creating table anon_test
#> Adding index ind1
#> [1] 0
dbtable$insert_data(csdb::nor_covid19_cases_by_time_location)
dbtable$connect()
dbtable$dbconnection$is_connected()
#> [1] TRUE
dbtable$tbl()
#> # Source: table<`anon_test`> [?? x 18]
#> # Database: sqlite 3.51.2 [/tmp/Rtmp3tTfJa/file3a0c741e4a2129.sqlite]
#> granularity_time granularity_geo country_iso3 location_code border age
#> <chr> <chr> <chr> <chr> <int> <chr>
#> 1 day county nor county_nor03 2020 total
#> 2 day county nor county_nor03 2020 total
#> 3 day county nor county_nor03 2020 total
#> 4 day county nor county_nor03 2020 total
#> 5 day county nor county_nor03 2020 total
#> 6 day county nor county_nor03 2020 total
#> 7 day county nor county_nor03 2020 total
#> 8 day county nor county_nor03 2020 total
#> 9 day county nor county_nor03 2020 total
#> 10 day county nor county_nor03 2020 total
#> # ℹ more rows
#> # ℹ 12 more variables: sex <chr>, isoyear <int>, isoweek <int>,
#> # isoyearweek <chr>, season <chr>, seasonweek <dbl>, calyear <int>,
#> # calmonth <int>, calyearmonth <chr>, date <date>,
#> # covid19_cases_testdate_n <int>, covid19_cases_testdate_pr100000 <dbl>