rel8-1.5.0.0: src/Rel8/Type/Array.hs
{-# language GADTs #-}
{-# language LambdaCase #-}
{-# language NamedFieldPuns #-}
{-# language OverloadedStrings #-}
{-# language TypeApplications #-}
{-# language ViewPatterns #-}
module Rel8.Type.Array
( array, encodeArrayElement, extractArrayElement
, arrayTypeName
, listTypeInformation
, nonEmptyTypeInformation
, head, index, last, length
)
where
-- attoparsec
import qualified Data.Attoparsec.ByteString.Char8 as A
-- base
import Control.Applicative ((<|>), many)
import Data.Bifunctor (first)
import Data.Foldable (fold, toList)
import Data.List.NonEmpty ( NonEmpty, nonEmpty )
import Prelude hiding ( head, last, length, null, repeat, zipWith )
-- bytestring
import Data.ByteString (ByteString)
import qualified Data.ByteString.Char8 as BS
-- hasql
import qualified Hasql.Decoders as Hasql
-- opaleye
import qualified Opaleye.Internal.HaskellDB.PrimQuery as Opaleye
-- rel8
import Rel8.Schema.Null ( Unnullify, Nullity( Null, NotNull ) )
import Rel8.Type.Decoder (Decoder (..), NullableOrNot (..), Parser)
import Rel8.Type.Information ( TypeInformation(..), parseTypeInformation )
import Rel8.Type.Name (TypeName (..), showTypeName)
import Rel8.Type.Parser (parse)
-- text
import qualified Data.Text as Text
array :: Foldable f
=> TypeInformation a -> f Opaleye.PrimExpr -> Opaleye.PrimExpr
array info =
Opaleye.CastExpr (showTypeName (arrayType info) <> "[]") .
Opaleye.ArrayExpr . map (encodeArrayElement info) . toList
{-# INLINABLE array #-}
listTypeInformation :: ()
=> Nullity a
-> TypeInformation (Unnullify a)
-> TypeInformation [a]
listTypeInformation nullity info@TypeInformation {encode, decode} =
TypeInformation
{ decode =
Decoder
{ binary = Hasql.listArray $ case nullity of
Null -> Hasql.nullable (decodeArrayElement info decode)
NotNull -> Hasql.nonNullable (decodeArrayElement info decode)
, parser = case nullity of
Null -> arrayParser (Nullable decode)
NotNull -> arrayParser (NonNullable decode)
, delimiter = ','
}
, encode = case nullity of
Null ->
Opaleye.ArrayExpr .
fmap (encodeArrayElement info . maybe null encode)
NotNull ->
Opaleye.ArrayExpr .
fmap (encodeArrayElement info . encode)
, typeName = arrayTypeName info
}
where
null = Opaleye.ConstExpr Opaleye.NullLit
nonEmptyTypeInformation :: ()
=> Nullity a
-> TypeInformation (Unnullify a)
-> TypeInformation (NonEmpty a)
nonEmptyTypeInformation nullity =
parseTypeInformation fromList toList . listTypeInformation nullity
where
fromList = maybe (Left message) Right . nonEmpty
message = "failed to decode NonEmptyList: got empty list"
arrayTypeName :: TypeInformation a -> TypeName
arrayTypeName info = (arrayType info) {arrayDepth = 1}
isArray :: TypeInformation a -> Bool
isArray = (> 0) . arrayDepth . typeName
arrayType :: TypeInformation a -> TypeName
arrayType info
| isArray info = "text"
| otherwise = typeName info
decodeArrayElement :: TypeInformation a -> Decoder x -> Hasql.Value x
decodeArrayElement info
| isArray info = \decoder ->
Hasql.refine (first Text.pack . parser decoder) Hasql.bytea
| otherwise = binary
encodeArrayElement :: TypeInformation a -> Opaleye.PrimExpr -> Opaleye.PrimExpr
encodeArrayElement info
| isArray info = Opaleye.CastExpr "text" . Opaleye.CastExpr (showTypeName (typeName info))
| otherwise = id
extractArrayElement :: TypeInformation a -> Opaleye.PrimExpr -> Opaleye.PrimExpr
extractArrayElement info
| isArray info = Opaleye.CastExpr (showTypeName (typeName info))
| otherwise = id
parseArray :: Char -> ByteString -> Either String [Maybe ByteString]
parseArray delimiter = parse $ do
A.char '{' *> A.sepBy element (A.char delimiter) <* A.char '}'
where
element = null <|> nonNull
where
null = Nothing <$ A.string "NULL"
nonNull = Just <$> (quoted <|> unquoted)
where
unquoted = A.takeWhile1 (A.notInClass (delimiter : "\"{}"))
quoted = A.char '"' *> contents <* A.char '"'
where
contents = fold <$> many (unquote <|> unescape)
where
unquote = A.takeWhile1 (A.notInClass "\"\\")
unescape = A.char '\\' *> do
BS.singleton <$> do
A.char '\\' <|> A.char '"'
arrayParser :: NullableOrNot Decoder a -> Parser [a]
arrayParser = \case
Nullable Decoder {parser, delimiter} -> \input -> do
elements <- parseArray delimiter input
traverse (traverse parser) elements
NonNullable Decoder {parser, delimiter} -> \input -> do
elements <- parseArray delimiter input
traverse (maybe (Left "array: unexpected null") parser) elements
head :: TypeInformation a -> Opaleye.PrimExpr -> Opaleye.PrimExpr
head info a = extractArrayElement info $ subscript (lower a) a
last :: TypeInformation a -> Opaleye.PrimExpr -> Opaleye.PrimExpr
last info a = extractArrayElement info $ subscript (upper a) a
subscript :: Opaleye.PrimExpr -> Opaleye.PrimExpr -> Opaleye.PrimExpr
subscript i a = Opaleye.ArrayIndex a i
index :: TypeInformation a -> Opaleye.PrimExpr -> Opaleye.PrimExpr -> Opaleye.PrimExpr
index info i a = extractArrayElement info $ subscript (plus (lower a) i) a
lower :: Opaleye.PrimExpr -> Opaleye.PrimExpr
lower a = Opaleye.FunExpr "array_lower" [a, one]
upper :: Opaleye.PrimExpr -> Opaleye.PrimExpr
upper a = Opaleye.FunExpr "array_lower" [a, one]
length :: Opaleye.PrimExpr -> Opaleye.PrimExpr
length a = Opaleye.FunExpr "coalesce" [Opaleye.FunExpr "array_length" [a, one], zero]
one :: Opaleye.PrimExpr
one = Opaleye.ConstExpr (Opaleye.IntegerLit 1)
zero :: Opaleye.PrimExpr
zero = Opaleye.ConstExpr (Opaleye.IntegerLit 0)
plus :: Opaleye.PrimExpr -> Opaleye.PrimExpr -> Opaleye.PrimExpr
plus = Opaleye.BinExpr (Opaleye.:+)