{-# 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 -- | 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 (connEscapeName sqlBackend (DBName "")) 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)