-
Notifications
You must be signed in to change notification settings - Fork 0
perf: verdict lazy par agregats SQL, plus de collect integral des booleens #50
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,33 +18,87 @@ eq_col_counts <- function(tbl, col, ref_suffix, na_equal) { | |
| list(n = length(b), n_failed = sum(!b)) | ||
| } | ||
|
|
||
| # (n, n_failed) for every boolean validation column of a LAZY table, computed | ||
| # in ONE SQL aggregate scan: SUM(CASE WHEN col THEN 0 ELSE 1 END) counts the | ||
| # not-TRUE values (FALSE and NULL alike), matching the local reducers | ||
| # (tolerance: is.na | !ok; equality: !b, NA-free by construction). This is | ||
| # what lets the lazy verdict avoid collecting N x columns booleans into R. | ||
| # Returns a named list keyed by the UNDERLYING column name: | ||
| # list(<col> = list(n, n_failed), ...). | ||
| lazy_boolean_counts <- function(tbl_lazy, tol_cols, eq_cols) { | ||
| con <- dbplyr::remote_con(tbl_lazy) | ||
| sub <- dbplyr::sql_render(tbl_lazy) | ||
| q <- function(x) as.character(DBI::dbQuoteIdentifier(con, x = x)) | ||
|
|
||
| bool_cols <- c(datadiff_ok_col(tol_cols), datadiff_eq_col(eq_cols)) | ||
| underlying <- c(tol_cols, eq_cols) | ||
| nf_exprs <- vapply(X = seq_along(bool_cols), FUN = function(i) { | ||
| sprintf("SUM(CASE WHEN %s THEN 0 ELSE 1 END) AS %s", | ||
| q(bool_cols[i]), q(sprintf("..datadiff_nf_%d", i))) | ||
| }, FUN.VALUE = character(1)) | ||
|
|
||
| sql <- sprintf( | ||
| "SELECT COUNT(*) AS %s%s FROM (%s) AS %s", | ||
| q("..datadiff_total"), | ||
| if (length(nf_exprs) > 0) { | ||
| paste0(", ", paste(nf_exprs, collapse = ", ")) | ||
| } else { | ||
| "" | ||
| }, | ||
| sub, q("datadiff_counts") | ||
| ) | ||
| row <- DBI::dbGetQuery(con, statement = sql) | ||
|
|
||
| total <- as.numeric(row[["..datadiff_total"]]) | ||
| counts <- vector("list", length(underlying)) | ||
| names(counts) <- underlying | ||
| for (i in seq_along(underlying)) { | ||
| nf <- as.numeric(row[[sprintf("..datadiff_nf_%d", i)]]) | ||
| # SUM over an empty table is NULL: zero rows means zero failures | ||
| counts[[i]] <- list(n = total, n_failed = if (is.na(nf)) { | ||
| 0 | ||
| } else { | ||
| nf | ||
| }) | ||
| } | ||
| counts | ||
| } | ||
|
|
||
| #' Build a faithful coverage summary of the checks performed | ||
| #' | ||
| #' @param tbl Local data.frame holding the boolean validation columns (and, on | ||
| #' the local path, the raw + reference columns for equality recomputation). | ||
| #' Ignored when `counts` is supplied. | ||
| #' @param tol_cols,eq_cols Character vectors of tolerance / equality columns. | ||
| #' @param missing_in_candidate,type_mismatch_cols Structural failures. | ||
| #' @param row_validation_info List with `check_count`. | ||
| #' @param row_count_ok Logical row-count outcome. | ||
| #' @param ref_suffix Suffix identifying reference columns. | ||
| #' @param na_equal Logical; NA equality semantics for equality columns. | ||
| #' @param counts Optional precomputed per-column counts (named list | ||
| #' `col -> list(n, n_failed)` covering every tolerance and equality column), | ||
| #' as produced by `lazy_boolean_counts()`: the lazy path aggregates in SQL | ||
| #' instead of collecting the booleans. | ||
| #' @return A `datadiff_coverage` data.frame with columns `column`, `check`, | ||
| #' `n`, `n_failed`, `status` (one row per check performed). | ||
| #' @noRd | ||
| build_coverage <- function(tbl, tol_cols, eq_cols, | ||
| missing_in_candidate, type_mismatch_cols, | ||
| row_validation_info, row_count_ok, | ||
| ref_suffix, na_equal) { | ||
| ref_suffix, na_equal, counts = NULL) { | ||
| columns <- character(0) | ||
| checks <- character(0) | ||
| ns <- integer(0) | ||
| n_failed <- integer(0) | ||
| ns <- numeric(0) | ||
| n_failed <- numeric(0) | ||
|
|
||
| add <- function(column, check, n, nf) { | ||
| columns <<- c(columns, column) | ||
| checks <<- c(checks, check) | ||
| ns <<- c(ns, as.integer(n)) | ||
| n_failed <<- c(n_failed, as.integer(nf)) | ||
| # numeric, not integer: the lazy counts come from SQL aggregates and can | ||
| # exceed 2^31 - 1, where as.integer() would overflow to NA and silently | ||
| # corrupt the coverage and the verdict | ||
| ns <<- c(ns, as.numeric(n)) | ||
| n_failed <<- c(n_failed, as.numeric(nf)) | ||
| } | ||
|
|
||
| # Existence checks: every common (non-type-mismatch) column gets a col_exists | ||
|
|
@@ -54,11 +108,12 @@ build_coverage <- function(tbl, tol_cols, eq_cols, | |
| add(c, "col_exists", 1L, 0L) | ||
| } | ||
| for (c in tol_cols) { | ||
| cnt <- tol_col_counts(tbl, col = c) | ||
| cnt <- counts[[c]] %||% tol_col_counts(tbl, col = c) | ||
| add(c, "tolerance", cnt$n, cnt$n_failed) | ||
| } | ||
| for (c in eq_cols) { | ||
| cnt <- eq_col_counts(tbl, col = c, ref_suffix = ref_suffix, na_equal = na_equal) | ||
| cnt <- counts[[c]] %||% | ||
| eq_col_counts(tbl, col = c, ref_suffix = ref_suffix, na_equal = na_equal) | ||
| add(c, "equality", cnt$n, cnt$n_failed) | ||
|
Comment on lines
+111
to
117
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Valide et applique : les compteurs de coverage sont stockes en numeric (as.numeric) - as.integer debordait a NA au-dela de 2^31-1, precisement atteignable maintenant que les comptes viennent d'agregats SQL sur des tables que R n'aurait pas pu collecter. Les consommateurs (all(n_failed == 0L), rapport, summary) fonctionnent a l'identique sur des doubles. |
||
| } | ||
| for (c in missing_in_candidate) { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Valides tous les deux, et le second cachait un vrai bug au-dela de l'aller-retour inutile : sur un echec purement structurel (check_count off), l'agent recevait une table vide, les steps dummy interrogeaient 0 unite et passaient - pointblank::all_passed() aurait contredit la coverage (en pratique le cas crashait avant : Query contains no columns). Corrige : garde val_cols vide (aucun compute), garde red_cols vide avec seed d'UNE ligne pour porter les unites en echec des steps structurels, 2 tests de regression (echec structurel seul, comparaison cle-seule).