Skip to content

Commit 00eae56

Browse files
committed
version bump; update docs, benchmark, tests
1 parent d8e2bc8 commit 00eae56

36 files changed

+278
-263
lines changed

DESCRIPTION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ Description: Simplified JSON document database access and manipulation,
44
providing a common API across supported 'NoSQL' databases
55
'Elasticsearch', 'CouchDB', 'MongoDB' as well as
66
'SQLite/JSON1', 'PostgreSQL', and 'DuckDB'.
7-
Version: 0.11.0
7+
Version: 0.11.0.9000
88
Authors@R: c(
99
person(given = "Ralf",
1010
family = "Herold",

R/create.R

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -431,7 +431,7 @@ docdb_create.src_postgres <- function(src, key, value, ...) {
431431
" CASE WHEN length(json->>'_id') > 0 THEN",
432432
" json->>'_id' ELSE gen_random_uuid()::TEXT END AS _id,",
433433
" CASE WHEN length(json->>'_id') > 0 THEN",
434-
" jsonb_merge_patch(json, '{\"_id\": null}') ELSE json END AS json",
434+
" jsonb_patch(json, '{\"_id\": null}') ELSE json END AS json",
435435
" FROM \"", tblName, "\";")
436436
), silent = TRUE)
437437

R/src_postgres.R

Lines changed: 19 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
#' Any root-level `_id` is extracted from the document(s) and used
2424
#' for column `_id`, otherwise a UUID is created as `_id`.
2525
#' The table is indexed on `_id`. A custom `plpgsql` function
26-
#' [jsonb_merge_patch()](https://github.com/ropensci/nodbi/blob/master/R/src_postgres.R#L60)
2726
#' is used for `docdb_update()`.
2827
#' The order of variables in data frames returned by `docdb_get()`
2928
#' and `docdb_query()` can differ from their order the input to
@@ -73,29 +72,29 @@ src_postgres <- function(dbname = "test",
7372
# add plpgsql function for docdb_update(), which should only raise
7473
# an error if the plpgsql extension is not installed in dbname
7574
out <- try(
76-
DBI::dbExecute(conn = con, statement = '
77-
CREATE OR REPLACE FUNCTION
78-
jsonb_patch("injsn" jsonb, "delta" jsonb)
75+
DBI::dbExecute(conn = con, statement = "
76+
CREATE OR REPLACE FUNCTION jsonb_patch(base jsonb, patch jsonb)
7977
RETURNS jsonb LANGUAGE plpgsql AS $$
80-
BEGIN
81-
RETURN
82-
COALESCE(
78+
BEGIN
79+
RETURN (
80+
SELECT COALESCE(
8381
JSONB_OBJECT_AGG(
84-
COALESCE("keyInjsn", "keyDelta"),
82+
COALESCE(k1, k2),
8583
CASE
86-
WHEN "valInjsn" IS NULL THEN "valDelta"
87-
WHEN "valDelta" IS NULL THEN "valInjsn"
88-
WHEN (jsonb_typeof("valInjsn") <> \'object\') OR
89-
(jsonb_typeof("valDelta") <> \'object\') THEN "valDelta"
90-
ELSE jsonb_patch("valInjsn", "valDelta")
84+
WHEN v1 IS NULL THEN v2
85+
WHEN v2 IS NULL THEN v1
86+
WHEN jsonb_typeof(v1) = 'object' AND
87+
jsonb_typeof(v2) = 'object'
88+
THEN jsonb_patch(v1, v2)
89+
ELSE v2
9190
END
92-
), \'{}\'::jsonb)
93-
FROM jsonb_each(injsn) AS fromInjsn("keyInjsn", "valInjsn")
94-
FULL JOIN jsonb_each(delta) AS fromDelta("keyDelta", "valDelta")
95-
ON "keyInjsn" = "keyDelta"
96-
WHERE (jsonb_typeof("valDelta") <> \'null\') OR ("valDelta" ISNULL);
97-
END;
98-
$$;'), silent = TRUE)
91+
), '{}'::jsonb)
92+
FROM jsonb_each(base) AS b(k1, v1)
93+
FULL JOIN jsonb_each(patch) AS p(k2, v2) ON k1 = k2
94+
WHERE jsonb_typeof(v2) <> 'null' OR v2 IS NULL
95+
);
96+
END;
97+
$$;"), silent = TRUE)
9998

10099
if (inherits(out, "try-error") &&
101100
!grepl("tuple concurrently updated", out)) {

README.Rmd

Lines changed: 73 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,12 @@ editor_options:
77
# nodbi
88

99
```{r echo=FALSE}
10-
1110
knitr::opts_chunk$set(
1211
collapse = TRUE,
1312
eval = FALSE,
1413
comment = "#",
1514
out.width = "100%"
1615
)
17-
1816
```
1917

2018
<!-- badges: start -->
@@ -24,7 +22,7 @@ knitr::opts_chunk$set(
2422
[![Lifecycle: stable](https://img.shields.io/badge/lifecycle-stable-brightgreen.svg)](https://lifecycle.r-lib.org/articles/stages.html#stable)
2523
<!-- badges: end -->
2624

27-
`nodbi` is an R package that provides a single interface for several NoSQL databases and databases with JSON functionality, with the same function parameters and return values across all database backends. Last updated 2024-11-10.
25+
`nodbi` is an R package that provides a single interface for several NoSQL databases and databases with JSON functionality, with the same function parameters and return values across all database backends. Last updated 2025-03-29.
2826

2927
| Currently, `nodbi` supports<br/>as database backends | for an `R` object of any<br/>of these data types | for these operations |
3028
| :--------------------- | :--------------------- | :--------------------- |
@@ -96,7 +94,8 @@ src <- nodbi::src_duckdb(dbdir = ":memory:", ...)
9694
```{r}
9795
src <- nodbi::src_mongo(
9896
collection = "my_container", db = "my_database",
99-
url = "mongodb://localhost", ...)
97+
url = "mongodb://localhost", ...
98+
)
10099
```
101100

102101
### SQLite
@@ -114,7 +113,8 @@ src <- nodbi::src_sqlite(dbname = ":memory:", ...)
114113
```{r}
115114
src <- nodbi::src_couchdb(
116115
host = "127.0.0.1", port = 5984L, path = NULL,
117-
transport = "http", user = NULL, pwd = NULL, headers = NULL)
116+
transport = "http", user = NULL, pwd = NULL, headers = NULL
117+
)
118118
```
119119

120120
### Elasticsearch
@@ -124,7 +124,8 @@ src <- nodbi::src_couchdb(
124124
```{r}
125125
src <- nodbi::src_elastic(
126126
host = "127.0.0.1", port = 9200L, path = NULL,
127-
transport_schema = "http", user = NULL, pwd = NULL, ...)
127+
transport_schema = "http", user = NULL, pwd = NULL, ...
128+
)
128129
```
129130

130131
### PostgreSQL
@@ -133,7 +134,8 @@ src <- nodbi::src_elastic(
133134

134135
```{r}
135136
src <- nodbi::src_postgres(
136-
dbname = "my_database", host = "127.0.0.1", port = 5432L, ...)
137+
dbname = "my_database", host = "127.0.0.1", port = 5432L, ...
138+
)
137139
```
138140

139141
## Walk-through {#walk-through}
@@ -154,8 +156,9 @@ src <- src_sqlite()
154156
src <- src_postgres()
155157
src <- src_elastic()
156158
src <- src_couchdb(
157-
user = Sys.getenv("COUCHDB_TEST_USER"),
158-
pwd = Sys.getenv("COUCHDB_TEST_PWD"))
159+
user = Sys.getenv("COUCHDB_TEST_USER"),
160+
pwd = Sys.getenv("COUCHDB_TEST_PWD")
161+
)
159162
160163
# check if container already exists
161164
docdb_exists(src, key)
@@ -180,17 +183,17 @@ docdb_create(src, key, contacts)
180183
dplyr::tibble(docdb_get(src, key))
181184
# # A tibble: 135 × 27
182185
# `_id` isActive balance age eyeColor name email about registered tags friends
183-
# <chr> <lgl> <chr> <int> <chr> <chr> <chr> <chr> <chr> <list> <list>
184-
# 1 5cd6… TRUE $2,412… 20 blue Kris… kris… "Sin… 2017-07-1… <chr> <df>
185-
# 2 5cd6… FALSE $3,400… 20 brown Rae … raec… "Nis… 2018-12-1… <chr> <df>
186-
# 3 5cd6… TRUE $1,161… 22 brown Pace… pace… "Eiu… 2018-08-1… <chr> <df>
187-
# 4 5cd6… FALSE $2,579… 30 brown Will… will… "Nul… 2018-02-1… <chr> <df>
188-
# 5 5cd6… FALSE $3,808… 23 green Lacy… lacy… "Sun… 2014-08-0… <chr> <df>
189-
# 6 69bc… NA NA NA NA NA NA NA NA <NULL> <NULL>
190-
# 7 69bc… NA NA NA NA NA NA NA NA <NULL> <NULL>
191-
# 8 69bc… NA NA NA NA NA NA NA NA <NULL> <NULL>
192-
# 9 69bc… NA NA NA NA NA NA NA NA <NULL> <NULL>
193-
# 10 69bc… NA NA NA NA NA NA NA NA <NULL> <NULL>
186+
# <chr> <lgl> <chr> <int> <chr> <chr> <chr> <chr> <chr> <list> <list>
187+
# 1 5cd6… TRUE $2,412… 20 blue Kris… kris… "Sin… 2017-07-1… <chr> <df>
188+
# 2 5cd6… FALSE $3,400… 20 brown Rae … raec… "Nis… 2018-12-1… <chr> <df>
189+
# 3 5cd6… TRUE $1,161… 22 brown Pace… pace… "Eiu… 2018-08-1… <chr> <df>
190+
# 4 5cd6… FALSE $2,579… 30 brown Will… will… "Nul… 2018-02-1… <chr> <df>
191+
# 5 5cd6… FALSE $3,808… 23 green Lacy… lacy… "Sun… 2014-08-0… <chr> <df>
192+
# 6 69bc… NA NA NA NA NA NA NA NA <NULL> <NULL>
193+
# 7 69bc… NA NA NA NA NA NA NA NA <NULL> <NULL>
194+
# 8 69bc… NA NA NA NA NA NA NA NA <NULL> <NULL>
195+
# 9 69bc… NA NA NA NA NA NA NA NA <NULL> <NULL>
196+
# 10 69bc… NA NA NA NA NA NA NA NA <NULL> <NULL>
194197
# # ℹ 125 more rows
195198
# # ℹ 16 more variables: url <chr>, args <df[,0]>, headers <df[,4]>, origin <chr>,
196199
# # id <int>, mpg <dbl>, cyl <int>, disp <dbl>, hp <int>, drat <dbl>, wt <dbl>,
@@ -206,7 +209,7 @@ docdb_query(src, key, query = '{"mpg": {"$gte": 30}}')
206209
# 3 Toyota Corolla 34 4 71 65 4.2 1.8 20 1 1 4 1
207210
# 4 Lotus Europa 30 4 95 113 3.8 1.5 17 1 1 5 2
208211
209-
# query some fields from some documents; 'query' is a mandatory
212+
# query some fields from some documents; 'query' is a mandatory
210213
# parameter and is used here in its position in the signature
211214
docdb_query(src, key, '{"mpg": {"$gte": 30}}', fields = '{"wt": 1, "mpg": 1}')
212215
# _id wt mpg
@@ -217,10 +220,11 @@ docdb_query(src, key, '{"mpg": {"$gte": 30}}', fields = '{"wt": 1, "mpg": 1}')
217220
218221
# query some subitem fields from some documents
219222
str(docdb_query(
220-
src, key,
221-
query = '{"$or": [{"age": {"$gt": 21}},
222-
{"friends.name": {"$regex": "^B[a-z]{3,9}.*"}}]}',
223-
fields = '{"age": 1, "friends.name": 1}'))
223+
src, key,
224+
query = '{"$or": [{"age": {"$gt": 21}},
225+
{"friends.name": {"$regex": "^B[a-z]{3,9}.*"}}]}',
226+
fields = '{"age": 1, "friends.name": 1}'
227+
))
224228
# 'data.frame': 3 obs. of 3 variables:
225229
# $ _id : chr "5cd6785325ce3a94dfc54096" "5cd6785335b63cb19dfa8347" "5cd67853f841025e65ce0ce2"
226230
# $ age : int 22 30 23
@@ -229,7 +233,7 @@ str(docdb_query(
229233
# ..$ : chr "Coleen Dunn" "Doris Phillips" "Concetta Turner"
230234
# ..$ : chr "Wooten Goodwin" "Brandie Woodward" "Angelique Britt"
231235
232-
# such queries can also be used for updating (patching) selected documents
236+
# such queries can also be used for updating (patching) selected documents
233237
# with a new 'value'(s) from a JSON string, a data frame a list or a file with NSJSON)
234238
docdb_update(src, key, value = '{"vs": 9, "xy": [1, 2]}', query = '{"carb": 3}')
235239
# [1] 3
@@ -246,7 +250,7 @@ docdb_get(src, key)[c(3, 109, 130, 101), c("_id", "xy", "url", "email")]
246250
# *note* that dplyr includes a (deprecated) function src_sqlite
247251
# which would mask nodbi's src_sqlite, so it is excluded here
248252
library("dplyr", exclude = c("src_sqlite", "src_postgres"))
249-
#
253+
#
250254
docdb_get(src, key) %>%
251255
group_by(gear) %>%
252256
summarise(mean_mpg = mean(mpg))
@@ -256,9 +260,9 @@ docdb_get(src, key) %>%
256260
# 1 3 16.1
257261
# 2 4 24.5
258262
# 3 5 21.4
259-
# 4 NA NA
263+
# 4 NA NA
260264
261-
# delete documents; query is optional parameter and has to be
265+
# delete documents; query is optional parameter and has to be
262266
# specified for deleting documents instead of deleting the container
263267
dim(docdb_query(src, key, query = '{"$or": [{"age": {"$lte": 20}}, {"age": {"$gte": 25}}]}'))
264268
# [1] 3 11
@@ -270,9 +274,10 @@ nrow(docdb_get(src, key))
270274
# delete container from database
271275
docdb_delete(src, key)
272276
# [1] TRUE
273-
#
277+
#
274278
# shutdown
275-
DBI::dbDisconnect(src$con, shutdown = TRUE); rm(src)
279+
DBI::dbDisconnect(src$con, shutdown = TRUE)
280+
rm(src)
276281
```
277282

278283
## Benchmark {#benchmark}
@@ -286,8 +291,9 @@ srcPostgres <- src_postgres()
286291
srcDuckdb <- src_duckdb()
287292
srcElastic <- src_elastic()
288293
srcCouchdb <- src_couchdb(
289-
user = Sys.getenv("COUCHDB_TEST_USER"),
290-
pwd = Sys.getenv("COUCHDB_TEST_PWD"))
294+
user = Sys.getenv("COUCHDB_TEST_USER"),
295+
pwd = Sys.getenv("COUCHDB_TEST_PWD")
296+
)
291297
292298
key <- "test"
293299
query <- '{"clarity": {"$in": ["NOTME", "VS1"]}}'
@@ -318,25 +324,25 @@ result <- rbenchmark::benchmark(
318324
replications = 3L
319325
)
320326
321-
# 2024-11-10 with M3 hardware, databases via homebrew
322-
result[rev(order(result$elapsed)), c('test', 'replications', 'elapsed')]
327+
# 2025-03-29 with M3 hardware, databases via homebrew
328+
result[rev(order(result$elapsed)), c("test", "replications", "elapsed")]
323329
# test replications elapsed
324-
# 4 CouchDB 3 52.81
325-
# 3 Elastic 3 27.76
326-
# 5 PostgreSQL 3 1.59
327-
# 1 MongoDB 3 1.41
328-
# 6 DuckDB 3 1.10
329-
# 2 SQLite 3 0.71
330+
# 4 CouchDB 3 69.69
331+
# 3 Elastic 3 26.79
332+
# 1 MongoDB 3 1.61
333+
# 5 PostgreSQL 3 1.52
334+
# 6 DuckDB 3 1.12
335+
# 2 SQLite 3 0.67
330336
331337
message(R.version$version.string)
332-
# R version 4.4.2 (2024-10-31)
338+
# R Under development (unstable) (2025-03-10 r87922)
333339
334340
pkgs <- c("RSQLite", "duckdb", "RPostgres", "mongolite", "elastic", "sofa")
335341
for (pkg in pkgs) message(pkg, ": ", packageVersion(pkg))
336-
# RSQLite: 2.3.7.9017
337-
# duckdb: 1.1.2
338-
# RPostgres: 1.4.7
339-
# mongolite: 2.8.1
342+
# RSQLite: 2.3.9
343+
# duckdb: 1.2.1
344+
# RPostgres: 1.4.8
345+
# mongolite: 3.1.2
340346
# elastic: 1.2.0
341347
# sofa: 0.4.0
342348
```
@@ -346,33 +352,34 @@ for (pkg in pkgs) message(pkg, ": ", packageVersion(pkg))
346352
Every database backend is subjected to identical tests, see [core-nodbi.R](https://github.com/ropensci/nodbi/blob/master/tests/testthat/core-nodbi.R).
347353

348354
```{r testing_and_coverage}
349-
# 2024-11-10
355+
# 2025-03-29
356+
350357
suppressMessages(testthat::test_local())
351358
# ✔ | F W S OK | Context
352-
# ✔ | 2 175 | couchdb [47.9s]
353-
# ✔ | 1 174 | duckdb [3.5s]
354-
# ✔ | 2 173 | elastic [38.3s]
355-
# ✔ | 2 173 | mongodb [3.6s]
356-
# ✔ | 176 | postgres [4.3s]
357-
# ✔ | 177 | sqlite [3.5s]
358-
#
359-
# ══ Results ══════════════════════════
360-
# Duration: 101.2 s
361-
#
362-
# ── Skipped tests (7) ────────────────
363-
# • Testing for auto disconnect and shutdown not relevant (3):
364-
# test-couchdb.R:26:3, test-elastic.R:21:3, test-mongodb.R:24:3
365-
# • Testing for parallel writes not possible or implemented (4):
366-
# test-couchdb.R:26:3, test-duckdb.R:22:3,
359+
# ✔ | 2 175 | couchdb [46.4s]
360+
# ✔ | 1 174 | duckdb [3.7s]
361+
# ✔ | 2 173 | elastic [40.5s]
362+
# ✔ | 2 173 | mongodb [3.9s]
363+
# ✔ | 176 | postgres [4.7s]
364+
# ✔ | 177 | sqlite [3.6s]
365+
#
366+
# ══ Results ═════════════════════════════════════════════════
367+
# Duration: 102.8 s
368+
#
369+
# ── Skipped tests (7) ───────────────────────────────────────
370+
# • Testing for auto disconnect and shutdown not relevant (3):
371+
# test-couchdb.R:26:3, test-elastic.R:21:3,
372+
# test-mongodb.R:24:3
373+
# • Testing for parallel writes not possible or implemented (4):
374+
# test-couchdb.R:26:3, test-duckdb.R:22:3,
367375
# test-elastic.R:21:3, test-mongodb.R:24:3
368-
#
376+
#
369377
# [ FAIL 0 | WARN 0 | SKIP 7 | PASS 1048 ]
370378
371-
# 2024-11-10
372379
covr::package_coverage(path = ".", type = "tests")
373-
# nodbi Coverage: 94.07%
374-
# R/src_postgres.R: 79.31%
380+
# nodbi Coverage: 94.06%
375381
# R/src_duckdb.R: 79.49%
382+
# R/src_postgres.R: 80.00%
376383
# R/zzz.R: 87.79%
377384
# R/src_mongo.R: 92.59%
378385
# R/update.R: 94.40%

0 commit comments

Comments
 (0)