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 :: ByteString -> Query
escapeIdentifier ByteString
v = Query {queryString :: [SingleQueryFragment]
queryString = [ByteString -> SingleQueryFragment
FragmentOfStaticSql ByteString
"\"", ByteString -> SingleQueryFragment
FragmentOfStaticSql (ByteString -> ByteString
doubleQuotes ByteString
v), ByteString -> SingleQueryFragment
FragmentOfStaticSql ByteString
"\""], queryParams :: [EncodingContext -> (Maybe Oid, BinaryField)]
queryParams = [], isPrepared :: Bool
isPrepared = Bool
False}
  where
    doubleQuotes :: ByteString -> ByteString
doubleQuotes = ByteString -> [ByteString] -> ByteString
BS.intercalate ByteString
"\"\"" ([ByteString] -> ByteString)
-> (ByteString -> [ByteString]) -> ByteString -> ByteString
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Word8 -> ByteString -> [ByteString]
BS.split Word8
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 :: forall a. ToPgRow a => [a] -> Query
vALUES [] =
  let rowOids :: [EncodingContext -> Maybe Oid]
rowOids = RowEncoder a
forall a. ToPgRow a => RowEncoder a
rowEncoder.toTypeOids (forall t. Proxy t
forall {k} (t :: k). Proxy t
Proxy @a)
      nullParams :: [EncodingContext -> (Maybe Oid, BinaryField)]
      nullParams :: [EncodingContext -> (Maybe Oid, BinaryField)]
nullParams = ((EncodingContext -> Maybe Oid)
 -> EncodingContext -> (Maybe Oid, BinaryField))
-> [EncodingContext -> Maybe Oid]
-> [EncodingContext -> (Maybe Oid, BinaryField)]
forall a b. (a -> b) -> [a] -> [b]
map (\EncodingContext -> Maybe Oid
f -> \EncodingContext
encCtx -> (EncodingContext -> Maybe Oid
f EncodingContext
encCtx, BinaryField
SqlNull)) [EncodingContext -> Maybe Oid]
rowOids
   in [sql|(SELECT * FROM (VALUES ^{commaSeparatedRowTuples [nullParams]}) _subq LIMIT 0)|]
vALUES [a]
rows =
  let allParams :: [[EncodingContext -> (Maybe Oid, BinaryField)]]
allParams = (a -> [EncodingContext -> (Maybe Oid, BinaryField)])
-> [a] -> [[EncodingContext -> (Maybe Oid, BinaryField)]]
forall a b. (a -> b) -> [a] -> [b]
map RowEncoder a
forall a. ToPgRow a => RowEncoder a
rowEncoder.toPgParams [a]
rows
   in Query
"VALUES " Query -> Query -> Query
forall a. Semigroup a => a -> a -> a
<> [[EncodingContext -> (Maybe Oid, BinaryField)]] -> Query
commaSeparatedRowTuples [[EncodingContext -> (Maybe Oid, BinaryField)]]
allParams

commaSeparatedRowTuples :: [[EncodingContext -> (Maybe Oid, BinaryField)]] -> Query
commaSeparatedRowTuples :: [[EncodingContext -> (Maybe Oid, BinaryField)]] -> Query
commaSeparatedRowTuples [[EncodingContext -> (Maybe Oid, BinaryField)]]
rowTuples =
  let (Int
_, [[SingleQueryFragment]]
queryFragsPerRow) =
        (Int
 -> [EncodingContext -> (Maybe Oid, BinaryField)]
 -> (Int, [SingleQueryFragment]))
-> Int
-> [[EncodingContext -> (Maybe Oid, BinaryField)]]
-> (Int, [[SingleQueryFragment]])
forall (t :: * -> *) s a b.
Traversable t =>
(s -> a -> (s, b)) -> s -> t a -> (s, t b)
List.mapAccumR
          ( \(!Int
maxArgSoFar) [EncodingContext -> (Maybe Oid, BinaryField)]
singleRow ->
              let numParams :: Int
numParams = [EncodingContext -> (Maybe Oid, BinaryField)] -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length [EncodingContext -> (Maybe Oid, BinaryField)]
singleRow
                  numberedArgs :: [SingleQueryFragment]
numberedArgs = (Int -> SingleQueryFragment) -> [Int] -> [SingleQueryFragment]
forall a b. (a -> b) -> [a] -> [b]
map (Int -> SingleQueryFragment
QueryArgumentPlaceHolder (Int -> SingleQueryFragment)
-> (Int -> Int) -> Int -> SingleQueryFragment
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
maxArgSoFar)) [Int
1 .. Int
numParams]
               in (Int
maxArgSoFar Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
numParams, ByteString -> SingleQueryFragment
FragmentOfStaticSql ByteString
"(" SingleQueryFragment
-> [SingleQueryFragment] -> [SingleQueryFragment]
forall a. a -> [a] -> [a]
: (SingleQueryFragment
-> [SingleQueryFragment] -> [SingleQueryFragment]
forall a. a -> [a] -> [a]
List.intersperse (ByteString -> SingleQueryFragment
FragmentOfStaticSql ByteString
",") [SingleQueryFragment]
numberedArgs [SingleQueryFragment]
-> [SingleQueryFragment] -> [SingleQueryFragment]
forall a. [a] -> [a] -> [a]
++ [ByteString -> SingleQueryFragment
FragmentOfStaticSql ByteString
")"]))
          )
          Int
0
          [[EncodingContext -> (Maybe Oid, BinaryField)]]
rowTuples
   in Query {queryString :: [SingleQueryFragment]
queryString = [[SingleQueryFragment]] -> [SingleQueryFragment]
forall a. Monoid a => [a] -> a
mconcat ([[SingleQueryFragment]] -> [SingleQueryFragment])
-> [[SingleQueryFragment]] -> [SingleQueryFragment]
forall a b. (a -> b) -> a -> b
$ [SingleQueryFragment]
-> [[SingleQueryFragment]] -> [[SingleQueryFragment]]
forall a. a -> [a] -> [a]
List.intersperse [ByteString -> SingleQueryFragment
FragmentOfStaticSql ByteString
","] [[SingleQueryFragment]]
queryFragsPerRow, queryParams :: [EncodingContext -> (Maybe Oid, BinaryField)]
queryParams = [[EncodingContext -> (Maybe Oid, BinaryField)]]
-> [EncodingContext -> (Maybe Oid, BinaryField)]
forall a. Monoid a => [a] -> a
mconcat [[EncodingContext -> (Maybe Oid, BinaryField)]]
rowTuples, isPrepared :: Bool
isPrepared = Bool
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
preparedStatement Query {Bool
[SingleQueryFragment]
[EncodingContext -> (Maybe Oid, BinaryField)]
queryString :: Query -> [SingleQueryFragment]
queryParams :: Query -> [EncodingContext -> (Maybe Oid, BinaryField)]
isPrepared :: Query -> Bool
queryString :: [SingleQueryFragment]
queryParams :: [EncodingContext -> (Maybe Oid, BinaryField)]
isPrepared :: Bool
..} = Query {isPrepared :: Bool
isPrepared = Bool
True, [SingleQueryFragment]
[EncodingContext -> (Maybe Oid, BinaryField)]
queryString :: [SingleQueryFragment]
queryParams :: [EncodingContext -> (Maybe Oid, BinaryField)]
queryString :: [SingleQueryFragment]
queryParams :: [EncodingContext -> (Maybe Oid, BinaryField)]
..}

-- | 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
nonPreparedStatement Query {Bool
[SingleQueryFragment]
[EncodingContext -> (Maybe Oid, BinaryField)]
queryString :: Query -> [SingleQueryFragment]
queryParams :: Query -> [EncodingContext -> (Maybe Oid, BinaryField)]
isPrepared :: Query -> Bool
queryString :: [SingleQueryFragment]
queryParams :: [EncodingContext -> (Maybe Oid, BinaryField)]
isPrepared :: Bool
..} = Query {isPrepared :: Bool
isPrepared = Bool
False, [SingleQueryFragment]
[EncodingContext -> (Maybe Oid, BinaryField)]
queryString :: [SingleQueryFragment]
queryParams :: [EncodingContext -> (Maybe Oid, BinaryField)]
queryString :: [SingleQueryFragment]
queryParams :: [EncodingContext -> (Maybe Oid, BinaryField)]
..}