Skip to content

Commit bd990f4

Browse files
committed
Simplify structure and interface of constraint check results objects
1 parent 164ce42 commit bd990f4

9 files changed

Lines changed: 183 additions & 98 deletions

File tree

DESCRIPTION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
Package: qualifyr
22
Type: Package
33
Title: Validate Relational Data Structures
4-
Version: 0.1.0.9002
4+
Version: 0.1.0.9003
55
Authors@R: c(
66
person(
77
"Ian", "Farm",

NAMESPACE

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ S3method("[[",qf_dataset)
1010
S3method("[[<-",qf_dataset)
1111
S3method("|",qf_exception)
1212
S3method("|",qf_exception_list)
13+
S3method(as.logical,qf_check)
1314
S3method(as_qf_constraint,qf_constraint)
1415
S3method(as_qf_constraint,qf_constraint_specifier)
1516
S3method(as_qf_constraint_list,list)
@@ -25,11 +26,11 @@ S3method(check_constraint_strict,cstr_unique_key)
2526
S3method(check_constraints,qf_dataset)
2627
S3method(check_constraints,qf_table)
2728
S3method(print,cstr_foreign_key)
29+
S3method(print,qf_check)
30+
S3method(print,qf_check_dataset)
31+
S3method(print,qf_check_table)
2832
S3method(print,qf_constraint)
2933
S3method(print,qf_dataset)
30-
S3method(print,qf_report_check)
31-
S3method(print,qf_report_check_dataset)
32-
S3method(print,qf_report_check_table)
3334
S3method(print,qf_table)
3435
S3method(utils::str,qf_dataset)
3536
S3method(utils::str,qf_table)
@@ -58,8 +59,10 @@ export(get_foreign_key)
5859
export(get_not_missing)
5960
export(get_primary_key)
6061
export(get_unique_key)
62+
export(handled)
6163
export(is_qf_constraint)
6264
export(is_qf_dataset)
6365
export(is_qf_exception)
6466
export(is_qf_table)
6567
export(qf_dataset)
68+
export(satisfied)

R/qf_check.R

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
# Class definition =============================================================
2+
3+
new_qf_check <- function(x, subclass, satisfied, handled, ...) {
4+
structure(
5+
x,
6+
class = c(subclass, "qf_check"),
7+
satisfied = satisfied, handled = handled,
8+
...
9+
)
10+
}
11+
12+
13+
14+
# Accessors ====================================================================
15+
16+
#' Inspect constraint check results
17+
#'
18+
#' Functions to access attributes of constraint check results
19+
#'
20+
#' @param x An object returned by [`check_constraints()`], i.e., a `<qf_check`>
21+
#' object
22+
#' @param ... Not used
23+
#'
24+
#' @returns Logical; for `satisfied()`, whether all constraints were satisfied,
25+
#' and for `handled()`, whether all constraints were either satisfied or
26+
#' excepted. `as.logical(x)` is equivalent to `handled(x)`.
27+
#'
28+
#' @name inspect_results
29+
NULL
30+
31+
32+
#' @rdname inspect_results
33+
#' @export
34+
satisfied <- function(x) {
35+
attr(x, "satisfied")
36+
}
37+
#' @rdname inspect_results
38+
#' @export
39+
handled <- function(x) {
40+
attr(x, "handled")
41+
}
42+
#' @rdname inspect_results
43+
#' @export
44+
as.logical.qf_check <- function(x, ...) {
45+
handled(x)
46+
}
47+
48+
49+
50+
# Pretty printing ==============================================================
51+
52+
#' @noRd
53+
#' @export
54+
print.qf_check <- function(x, max_width = getOption("width"), ...) {
55+
print(attr(x, "constraint"))
56+
if (satisfied(x)) {
57+
cat(" All rows satisfied")
58+
} else if (handled(x)) {
59+
cat(" All rows satisfied or excepted")
60+
} else {
61+
message <- " Violating rows: "
62+
indices <- format_indices(
63+
which(!x$handled),
64+
max_width - nchar(message)
65+
)
66+
cat0(message, indices)
67+
}
68+
cat("\n")
69+
invisible(x)
70+
}
71+
72+
#' @noRd
73+
#' @export
74+
print.qf_check_table <- function(x, ...) {
75+
check_header(x)
76+
for (cstr in x) print(cstr)
77+
invisible(x)
78+
}
79+
80+
#' @noRd
81+
#' @export
82+
print.qf_check_dataset <- function(x, ...) {
83+
check_header(unlist(x, recursive = FALSE))
84+
indent <- " "
85+
for (i in 1:length(x)) {
86+
if (!purrr::every(x[[i]], handled)) {
87+
cat0("=> In table '", names(x)[[i]], "':\n")
88+
for (j in 1:length(x[[i]])) {
89+
if (!handled(x[[i]][[j]])) {
90+
constraint_check_text <- utils::capture.output({
91+
cat0("[[", j, "]] ")
92+
print(x[[i]][[j]], max_width = getOption("width") - nchar(indent))
93+
})
94+
cat(paste0(indent, constraint_check_text), sep = "\n")
95+
}
96+
}
97+
}
98+
}
99+
}
100+
101+
check_header <- function(x) {
102+
satisfied <- purrr::map_lgl(x, satisfied)
103+
handled <- purrr::map_lgl(x, handled)
104+
n_satisfied <- sum(satisfied)
105+
n_excepted <- sum(handled) - sum(satisfied)
106+
n_violated <- length(x) - sum(handled)
107+
cat("Constraint check report:", n_satisfied, "satisfied", "/", n_excepted,
108+
"excepted", "/", n_violated, "violated:", "\n")
109+
}
110+
111+

R/qf_constraint.R

Lines changed: 12 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -246,24 +246,23 @@ apply_to_each <- function(.cstr, ..., .args = list()) {
246246
#' @param constraint A `<qf_constraint>` object
247247
#' @param table The table to which the constraint is applied
248248
#'
249-
#' @returns A list; see `check_constraints` for details.
249+
#' @returns A `<qf_check_constraint/qf_check>` object
250250
#'
251251
#' @noRd
252252
check_constraint <- function(constraint, table) {
253-
satisfied_rows <- check_constraint_strict(constraint, table)
254-
excepted_rows <- attr(constraint, "exceptions") |>
253+
results <- data.frame(row.names = seq_len(nrow(table)))
254+
results$satisfied <- check_constraint_strict(constraint, table)
255+
results$excepted <- attr(constraint, "exceptions") |>
255256
purrr::map(excepted_rows, table = table) |>
256257
purrr::reduce(`|`, .init = rep(FALSE, nrow(table)))
257-
handled_rows <- satisfied_rows | excepted_rows
258-
structure(list(
259-
satisfied = all(satisfied_rows),
260-
handled = all(handled_rows),
261-
rows = list(
262-
satisfied = satisfied_rows,
263-
excepted = excepted_rows,
264-
handled = handled_rows
265-
)
266-
), class = "qf_report_check", constraint = constraint)
258+
results$handled <- results$satisfied | results$excepted
259+
new_qf_check(
260+
results,
261+
satisfied = all(results$satisfied),
262+
handled = all(results$handled),
263+
constraint = constraint,
264+
subclass = "qf_check_constraint"
265+
)
267266
}
268267

269268
#' Check that a constraint is satisfied, ignoring exceptions
@@ -279,69 +278,6 @@ check_constraint_strict <- function(constraint, table) {
279278
UseMethod("check_constraint_strict")
280279
}
281280

282-
#' @noRd
283-
#' @export
284-
print.qf_report_check <- function(x, max_width = getOption("width"), ...) {
285-
print(attr(x, "constraint"))
286-
if (x$satisfied) {
287-
cat(" All rows satisfied")
288-
} else if (x$handled) {
289-
cat(" All rows satisfied or excepted")
290-
} else {
291-
message <- " Violating rows: "
292-
indices <- format_indices(
293-
which(!x$rows$handled),
294-
max_width - nchar(message)
295-
)
296-
cat0(message, indices)
297-
}
298-
cat("\n")
299-
invisible(x)
300-
}
301-
302-
#' @noRd
303-
#' @export
304-
print.qf_report_check_table <- function(x, ...) {
305-
report_check_header(x)
306-
for (cstr in x) print(cstr)
307-
invisible(x)
308-
}
309-
310-
#' @noRd
311-
#' @export
312-
print.qf_report_check_dataset <- function(x, ...) {
313-
report_check_header(unlist(x, recursive = FALSE))
314-
indent <- " "
315-
for (i in 1:length(x)) {
316-
if (any(vapply(x[[i]], \(el) !el$handled, logical(1)))) {
317-
cat0("=> In table '", names(x)[[i]], "':\n")
318-
for (j in 1:length(x[[i]])) {
319-
if (!x[[i]][[j]]$handled) {
320-
report_check_constraint <- utils::capture.output({
321-
cat0("[[", j, "]] ")
322-
print(x[[i]][[j]], max_width = getOption("width") - nchar(indent))
323-
})
324-
cat(paste0(indent, report_check_constraint), sep = "\n")
325-
}
326-
}
327-
}
328-
}
329-
}
330-
331-
report_check_header <- function(x) {
332-
satisfied <- purrr::map_lgl(x, "satisfied")
333-
handled <- purrr::map_lgl(x, "handled")
334-
n_satisfied <- sum(satisfied)
335-
n_excepted <- sum(handled) - sum(satisfied)
336-
n_violated <- length(x) - sum(handled)
337-
cat("Constraint check report:", n_satisfied, "satisfied", "/", n_excepted,
338-
"excepted", "/", n_violated, "violated:", "\n")
339-
}
340-
341-
342-
343-
# Exception handling ===========================================================
344-
345281
#' Get or set the exceptions of a constraint
346282
#'
347283
#' @param x A constraint object

R/qf_dataset.R

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,12 @@ is_qf_dataset <- function(x) {
6161
#' @rdname check_constraints
6262
#' @export
6363
check_constraints.qf_dataset <- function(x) {
64-
structure(
65-
purrr::map(x, check_constraints),
66-
class = "qf_report_check_dataset"
64+
table_checks <- purrr::map(x, check_constraints)
65+
new_qf_check(
66+
table_checks,
67+
subclass = "qf_check_dataset",
68+
satisfied = purrr::every(table_checks, satisfied),
69+
handled = purrr::every(table_checks, handled)
6770
)
6871
}
6972

R/qf_table.R

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,12 @@ is_qf_table <- function(x) {
6363
#' @rdname check_constraints
6464
#' @export
6565
check_constraints.qf_table <- function(x) {
66-
structure(
67-
purrr::map(constraints(x), check_constraint, x),
68-
class = "qf_report_check_table"
66+
constraint_checks <- purrr::map(constraints(x), check_constraint, x)
67+
new_qf_check(
68+
constraint_checks,
69+
subclass = "qf_check_table",
70+
satisfied = purrr::every(constraint_checks, satisfied),
71+
handled = purrr::every(constraint_checks, satisfied)
6972
)
7073
}
7174

man/inspect_results.Rd

Lines changed: 29 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/testthat/test-qf_constraint.R

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -94,10 +94,10 @@ test_that("constraint checking works", {
9494
# Case 1: Primary key on 'flights' is invalid because of duplicate keys;
9595
# otherwise OK
9696
result <- check_constraints(dset)
97-
expect_true(result$airlines[[1]]$satisfied)
98-
expect_true(result$airlines[[2]]$satisfied)
99-
expect_false(result$flights[[1]]$satisfied)
100-
expect_true(result$flights[[2]]$satisfied)
97+
expect_true(satisfied(result$airlines[[1]]))
98+
expect_true(satisfied(result$airlines[[2]]))
99+
expect_false(satisfied(result$flights[[1]]))
100+
expect_true(satisfied(result$flights[[2]]))
101101

102102
expect_snapshot_output(print(result))
103103

@@ -107,10 +107,10 @@ test_that("constraint checking works", {
107107
dset2 <- dset
108108
dset2$airlines[dset$airlines$carrier == "DL", ] <- NA
109109
result2 <- check_constraints(dset2)
110-
expect_false(result2$airlines[[1]]$satisfied)
111-
expect_false(result2$airlines[[2]]$satisfied)
112-
expect_false(result2$flights[[1]]$satisfied)
113-
expect_false(result2$flights[[2]]$satisfied)
110+
expect_false(satisfied(result2$airlines[[1]]))
111+
expect_false(satisfied(result2$airlines[[2]]))
112+
expect_false(satisfied(result2$flights[[1]]))
113+
expect_false(satisfied(result2$flights[[2]]))
114114

115115
# Foreign key referencing own table:
116116
enneagram <- as_qf_table(data.frame(
@@ -124,8 +124,8 @@ test_that("constraint checking works", {
124124
cstr_foreign_key(integration, .self$number)
125125
)
126126
result9 <- check_constraints(enneagram)
127-
expect_true(result9[[2]]$satisfied)
128-
expect_true(result9[[3]]$satisfied)
127+
expect_true(satisfied(result9[[2]]))
128+
expect_true(satisfied(result9[[3]]))
129129

130130
})
131131

tests/testthat/test-qf_exception.R

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,6 @@ test_that("exceptions work", {
44
except_where(carrier %in% "WN", flight %in% 2269) |
55
except_where(carrier %in% "UA", flight %in% c(207, 236, 258, 635))
66
result <- check_constraints(dset)$flights[[1]]
7-
expect_false(result$satisfied)
8-
expect_true(result$handled)
7+
expect_false(satisfied(result))
8+
expect_true(handled(result))
99
})

0 commit comments

Comments
 (0)