hpgsql-0.1.0.0: src/Hpgsql/Query.hs
module Hpgsql.Query
( Query, -- Do not export constructor
SingleQuery, -- Do not export constructor
sql,
sqlPrep,
mkQuery,
escapeIdentifier,
vALUES,
preparedStatement,
nonPreparedStatement,
-- * Internal
-- $internal
breakQueryIntoStatements,
mkQueryInternal,
encodeParam,
)
where
import Data.ByteString (ByteString)
import qualified Data.ByteString as BS
import qualified Data.List as List
import Data.Proxy (Proxy (..))
import Hpgsql.Builder (BinaryField (..))
import Hpgsql.Encoding (RowEncoder (..), ToPgRow (..))
import Hpgsql.InternalTypes (Query (..), SingleQuery (..), SingleQueryFragment (..), breakQueryIntoStatements)
import Hpgsql.QueryInternal (encodeParam, mkQuery, mkQueryInternal, sql, sqlPrep)
import Hpgsql.TypeInfo (EncodingContext, Oid)
-- $internal
-- These functions are internal implementation details and are not intended for public use.
-- They may change or be removed without notice.
-- | Escapes a database object identifier like a table name or a column name,
-- so it can be embedded inside a @[sql|...|]@ quasiquote using @^{expr}@ syntax:
--
-- > [sql| SELECT ^{escapeIdentifier "çolumñ"} FROM ^{escapeIdentifier "sChEmA"}.some_table |]
escapeIdentifier :: ByteString -> Query
escapeIdentifier v = Query {queryString = [FragmentOfStaticSql "\"", FragmentOfStaticSql (doubleQuotes v), FragmentOfStaticSql "\""], queryParams = [], isPrepared = False}
where
doubleQuotes = BS.intercalate "\"\"" . BS.split 0x22 {- '"' -}
-- | Generates a query like @VALUES ($1,$2), ($3,$4)@ from a list of rows.
-- Can be embedded inside a @[sql|...|]@ quasiquote using @^{expr}@ syntax:
--
-- > [sql| INSERT INTO emp(id,name) ^{vALUES rows} ON CONFLICT DO NOTHING |]
vALUES :: forall a. (ToPgRow a) => [a] -> Query
vALUES [] =
let rowOids = rowEncoder.toTypeOids (Proxy @a)
nullParams :: [EncodingContext -> (Maybe Oid, BinaryField)]
nullParams = map (\f -> \encCtx -> (f encCtx, SqlNull)) rowOids
in [sql|(SELECT * FROM (VALUES ^{commaSeparatedRowTuples [nullParams]}) _subq LIMIT 0)|]
vALUES rows =
let allParams = map rowEncoder.toPgParams rows
in "VALUES " <> commaSeparatedRowTuples allParams
commaSeparatedRowTuples :: [[EncodingContext -> (Maybe Oid, BinaryField)]] -> Query
commaSeparatedRowTuples rowTuples =
let (_, queryFragsPerRow) =
List.mapAccumR
( \(!maxArgSoFar) singleRow ->
let numParams = length singleRow
numberedArgs = map (QueryArgumentPlaceHolder . (+ maxArgSoFar)) [1 .. numParams]
in (maxArgSoFar + numParams, FragmentOfStaticSql "(" : (List.intersperse (FragmentOfStaticSql ",") numberedArgs ++ [FragmentOfStaticSql ")"]))
)
0
rowTuples
in Query {queryString = mconcat $ List.intersperse [FragmentOfStaticSql ","] queryFragsPerRow, queryParams = mconcat rowTuples, isPrepared = False}
-- | Turns a `Query` into a prepared `Query`, i.e. every SQL statement
-- in the `Query` will be a prepared statement.
preparedStatement :: Query -> Query
preparedStatement Query {..} = Query {isPrepared = True, ..}
-- | Turns a `Query` into a non-prepared `Query`, i.e. every SQL statement
-- in the `Query` will not be a prepared statement.
nonPreparedStatement :: Query -> Query
nonPreparedStatement Query {..} = Query {isPrepared = False, ..}