{-# LANGUAGE MagicHash #-}
-- | A value of any SQL Server type, for code that handles columns and
-- parameters whose types are only known at runtime: serving any table,
-- copying rows between tables, a REPL.
--
-- Decoding: 'SqlValue' is a 'Hmssql.Decoding.FromSqlField' that accepts
-- any column ('Hmssql.Decoding.sqlValueDecoder'), and 'SqlRow' a whole row
-- with its columns ('Hmssql.Decoding.sqlRowDecoder',
-- 'Hmssql.Decoding.sqlRowDecoderFor'); 'Hmssql.Query.queryMulti' and
-- 'Hmssql.Query.callProcedure' return every result set as 'SqlRow's.  A
-- @sql_variant@ decodes as its base type.
--
-- Encoding: 'SqlValue' is a 'Hmssql.Encoding.ToSqlField' sent as the type
-- its constructor names, so @SELECT \@p1@ gives the same value back;
-- 'SqlNull' is an @nvarchar(max)@ NULL, which the server converts to most
-- types but not to @binary@ or @sql_variant@.
module Hmssql.SqlValue
  ( SqlValue (..)
  , SqlRow (..)
  ) where

import qualified Data.ByteString as B
import Data.Decimal (Decimal)
import Data.Int (Int16, Int32, Int64)
import Data.Text (Text)
import Data.Time (Day, LocalTime, TimeOfDay)
import Data.UUID.Types (UUID)
import Data.Word (Word8)

import Hmssql.Internal.Derived
import Hmssql.Tds.Primitives (DateTimeOffset, Money)
import Hmssql.Tds.Token (ColumnInfo)

-- | One constructor per family of SQL Server types.
data SqlValue
  = SqlNull
  | SqlBool !Bool
    -- ^ @bit@
  | SqlWord8 !Word8
    -- ^ @tinyint@: an unsigned value from 0 through 255
  | SqlInt16 !Int16
    -- ^ @smallint@
  | SqlInt32 !Int32
    -- ^ @int@
  | SqlInt64 !Int64
    -- ^ @bigint@
  | SqlFloat !Float
    -- ^ @real@
  | SqlDouble !Double
    -- ^ @float@
  | SqlDecimal !Decimal
    -- ^ @decimal@, @numeric@
  | SqlMoney !Money
    -- ^ @money@, @smallmoney@
  | SqlGuid !UUID
    -- ^ @uniqueidentifier@
  | SqlDateTime !LocalTime
    -- ^ @datetime@, @smalldatetime@, @datetime2@
  | SqlDate !Day
    -- ^ @date@
  | SqlTime !TimeOfDay
    -- ^ @time@
  | SqlDateTimeOffset !DateTimeOffset
    -- ^ @datetimeoffset@
  | SqlText !Text
    -- ^ The character types: @char@, @varchar@, @text@ (decoded by the
    -- collation's code page), @nchar@, @nvarchar@, @ntext@, and @json@ and
    -- @vector@, which arrive as character types
  | SqlBinary !B.ByteString
    -- ^ @binary@, @varbinary@, @image@, @rowversion@
  | SqlXml !Text
    -- ^ @xml@

instance Show SqlValue where
  showsPrec :: Int -> SqlValue -> ShowS
showsPrec Int
p = \case
    SqlValue
SqlNull -> String -> ShowS
showString String
"SqlNull"
    SqlBool Bool
a -> Int -> Addr# -> Bool -> ShowS
forall a. Show a => Int -> Addr# -> a -> ShowS
showCon1 Int
p Addr#
"SqlBool"# Bool
a
    SqlWord8 Word8
a -> Int -> Addr# -> Word8 -> ShowS
forall a. Show a => Int -> Addr# -> a -> ShowS
showCon1 Int
p Addr#
"SqlWord8"# Word8
a
    SqlInt16 Int16
a -> Int -> Addr# -> Int16 -> ShowS
forall a. Show a => Int -> Addr# -> a -> ShowS
showCon1 Int
p Addr#
"SqlInt16"# Int16
a
    SqlInt32 Int32
a -> Int -> Addr# -> Int32 -> ShowS
forall a. Show a => Int -> Addr# -> a -> ShowS
showCon1 Int
p Addr#
"SqlInt32"# Int32
a
    SqlInt64 Int64
a -> Int -> Addr# -> Int64 -> ShowS
forall a. Show a => Int -> Addr# -> a -> ShowS
showCon1 Int
p Addr#
"SqlInt64"# Int64
a
    SqlFloat Float
a -> Int -> Addr# -> Float -> ShowS
forall a. Show a => Int -> Addr# -> a -> ShowS
showCon1 Int
p Addr#
"SqlFloat"# Float
a
    SqlDouble Double
a -> Int -> Addr# -> Double -> ShowS
forall a. Show a => Int -> Addr# -> a -> ShowS
showCon1 Int
p Addr#
"SqlDouble"# Double
a
    SqlDecimal Decimal
a -> Int -> Addr# -> Decimal -> ShowS
forall a. Show a => Int -> Addr# -> a -> ShowS
showCon1 Int
p Addr#
"SqlDecimal"# Decimal
a
    SqlMoney Money
a -> Int -> Addr# -> Money -> ShowS
forall a. Show a => Int -> Addr# -> a -> ShowS
showCon1 Int
p Addr#
"SqlMoney"# Money
a
    SqlGuid UUID
a -> Int -> Addr# -> UUID -> ShowS
forall a. Show a => Int -> Addr# -> a -> ShowS
showCon1 Int
p Addr#
"SqlGuid"# UUID
a
    SqlDateTime LocalTime
a -> Int -> Addr# -> LocalTime -> ShowS
forall a. Show a => Int -> Addr# -> a -> ShowS
showCon1 Int
p Addr#
"SqlDateTime"# LocalTime
a
    SqlDate Day
a -> Int -> Addr# -> Day -> ShowS
forall a. Show a => Int -> Addr# -> a -> ShowS
showCon1 Int
p Addr#
"SqlDate"# Day
a
    SqlTime TimeOfDay
a -> Int -> Addr# -> TimeOfDay -> ShowS
forall a. Show a => Int -> Addr# -> a -> ShowS
showCon1 Int
p Addr#
"SqlTime"# TimeOfDay
a
    SqlDateTimeOffset DateTimeOffset
a -> Int -> Addr# -> DateTimeOffset -> ShowS
forall a. Show a => Int -> Addr# -> a -> ShowS
showCon1 Int
p Addr#
"SqlDateTimeOffset"# DateTimeOffset
a
    SqlText Text
a -> Int -> Addr# -> Text -> ShowS
forall a. Show a => Int -> Addr# -> a -> ShowS
showCon1 Int
p Addr#
"SqlText"# Text
a
    SqlBinary ByteString
a -> Int -> Addr# -> ByteString -> ShowS
forall a. Show a => Int -> Addr# -> a -> ShowS
showCon1 Int
p Addr#
"SqlBinary"# ByteString
a
    SqlXml Text
a -> Int -> Addr# -> Text -> ShowS
forall a. Show a => Int -> Addr# -> a -> ShowS
showCon1 Int
p Addr#
"SqlXml"# Text
a
  {-# OPAQUE showsPrec #-}

instance Eq SqlValue where
  SqlValue
x == :: SqlValue -> SqlValue -> Bool
== SqlValue
y = case (SqlValue
x, SqlValue
y) of
    (SqlValue
SqlNull, SqlValue
SqlNull) -> Bool
True
    (SqlBool Bool
a, SqlBool Bool
a') -> Bool
a Bool -> Bool -> Bool
forall a. Eq a => a -> a -> Bool
== Bool
a'
    (SqlWord8 Word8
a, SqlWord8 Word8
a') -> Word8
a Word8 -> Word8 -> Bool
forall a. Eq a => a -> a -> Bool
== Word8
a'
    (SqlInt16 Int16
a, SqlInt16 Int16
a') -> Int16
a Int16 -> Int16 -> Bool
forall a. Eq a => a -> a -> Bool
== Int16
a'
    (SqlInt32 Int32
a, SqlInt32 Int32
a') -> Int32
a Int32 -> Int32 -> Bool
forall a. Eq a => a -> a -> Bool
== Int32
a'
    (SqlInt64 Int64
a, SqlInt64 Int64
a') -> Int64
a Int64 -> Int64 -> Bool
forall a. Eq a => a -> a -> Bool
== Int64
a'
    (SqlFloat Float
a, SqlFloat Float
a') -> Float
a Float -> Float -> Bool
forall a. Eq a => a -> a -> Bool
== Float
a'
    (SqlDouble Double
a, SqlDouble Double
a') -> Double
a Double -> Double -> Bool
forall a. Eq a => a -> a -> Bool
== Double
a'
    (SqlDecimal Decimal
a, SqlDecimal Decimal
a') -> Decimal -> Decimal -> Bool
forall a. Eq a => a -> a -> Bool
eq Decimal
a Decimal
a'
    (SqlMoney Money
a, SqlMoney Money
a') -> Money -> Money -> Bool
forall a. Eq a => a -> a -> Bool
eq Money
a Money
a'
    (SqlGuid UUID
a, SqlGuid UUID
a') -> UUID -> UUID -> Bool
forall a. Eq a => a -> a -> Bool
eq UUID
a UUID
a'
    (SqlDateTime LocalTime
a, SqlDateTime LocalTime
a') -> LocalTime -> LocalTime -> Bool
forall a. Eq a => a -> a -> Bool
eq LocalTime
a LocalTime
a'
    (SqlDate Day
a, SqlDate Day
a') -> Day -> Day -> Bool
forall a. Eq a => a -> a -> Bool
eq Day
a Day
a'
    (SqlTime TimeOfDay
a, SqlTime TimeOfDay
a') -> TimeOfDay -> TimeOfDay -> Bool
forall a. Eq a => a -> a -> Bool
eq TimeOfDay
a TimeOfDay
a'
    (SqlDateTimeOffset DateTimeOffset
a, SqlDateTimeOffset DateTimeOffset
a') -> DateTimeOffset -> DateTimeOffset -> Bool
forall a. Eq a => a -> a -> Bool
eq DateTimeOffset
a DateTimeOffset
a'
    (SqlText Text
a, SqlText Text
a') -> Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
eq Text
a Text
a'
    (SqlBinary ByteString
a, SqlBinary ByteString
a') -> ByteString -> ByteString -> Bool
forall a. Eq a => a -> a -> Bool
eq ByteString
a ByteString
a'
    (SqlXml Text
a, SqlXml Text
a') -> Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
eq Text
a Text
a'
    (SqlValue, SqlValue)
_ -> Bool
False
  {-# OPAQUE (==) #-}

-- | A row of 'SqlValue's with the columns they came from.
data SqlRow = SqlRow
  { SqlRow -> [ColumnInfo]
sqlRowColumns :: ![ColumnInfo]
    -- ^ The result set's columns, one per value.
  , SqlRow -> [SqlValue]
sqlRowValues :: ![SqlValue]
  }

instance Show SqlRow where
  showsPrec :: Int -> SqlRow -> ShowS
showsPrec Int
p (SqlRow [ColumnInfo]
a [SqlValue]
b) =
    Int -> Addr# -> [Field] -> ShowS
showRecord Int
p Addr#
"SqlRow"# [Addr#
"sqlRowColumns"# Addr# -> [ColumnInfo] -> Field
forall a. Show a => Addr# -> a -> Field
:= [ColumnInfo]
a, Addr#
"sqlRowValues"# Addr# -> [SqlValue] -> Field
forall a. Show a => Addr# -> a -> Field
:= [SqlValue]
b]
  {-# OPAQUE showsPrec #-}

instance Eq SqlRow where
  SqlRow [ColumnInfo]
a [SqlValue]
b == :: SqlRow -> SqlRow -> Bool
== SqlRow [ColumnInfo]
a' [SqlValue]
b' = [ColumnInfo] -> [ColumnInfo] -> Bool
forall a. Eq a => a -> a -> Bool
eq [ColumnInfo]
a [ColumnInfo]
a' Bool -> Bool -> Bool
&& [SqlValue] -> [SqlValue] -> Bool
forall a. Eq a => a -> a -> Bool
eq [SqlValue]
b [SqlValue]
b'
  {-# OPAQUE (==) #-}