Skip to content

Commit

Permalink
#4 more tabular work for privileges and row policies
Browse files Browse the repository at this point in the history
- example code for passwd table turned into a fxn to do one liner for table setup
- use one liner setup in privileges and row policy fxn docs
- rework rls_run to fetch con from query input if con not passed, also separate file
-
  • Loading branch information
sckott committed Nov 12, 2024
1 parent 5d98b0a commit b0f8b7b
Show file tree
Hide file tree
Showing 20 changed files with 185 additions and 31 deletions.
6 changes: 6 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Generated by roxygen2: do not edit by hand

S3method(as_con,PqConnection)
S3method(as_con,row_policy)
S3method(as_priv,privilege)
S3method(as_priv,tbl_sql)
S3method(as_row_policy,row_policy)
Expand Down Expand Up @@ -35,6 +37,7 @@ export(rls_tbl)
export(row_policy)
export(rows_existing)
export(rows_new)
export(setup_example_table)
export(to)
export(translate_privilege)
import(dbplyr)
Expand All @@ -45,10 +48,12 @@ importFrom(cli,ansi_collapse)
importFrom(cli,cat_line)
importFrom(cli,cli_abort)
importFrom(cli,format_error)
importFrom(dbplyr,copy_inline)
importFrom(dbplyr,sql)
importFrom(dbplyr,translate_sql)
importFrom(dplyr,"%>%")
importFrom(dplyr,filter)
importFrom(dplyr,rows_append)
importFrom(dplyr,tbl)
importFrom(glue,glue)
importFrom(glue,glue_safe)
Expand All @@ -65,3 +70,4 @@ importFrom(rlang,is_empty)
importFrom(rlang,is_scalar_character)
importFrom(rlang,quo_is_null)
importFrom(tibble,as_tibble)
importFrom(tibble,tribble)
1 change: 1 addition & 0 deletions R/as_priv.R
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#' As privilege
#' @param x some input
#' @export
#' @return an object of S3 class "privilege"
as_priv <- function(x) {
UseMethod("as_priv")
}
Expand Down
1 change: 1 addition & 0 deletions R/as_row_policy.R
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#' As row policy
#' @param x some input
#' @export
#' @return an object of S3 class "row_policy"
as_row_policy <- function(x) {
UseMethod("as_row_policy")
}
Expand Down
49 changes: 47 additions & 2 deletions R/examples.R
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
#' Setup for running examples throughout this package
#'
#' @name passwd
#' @aliases passwd
#' @export
#' @importFrom tibble tribble
#' @importFrom dbplyr copy_inline
#' @importFrom dplyr rows_append
#' @param con a postgres or redshift connection object
#' @param which (character) the table to create. only option right
#' now is "passwd"
#' @examplesIf interactive() && has_postgres()
#' library(RPostgres)
#' library(dplyr)
Expand Down Expand Up @@ -41,4 +48,42 @@
#'
#' ## Check that the data is in the table
#' tbl(con, "passwd")
NULL
setup_example_table <- function(con, which = "passwd") {
options <- c("passwd")
if (!which %in% options) {
rls_abort(
format_error("{.arg {which}} must be one of {clz_col(options)}")
)
}
dbExecute(con, eg_schemas[[which]])
rows_append(
tbl(con, which),
copy_inline(con, eg_data[[which]]),
in_place = TRUE
)
tbl(con, which)
}

eg_schemas <- list(
passwd = "
CREATE TABLE passwd (
user_name text UNIQUE NOT NULL,
pwhash text,
uid int PRIMARY KEY,
gid int NOT NULL,
real_name text NOT NULL,
home_phone text,
home_dir text NOT NULL,
shell text NOT NULL
);
"
)

eg_data <- list(
passwd = tribble(
~user_name, ~pwhash, ~uid, ~gid, ~real_name, ~home_phone, ~home_dir,~shell,
"admin", "xxx", 0, 0, "Admin", "111-222-3333", "/root", "/bin/dash",
"bob", "xxx", 1, 1, "Bob", "123-456-7890", "/home/bob", "/bin/zsh",
"alice", "xxx", 2, 1, "Alice", "098-765-4321", "/home/alice", "/bin/zsh"
)
)
8 changes: 7 additions & 1 deletion R/pipeline.R
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,13 @@ pipe_autoexec <- function(toggle) {
info <- pipeline_info()

if (isTRUE(info[["is_piped"]])) {
rls_exit <- function(x) if (inherits(x, c("privilege", "row_policy"))) rls_run(x$data$src$con, x) else x
rls_exit <- function(x) {
if (inherits(x, c("privilege", "row_policy"))) {
rls_run(x, x$data$src$con)
} else {
x
}
}
pipeline_on_exit(info$env)
info$env$.rls_exitfun <- if (toggle) rls_exit else identity
}
Expand Down
30 changes: 13 additions & 17 deletions R/privileges.R
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,12 @@
#' @param ... one of all, select, update, insert, delete
#' @param cols (character) vector of column names
#' @examplesIf interactive() && has_postgres()
#' library(DBI)
#' library(RPostgres)
#' con <- dbConnect(Postgres())
#' if (!dbExistsTable(con, "passwd")) {
#' setup_example_table(con, "passwd")
#' }
#'
#' rls_tbl(con, "passwd") %>%
#' grant(update) %>%
Expand Down Expand Up @@ -37,8 +41,12 @@ grant <- function(.data, ..., cols = NULL) {
#' @export
#' @inheritParams grant
#' @examplesIf interactive() && has_postgres()
#' library(DBI)
#' library(RPostgres)
#' con <- dbConnect(Postgres())
#' if (!dbExistsTable(con, "passwd")) {
#' setup_example_table(con, "passwd")
#' }
#'
#' rls_tbl(con, "passwd") %>%
#' revoke(update) %>%
Expand Down Expand Up @@ -66,8 +74,13 @@ revoke <- function(.data, ..., cols = NULL) {
#' @param .data a `privilege` object
#' @param ... (character) one or more user (or role) names
#' @examplesIf interactive() && has_postgres()
#' library(DBI)
#' library(RPostgres)
#' con <- dbConnect(Postgres())
#' if (!dbExistsTable(con, "passwd")) {
#' setup_example_table(con, "passwd")
#' }
#'
#' rls_tbl(con, "passwd") %>% to(jane)
#' rls_tbl(con, "passwd") %>% to(jane, bob, alice)
to <- function(.data, ...) {
Expand Down Expand Up @@ -132,8 +145,6 @@ from <- to
#' dbExecute(con, sql)
translate_privilege <- function(priv, con) {
assert_is(priv, "privilege")
# stopifnot("Can not use grant and revoke" =
# xor(!is_empty(priv$grant), !is_empty(priv$revoke)))

template <- priv_templates[[priv$type]]

Expand Down Expand Up @@ -161,21 +172,6 @@ priv_templates <- list(
revoke = "REVOKE %s ON %s FROM %s"
)

#' Run a query
#'
#' @export
#' @param query an s3 object of class `privilege` or `row_policy, required
#' @param con DBI connection object, required
rls_run <- function(con, query) {
is_conn(con)
assert_is(query, c("privilege", "row_policy"))
sql <- switch(class(query),
privilege = translate_privilege(query, con),
row_policy = translate_row_policy(query, con)
)
dbExecute(con, sql)
}

rls_grant <- function(commands, cols) {
x <- list(commands = commands, cols = cols)
structure(x, class = "rls_grant")
Expand Down
34 changes: 31 additions & 3 deletions R/row_policy.R
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,15 @@
#' @inheritParams grant
#' @param name (character) scalar name for the policy. required
#' @examplesIf interactive() && has_postgres()
#' library(DBI)
#' library(RPostgres)
#' con <- dbConnect(Postgres())
#' if (!dbExistsTable(con, "passwd")) {
#' setup_example_table(con, "passwd")
#' }
#' rls_tbl(con, "passwd") %>%
#' row_policy("my_policy")
#' row_policy("my_policy") %>%
#' rls_run()
row_policy <- function(.data, name) {
pipe_autoexec(toggle = rls_env$auto_pipe)
assert_is(name, "character")
Expand All @@ -22,8 +27,12 @@ row_policy <- function(.data, name) {
#' @export
#' @inheritParams grant
#' @examplesIf interactive() && has_postgres()
#' library(DBI)
#' library(RPostgres)
#' con <- dbConnect(Postgres())
#' if (!dbExistsTable(con, "passwd")) {
#' setup_example_table(con, "passwd")
#' }
#' rls_tbl(con, "passwd") %>%
#' row_policy("my_policy") %>%
#' commands(update)
Expand All @@ -42,8 +51,12 @@ commands <- function(.data, ...) {
#' @param sql (character) sql syntax to use for existing rows
#' @details Use either `using` or `sql`, not both
#' @examplesIf interactive() && has_postgres()
#' library(DBI)
#' library(RPostgres)
#' con <- dbConnect(Postgres())
#' if (!dbExistsTable(con, "passwd")) {
#' setup_example_table(con, "passwd")
#' }
#' rls_tbl(con, "passwd") %>%
#' row_policy("my_policy") %>%
#' commands(update) %>%
Expand Down Expand Up @@ -72,8 +85,12 @@ rows_existing <- function(.data, using = NULL, sql = NULL) {
#' @param sql (character) sql syntax to use for new rows
#' @details Use either `check` or `sql`, not both
#' @examplesIf interactive() && has_postgres()
#' library(DBI)
#' library(RPostgres)
#' con <- dbConnect(Postgres())
#' if (!dbExistsTable(con, "passwd")) {
#' setup_example_table(con, "passwd")
#' }
#'
#' rls_tbl(con, "passwd") %>%
#' row_policy("a_policy") %>%
Expand Down Expand Up @@ -102,11 +119,22 @@ rows_new <- function(.data, check = NULL, sql = NULL) {
.data
}

#' @keywords internal
as_con <- function(x) {
assert_is(x, "row_policy")
x$data$src$con
UseMethod("as_con")
}
#' @export
as_con.row_policy <- function(x) {
return(x$data$src$con)
}
#' @export
as_con.PqConnection <- function(x) {
return(x)
}

#' @note param `fun` takes a function, by default uses a function
#' that simply returns whatever is passed in to it
#' @noRd
combine_if <- function(statement, item, fun = \(x) x) {
ifelse(!rlang::is_null(item), paste(statement, fun(item)), "")
}
Expand Down
19 changes: 19 additions & 0 deletions R/run.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#' Run a query
#'
#' @export
#' @param query an s3 object of class `privilege` or `row_policy, required.
#' if `con` is not supplied, we attempt to get the connection
#' from `query`; if it is not found we try to use a value passed to `con`.
#' @param con DBI connection object, optional, see `query`
#' @return error from PostgreSQL or Redshift upon error, or an integer
#' value
rls_run <- function(query, con = NULL) {
assert_is(query, c("privilege", "row_policy"))
con <- as_con(query %||% con)
is_conn(con)
sql <- switch(class(query),
privilege = translate_privilege(query, con),
row_policy = translate_row_policy(query, con)
)
dbExecute(con, sql)
}
6 changes: 4 additions & 2 deletions R/utils.R
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
is_conn <- function(con) {
stopifnot(!inherits(con, "DBIConnection") ==
"con must be of class DBIConnection")
stopifnot(
"con must be of class DBIConnection" =
inherits(con, "DBIConnection")
)
}

compact <- function(x) {
Expand Down
3 changes: 3 additions & 0 deletions man/as_priv.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions man/as_row_policy.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions man/commands.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions man/grant.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions man/revoke.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 8 additions & 4 deletions man/rls_run.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit b0f8b7b

Please sign in to comment.