content: {-# LANGUAGE OverloadedStrings #-}
  {-# LANGUAGE RecordWildCards #-}
  
  -- | This is an internal module. This module may have breaking changes without
  -- a corresponding major version bump. If you use this module, please open an
  -- issue with your use-case so we can safely support it.
  module Database.Esqueleto.Internal.ExprParser where
  
  import Prelude hiding (takeWhile)
  
  import Control.Applicative ((<|>))
  import Control.Monad (void)
  import Data.Attoparsec.Text
  import Data.Set (Set)
  import qualified Data.Set as Set
  import Data.Text (Text)
  import qualified Data.Text as Text
  import Database.Persist.Sql
  import Database.Persist.SqlBackend
  
  -- | A type representing the access of a table value. In Esqueleto, we get
  -- a guarantee that the access will look something like:
  --
  -- @
  -- escape-char [character] escape-char . escape-char [character] escape-char
  --             ^^^^^^^^^^^                           ^^^^^^^^^^^
  --             table name                            column name
  -- @
  data TableAccess = TableAccess
      { tableAccessTable  :: Text
      , tableAccessColumn :: Text
      }
      deriving (Eq, Ord, Show)
  
  -- | Parse a @SqlExpr (Value Bool)@'s textual representation into a list of
  -- 'TableAccess'
  parseOnExpr :: SqlBackend -> Text -> Either String (Set TableAccess)
  parseOnExpr sqlBackend text = do
      c <- mkEscapeChar sqlBackend
      parseOnly (onExpr c) text
  
  -- | This function uses the 'connEscapeName' function in the 'SqlBackend' with an
  -- empty identifier to pull out an escape character. This implementation works
  -- with postgresql, mysql, and sqlite backends.
  mkEscapeChar :: SqlBackend -> Either String Char
  mkEscapeChar sqlBackend =
      case Text.uncons (getEscapedRawName "" sqlBackend) of
          Nothing ->
              Left "Failed to get an escape character from the SQL backend."
          Just (c, _) ->
              Right c
  
  type ExprParser a = Char -> Parser a
  
  onExpr :: ExprParser (Set TableAccess)
  onExpr e = Set.fromList <$> many' tableAccesses
    where
      tableAccesses = do
          skipToEscape e <?> "Skipping to an escape char"
          parseTableAccess e <?> "Parsing a table access"
  
  skipToEscape :: ExprParser ()
  skipToEscape escapeChar = void (takeWhile (/= escapeChar))
  
  parseEscapedIdentifier :: ExprParser [Char]
  parseEscapedIdentifier escapeChar = do
      _ <- char escapeChar
      str <- parseEscapedChars escapeChar
      _ <- char escapeChar
      pure str
  
  parseTableAccess :: ExprParser TableAccess
  parseTableAccess ec = do
      tableAccessTable <- Text.pack <$> parseEscapedIdentifier ec
      _ <- char '.'
      tableAccessColumn <- Text.pack <$> parseEscapedIdentifier ec
      pure TableAccess {..}
  
  parseEscapedChars :: ExprParser [Char]
  parseEscapedChars escapeChar = go
    where
      twoEscapes = char escapeChar *> char escapeChar
      go = many' (notChar escapeChar <|> twoEscapes)
  