orville-postgresql-1.0.0.0: src/Orville/PostgreSQL/Raw/SqlCommenter.hs
{- |
Copyright : Flipstone Technology Partners 2023
License : MIT
Stability : Stable
This module provides the very basics for [sqlcommenter](https://google.github.io/sqlcommenter)
support.
@since 1.0.0.0
-}
module Orville.PostgreSQL.Raw.SqlCommenter
( SqlCommenterAttributes
, addSqlCommenterAttributes
)
where
import qualified Data.List as List
import qualified Data.Map.Strict as Map
import qualified Data.Text as T
import qualified Network.URI as URI
import qualified Orville.PostgreSQL.Raw.RawSql as RawSql
{- | The representation of 'T.Text' key/value pairs for supporting the sqlcommenter specification.
This allows you to attach key/values of 'T.Text' that supporting systems can use for advanced
metrics. See [sqlcommenter](https://google.github.io/sqlcommenter) for details of the
specification.
@since 1.0.0.0
-}
type SqlCommenterAttributes = Map.Map T.Text T.Text
{- | Adds a given @SqlCommenter@ set of key/value 'T.Text' pairs to a 'RawSql.SqlExpression'. This
performs all of the required serialization for the given values. Note that no values are
automatically added here, so any that you may wish to add can be freely set without a name clash
of any kind from this function itself.
@since 1.0.0.0
-}
addSqlCommenterAttributes :: RawSql.SqlExpression a => SqlCommenterAttributes -> a -> a
addSqlCommenterAttributes commenter a =
RawSql.unsafeFromRawSql $
RawSql.toRawSql a
<> keyValueSerializationToRawSql commenter
keyValueSerializationToRawSql :: SqlCommenterAttributes -> RawSql.RawSql
keyValueSerializationToRawSql =
RawSql.fromText . keyValueSerialization
{- | Perform the sqlcommenter serialization on for the whole @SqlCommenter@ map of key/value pairs.
The spec can be found
[here](https://google.github.io/sqlcommenter/spec/#key-value-serialization)
@since 1.0.0.0
-}
keyValueSerialization :: SqlCommenterAttributes -> T.Text
keyValueSerialization =
wrapInSqlComment . addCommasAndConcat . List.sort . fmap concatWithEquals . Map.toList . valueSerialization . keySerialization
addCommasAndConcat :: [T.Text] -> T.Text
addCommasAndConcat [] = T.pack "''"
addCommasAndConcat txts = T.concat $ List.intersperse (T.pack ",") txts
concatWithEquals :: (T.Text, T.Text) -> T.Text
concatWithEquals (k, v) =
k <> T.pack "=" <> v
-- | The spec can be found [here](https://google.github.io/sqlcommenter/spec/#key-serialization)
keySerialization :: SqlCommenterAttributes -> SqlCommenterAttributes
keySerialization =
Map.mapKeys escapeText
-- | The spec can be found [here](https://google.github.io/sqlcommenter/spec/#value-serialization)
valueSerialization :: SqlCommenterAttributes -> SqlCommenterAttributes
valueSerialization =
fmap (wrapInSingleQuote . escapeQuote . escapeText)
-- Here we ensure there is a space before the comment
wrapInSqlComment :: T.Text -> T.Text
wrapInSqlComment txt =
T.pack " /*" <> txt <> T.pack "*/"
wrapInSingleQuote :: T.Text -> T.Text
wrapInSingleQuote txt =
T.pack "'" <> txt <> T.pack "'"
escapeQuote :: T.Text -> T.Text
escapeQuote =
T.replace (T.pack "'") (T.pack "\'")
escapeText :: T.Text -> T.Text
escapeText =
T.pack . escapeStr . T.unpack
escapeStr :: String -> String
escapeStr = URI.escapeURIString URI.isUnescapedInURIComponent