R/Query.R

#' This class handles an SQL Query.
#'
#' This class represents an SQL query.
#'
#' @examples
#' # No example provided, as this class is abstract.
#'
#' @import R6
#' @export
Query <- R6::R6Class("Query",
  public = list(

    #' @description
    #' Initializer.
    #' @param stmts A character vector of statement class names.
    #'              It describes the accepted statements and their order, using
    #'              wildcards to indicate if a statement is optional, or if it
    #'              is allowed to occur multiple times. Example:
    #'                c("Select", "From", "Join*", "Where?", "Limit?")
    #' @return Nothing.
    initialize = function(stmts) {

      # Statements order
      private$set_statements(stmts)

      # Initialize list of statement objects
      private$stmts <- list()
      private$current_card <- rep(0L, length(private$stmt_card))
      names(private$current_card) <- names(private$stmt_card)

      return(invisible(NULL))
    },

    #' @description
    #' Add a statement.
    #' @param stmt The statement to add.
    #' @return Nothing.
    add = function(stmt) {
      chk::chk_is(stmt, "Statement")

      # Check if statement is allowed
      cls <- class(stmt)
      cls <- cls[which(cls %in% private$stmt_order)]
      if (length(cls) == 0L)
        stop(class(stmt)[1L], " is not in allowed statements: ",
             toString(private$stmt_order), ".", call. = FALSE)
      else if (length(cls) > 1L)
        stop("Multiple classes (", toString(cls), ") of ", class(stmt)[1L],
             " matches allowed statements: ",
             toString(private$stmt_order), ".", call. = FALSE)

      # Check cardinality
      if (private$current_card[cls] == 1L && private$stmt_card[cls] != "*")
        stop("Statement ", cls, " is not allowed multiple times.",
             call. = FALSE)
      private$current_card[cls] <- private$current_card[cls] + 1L

      private$stmts <- c(private$stmts, stmt)
      return(invisible(NULL))
    },

    #' @description
    #' Generates the string representation of this query.
    #' @return A string containing the full SQL query.
    toString = function() {

      # Check required statements
      req <- names(private$stmt_card)[private$stmt_card == "1"]
      miss <- req[private$current_card[req] == 0L]
      if (length(miss) > 0L)
        stop("The following required statements are missing: ",
             toString(missing), ".", call. = FALSE)

      # Sort statements
      ordered_stmts <- rep(list(list()), length(private$stmt_order))
      names(ordered_stmts) <- private$stmt_order
      for (s in private$stmts) {
        cls <- class(s)
        cls <- cls[which(cls %in% private$stmt_order)]
        ordered_stmts[[cls]] <- c(ordered_stmts[[cls]], s)
      }

      # Convert statements to tokens
      tokens <- list()
      i <- 0L
      for (stmt in unlist(ordered_stmts, recursive=FALSE)) {
          if (i > 0L)
            tokens <- c(tokens, .spc)
          tokens <- c(tokens, stmt$getTokens())
          i <- i + 1L
      }
      tokens <- c(tokens, .semicolon)

      return(paste(vapply(tokens, function(x) x$toString(), FUN.VALUE = ""),
                   collapse = ""))
    }
  ),
  private = list(
    stmts = NULL,
    stmt_order = NULL,
    stmt_card = NULL,
    current_card = NULL,

    set_statements = function(stmts) {
      chk::chk_character(stmts)

      # Add prefix
      stmts <- gsub("^", "Stmt", stmts)

      # Get order of statements
      private$stmt_order <- gsub("[*?]", "", stmts)

      # Get cardinality of statements
      card <- gsub("^[A-Za-z]+", "", stmts)
      card[!nzchar(card)] <- "1"  # Default is 1
      names(card) <- private$stmt_order
      private$stmt_card <- card
    }
  )
)

Try the sqlq package in your browser

Any scripts or data that you put into this service are public.

sqlq documentation built on Sept. 16, 2025, 9:10 a.m.