'Using `pool` in an R Shiny package

How can I implement the use of a pool object in an R Shiny application structured like a package?

I have structured my package according to the layout described by Hadley Wickham in "Mastering Shiny:

├── DESCRIPTION
├── NAMESPACE
├── R
  ├── app.R
  ├── modules.R
├── app.R
├── inst
└── tests

My Shiny application lives in R/app.R wrapped in a function called callApp(). The file ./app.R simply calls callApp().

The file modules.R represents actually many .R files that contain various functions and modules that comprise my Shiny application. My Shiny application looks like:

callApp <- function() {
  pool <- pool::dbPool(
    RMySQL::MySQL(),
    dbname = config::get("db_name"),
    host = config::get("db_host"),
    username = Sys.getenv("DB_USERNAME"),
    password = Sys.getenv("DB_PASSWORD")
  )

  onStop(function() {
    pool::poolClose(pool)
  })

  header <- dashboardHeader(
    title = "title"
  )

  sidebar <- dashboardSidebar(
      ....
  )

  body <- dashboardBody(
      ....
  )

  ui <- dashboardPage(header, sidebar, body)

  server <- function(input, output, session) {
    modulefunction_server("module1")
    modulefunction_server("module2")
  }

  shiny::shinyApp(ui, server)
}

As you can see, I am using the pool package to connect to a MySQL datatbase and retrieve my data. I have placed pool at the top of the main app.R as described in the pool documentation. However, the problem I am having is in regards to my function modulefunction_server():

modulefunction_server <- function(id) {

  shiny::moduleServer(id, function(input, output, session) {

      dates <- datepicker_server("datepicker1")

      data <- getData(startDate = dates$date_from(),
                      endDate = dates$date_to())

   
      output$report <- DT::renderDT({
        make_table(data)
      })
   })
 }

It is actually the function getData() that requires the pool object. And getData() is stored in my modules.R file:

getData <- function(startDate, endDate) {
  sql <- getSQLFromFile(system.file("sql","query.sql", package = "mypackage"))
  query <- DBI::sqlInterpolate(pool, sql, startDate = startDate, endDate = endDate)
  DBI::dbGetQuery(pool, query)
}

When I run my application, I receive the error:

 error in evaluating the argument 'conn' in selecting a method for function 'sqlInterpolate': object 'pool' not found

What is the recommended way of providing access to pool for all functions in a Shiny application? I suppose I could pass pool as an argument to each of the functions, but this becomes very confusing and messy when attempting to retrieve data from pool via a function that is nested 5, 10, or 15 functions deep into a complex Shiny application.

UPDATE:

I have found this related GitHub issue and have attempted to implement the solution:

I have moved pool into a globals.R file. I source globals.R at the beginning of my app.R I move the onStop() function into shinyApp() function:

callApp <- function() {
  source("R/globals.R")

  onStop(function() {
    pool::poolClose(pool)
  })

  header <- dashboardHeader(
    title = "title"
  )

  sidebar <- dashboardSidebar(
      ....
  )

  body <- dashboardBody(
      ....
  )

  ui <- dashboardPage(header, sidebar, body)

  server <- function(input, output, session) {
    modulefunction_server("module1")
    modulefunction_server("module2")
  }

  shiny::shinyApp(ui = ui, server = server, onStart = function() {
    onStop(function() {
       pool::poolClose(pool)
     })
  })
}

Unfortunately, while this allows pool to be visible to all functions, I am still left with a Warning in (function (e) : You have a leaked pooled object every time I rebuild my package.



Solution 1:[1]

I chose to use a global variable, bearing in mind I'm working on a Plumber application, not a Shiny application and my application is quite simple so YMMV...

In the top level script where Plumber is instantiated

library(plumber)
library(pool)
library(RMySQL)

# store mysqlPool in global scope,
# knowing this is the entry-point of the application...
mysqlPool <<- dbPool(
    MySQL(),
    # connection info ...
)

pr <- plumb(
    # ...
)

pr$registerHooks(
    list(
        'exit' = function() {
            poolClose(mysqlPool)
        }
    )
)

Then in a lower level "library" script, to access the connection, something like this:

library(RMySQL)

conn <- poolCheckout(mysqlPool)

query <- sqlInterpolate(
    conn,
    "INSERT INTO my_table (field1, field2) VALUES (?field1, ?field2)",
    field1,
    field2
)

dbSendQuery(conn, query)

poolReturn(conn)

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1