Skip to content

Commit

Permalink
#4 rework privilege methods
Browse files Browse the repository at this point in the history
- add new as_priv s3 class
- rework how to make privileges
- add auto execute sql code with code from jqr
- import more rlang fxns
  • Loading branch information
sckott committed Nov 8, 2024
1 parent 2ad5c83 commit 6fd941a
Show file tree
Hide file tree
Showing 22 changed files with 636 additions and 217 deletions.
4 changes: 3 additions & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ Imports:
rlang,
RPostgres,
tibble,
dbplyr
dbplyr,
cli,
magrittr
Suggests:
knitr,
rmarkdown,
Expand Down
23 changes: 19 additions & 4 deletions NAMESPACE
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
# Generated by roxygen2: do not edit by hand

S3method(as_priv,privilege)
S3method(as_priv,tbl_sql)
S3method(print,privilege)
S3method(print,rls_policy)
export("%>%")
export(as_priv)
export(auto_pipe)
export(from)
export(grant)
export(has_postgres)
export(on)
export(revoke)
export(rls_check_status)
export(rls_col_policy)
export(rls_column_privileges)
Expand All @@ -18,24 +24,33 @@ export(rls_list_roles)
export(rls_permissions)
export(rls_policies)
export(rls_privileges)
export(rls_run)
export(rls_table_privileges)
export(rls_tbl)
export(to)
export(translate_privilege)
export(user)
import(dbplyr)
importFrom(DBI,dbExecute)
importFrom(DBI,dbGetQuery)
importFrom(RPostgres,Postgres)
importFrom(cli,ansi_collapse)
importFrom(cli,cat_line)
importFrom(cli,cli_abort)
importFrom(cli,format_error)
importFrom(dbplyr,sql)
importFrom(dplyr,"%>%")
importFrom(dplyr,filter)
importFrom(dplyr,tbl)
importFrom(glue,glue)
importFrom(glue,glue_safe)
importFrom(glue,glue_sql)
importFrom(magrittr,"%>%")
importFrom(rlang,abort)
importFrom(rlang,as_name)
importFrom(rlang,caller_arg)
importFrom(rlang,caller_env)
importFrom(rlang,enquo)
importFrom(rlang,enquos)
importFrom(rlang,has_length)
importFrom(rlang,is_character)
importFrom(rlang,is_empty)
importFrom(rlang,names2)
importFrom(tibble,as_tibble)
47 changes: 47 additions & 0 deletions R/as_priv.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#' As privilege
#' @param x some input
#' @export
as_priv <- function(x) {
UseMethod("as_priv")
}
#' @export
as_priv.privilege <- function(x) {
return(x)
}
#' @export
as_priv.tbl_sql <- function(x) {
tmp <- list(
data = x,
user = NULL,
privilege = list(),
type = NULL
)
structure(tmp, class = "privilege")
}
#' @export
#' @importFrom cli cat_line
print.privilege <- function(x, ...) {
cat_line("<privilege>")
if (!is_really_empty(x$user)) cat_me("user", x$user)
if (!is_really_empty(x$privilege)) {
cat_me("type", x$type)
for (i in x$privilege) {
cat_me(x=i$commands, y=i$cols %|||% "<all cols>", indent = " ")
}
}
print(x$data)
}

cat_me <- function(x, y, indent = " ") {
y <- paste0(y, collapse = ", ")
cat_line(glue("{indent}{x}: {y}", .trim = FALSE))
}

rls_grant <- function(commands, cols) {
x <- list(commands = commands, cols = cols)
structure(x, class = "rls_grant")
}
rls_revoke <- function(commands, cols) {
x <- list(commands = commands, cols = cols)
structure(x, class = "rls_revoke")
}
1 change: 1 addition & 0 deletions R/globals.R
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Generated by roxyglobals: do not edit by hand

utils::globalVariables(c(
".rls_exitfun", # <pipeline_on_exit>
"privilege_type", # <rls_tbl>
"%like%", # <rls_list_roles>
"rolname", # <rls_list_roles>
Expand Down
3 changes: 3 additions & 0 deletions R/onload.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.onLoad <- function(...) {
rls_env$auto_pipe <- FALSE
}
115 changes: 115 additions & 0 deletions R/pipeline.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
## Adapted from @smbache Stefan Milton Bache's work in jqr

rls_env <- new.env()

#' Pipe operator
#'
#' @name %>%
#' @rdname pipe
#' @keywords internal
#' @export
#' @importFrom magrittr %>%
#' @usage lhs \%>\% rhs
NULL

#' Turn on or off auto execution of sql commands
#'
#' @export
#' @param x (logical) turn on auto excecution of SQL commands (`TRUE`),
#' or turn off (`FALSE`)
#' @return NULL
auto_pipe <- function(x = FALSE) {
assert_is(x, "logical")
assert_len(x, 1)
rls_env$auto_pipe <- x
}

#' Information on Potential Pipeline
#'
#' This function figures out whether it is called from within a pipeline.
#' It does so by examining the parent evironment of the active system frames,
#' and whether any of these are the same as the enclosing environment of
#' `\%>\%`.
#'
#' @return A list with the values `is_piped` (logical) and `env`
#' (an environment reference). The former is `TRUE` if a pipeline is
#' identified as `FALSE` otherwise. The latter holds a reference to
#' the `\%>\%` frame where the pipeline is created and evaluated.
#'
#' @noRd
pipeline_info <- function() {
parents <- lapply(sys.frames(), parent.env)

is_magrittr_env <-
vapply(parents, identical, logical(1), y = environment(`%>%`))

is_piped <- any(is_magrittr_env)

list(is_piped = is_piped,
env = if (is_piped) sys.frames()[[max(which(is_magrittr_env))]])
}

#' Toggle Auto Execution On or Off for Pipelines
#'
#' A call to `pipe_autoexec` allows a function to toggle auto execution of
#' `jq` on or off at the end of a pipeline.
#'
#' @param toggle logical: `TRUE` toggles auto execution on, `FALSE`
#' toggles auto execution off.
#'
#' @details Once auto execution is turned on the `result` identifier inside
#' the pipeline is bound to an "Active Binding". This will not be changed on
#' toggling auto execution off, but rather the function to be executed is
#' changed to `identity`.
#'
#' @noRd
pipe_autoexec <- function(toggle) {
if (!identical(toggle, TRUE) && !identical(toggle, FALSE)) {
stop("Argument 'toggle' must be logical.")
}

info <- pipeline_info()

if (isTRUE(info[["is_piped"]])) {
rls_exit <- function(x) if (inherits(x, "privilege")) rls_run(x@data$src$con, x) else x
pipeline_on_exit(info$env)
info$env$.rls_exitfun <- if (toggle) rls_exit else identity
}

invisible()
}

#' Setup On-Exit Action for a Pipeline
#'
#' A call to `pipeline_on_exit` will setup the pipeline for auto
#' execution by overriding the return value from an `on.exit`
#' expression pushed in the magrittr frame.
#'
#' @param env A reference to the `\%>\%` environment.
#' @global .rls_exitfun
#' @noRd
pipeline_on_exit <- function(env) {
# Only activate the first time; after this the binding is already active.
if (exists(".rls_exitfun", envir = env, inherits = FALSE, mode = "function")) {
return(invisible())
}
env$.rls_exitfun <- identity

# Need to be a bit careful with scoping since `env` is foreign. We
# inline closures in calls with `do.call()` and `as.call()`. Usage
# of `do.call()` instead of `eval()` is necessary so that
# `on.exit()`, `return()`, and `returnValue()` are evaluated in the
# correct environment upstack and not in the duplicate call frame
# that `eval()` pushes on the stack.
exit_clo <- function() {
out <- do.call(returnValue, list(), envir = env)

# Will be `NULL` in case of error. This doesn't matter here since
# we only modify return values that inherit from `"jqr"`.
if (!is.null(out)) {
do.call("return", alist(.rls_exitfun(returnValue())), envir = env)
}
}

do.call(on.exit, list(as.call(list(exit_clo)), add = TRUE), envir = env)
}
Loading

0 comments on commit 6fd941a

Please sign in to comment.