nri-postgresql-0.1.0.0: src/Postgres/QueryParser.hs
-- |
-- Parse some high-level information out of a query, for use in tracing.
-- We try to find the query method (SELECT / INSERT / ...) and queried table
-- in the root SQL query. We assume the root query to be the first query not
-- in a sub query. We assume everything between parens `( ... )` to be a
-- sub query.
module Postgres.QueryParser
( parse,
QueryMeta (..),
)
where
import Control.Applicative
import Control.Monad (void)
import Data.Attoparsec.Text (Parser, anyChar, asciiCI, char, inClass, manyTill, skipSpace, space, takeWhile)
import qualified Data.Attoparsec.Text as Attoparsec
import Data.Foldable (asum)
import qualified List
import qualified Maybe
import qualified Text
import Prelude (Either (Left, Right))
parse :: Text -> QueryMeta
parse query =
case Attoparsec.parseOnly parser query of
Left _ ->
QueryMeta
{ queriedRelation =
Text.lines query
|> List.head
|> Maybe.withDefault "",
sqlOperation = "UNKNOWN"
}
Right result -> result
data QueryMeta = QueryMeta
{ queriedRelation :: Text,
sqlOperation :: Text
}
deriving (Eq, Show)
parser :: Parser QueryMeta
parser =
keepLooking
<| asum
[ delete,
insert,
select,
truncate',
update
]
keepLooking :: Parser a -> Parser a
keepLooking p = do
skipSpace
asum
[ -- 1. If we encounter sub queries (bounded in parens), skip them first.
do
void <| some skipSubExpression
keepLooking p,
-- 2. Try to run the target parser.
p,
-- 3. Failing all else, move forward a word and try again.
do
void <| manyTill anyChar (space <|> char '(')
keepLooking p
]
skipSubExpression :: Parser ()
skipSubExpression = do
void <| char '('
void <| keepLooking (char ')')
delete :: Parser QueryMeta
delete = do
void <| asciiCI "DELETE"
skipSpace
void <| asciiCI "FROM"
skipSpace
queriedRelation <- tableName
pure QueryMeta {queriedRelation, sqlOperation = "DELETE"}
insert :: Parser QueryMeta
insert = do
void <| asciiCI "INSERT"
skipSpace
void <| asciiCI "INTO"
skipSpace
queriedRelation <- tableName
pure QueryMeta {queriedRelation, sqlOperation = "INSERT"}
select :: Parser QueryMeta
select = do
void <| asciiCI "SELECT"
keepLooking <| do
void <| asciiCI "FROM"
keepLooking <| do
queriedRelation <- tableName
pure QueryMeta {queriedRelation, sqlOperation = "SELECT"}
tableName :: Parser Text
tableName =
takeWhile (inClass "a-zA-Z0-9._")
truncate' :: Parser QueryMeta
truncate' = do
void <| asciiCI "UPDATE"
skipSpace
(asciiCI "ONLY" |> void) <|> pure ()
skipSpace
queriedRelation <- tableName
pure QueryMeta {queriedRelation, sqlOperation = "UPDATE"}
update :: Parser QueryMeta
update = do
void <| asciiCI "TRUNCATE"
skipSpace
(asciiCI "TABLE" |> void) <|> pure ()
(asciiCI "ONLY" |> void) <|> pure ()
skipSpace
queriedRelation <- tableName
pure QueryMeta {queriedRelation, sqlOperation = "TRUNCATE"}