{-# LANGUAGE
    DataKinds
  , MagicHash
  , OverloadedLabels
  , OverloadedStrings
  , TypeApplications
  , TypeOperators
#-}

module Squeal.PostgreSQL.QuerySpec where

import Data.Function
import Generics.SOP hiding (from)
import Test.Hspec

import Squeal.PostgreSQL

spec :: Spec
spec = do
  let
    qry `queryRenders` str =
      renderManipulation (queryStatement qry) `shouldBe` str
  it "correctly renders a simple SELECT query" $ do
    let
      statement :: Query Tables '[] SumAndCol1
      statement =
        select ((#col1 + #col2) `As` #sum :* #col1 `As` #col1 :* Nil)
        (from (Table (#table1 `As` #table1)) & where_ (#col1 .> #col2))
    statement `queryRenders`
      "SELECT (col1 + col2) AS sum, col1 AS col1\
      \ FROM table1 AS table1 WHERE (col1 > col2);"
  it "combines WHEREs using AND" $ do
    let
      statement :: Query Tables '[] SumAndCol1
      statement =
        select ((#col1 + #col2) `As` #sum :* #col1 `As` #col1 :* Nil)
        (from (Table (#table1 `As` #table1)) & where_ true & where_ false)
    statement `queryRenders`
      "SELECT (col1 + col2) AS sum, col1 AS col1\
      \ FROM table1 AS table1 WHERE (TRUE AND FALSE);"
  it "performs sub SELECTs" $ do
    let
      statement :: Query Tables '[] SumAndCol1
      statement =
        selectStar (from (Subquery
          (select ((#col1 + #col2) `As` #sum :* #col1 `As` #col1 :* Nil)
            (from (Table (#table1 `As` #table1))) `As` #sub)))
    statement `queryRenders`
      "SELECT * FROM\
      \ (SELECT (col1 + col2) AS sum, col1 AS col1\
      \ FROM table1 AS table1) AS sub;"
  it "does LIMIT clauses" $ do
    let
      statement :: Query Tables '[] Columns
      statement = selectStar (from (Table (#table1 `As` #table1)) & limit 1)
    statement `queryRenders` "SELECT * FROM table1 AS table1 LIMIT 1;"
  it "should use the minimum of given LIMITs" $ do
    let
      statement :: Query Tables '[] Columns
      statement =
        selectStar (from (Table (#table1 `As` #table1)) & limit 1 & limit 2)
    statement `queryRenders` "SELECT * FROM table1 AS table1 LIMIT 1;"
  it "should render parameters using $ signs" $ do
    let
      statement :: Query Tables '[ 'Required ('NotNull 'PGbool)] Columns
      statement = selectStar
        (from (Table (#table1 `As` #table1)) & where_ (param @1))
    statement `queryRenders`
      "SELECT * FROM table1 AS table1 WHERE ($1 :: bool);"
  it "does OFFSET clauses" $ do
    let
      statement :: Query Tables '[] Columns
      statement =
        selectStar (from (Table (#table1 `As` #table1)) & offset 1)
    statement `queryRenders` "SELECT * FROM table1 AS table1 OFFSET 1;"
  it "should use the sum of given OFFSETs" $ do
    let
      statement :: Query Tables '[] Columns
      statement =  selectStar
        (from (Table (#table1 `As` #table1)) & offset 1 & offset 2)
    statement `queryRenders` "SELECT * FROM table1 AS table1 OFFSET 3;"
  it "should render GROUP BY and HAVING clauses" $ do
    let
      statement :: Query Tables '[] SumAndCol1
      statement =
        select (sum_ #col2 `As` #sum :* #col1 `As` #col1 :* Nil)
        ( from (Table (#table1 `As` #table1))
          & group (By #col1 :* Nil) 
          & having (#col1 + sum_ #col2 .> 1) )
    statement `queryRenders`
      "SELECT sum(col2) AS sum, col1 AS col1\
      \ FROM table1 AS table1\
      \ GROUP BY col1\
      \ HAVING ((col1 + sum(col2)) > 1);"
  describe "JOINs" $ do
    it "should render CROSS JOINs" $ do
      let
        statement :: Query JoinTables '[] ValueColumns
        statement = select
          ( #orders ! #orderVal `As` #orderVal :*
            #customers ! #customerVal `As` #customerVal :*
            #shippers ! #shipperVal `As` #shipperVal :* Nil )
          ( from (Table (#orders `As` #orders)
            & CrossJoin (Table (#customers `As` #customers))
            & CrossJoin (Table (#shippers `As` #shippers))) )
      statement `queryRenders`
        "SELECT\
        \ orders.orderVal AS orderVal,\
        \ customers.customerVal AS customerVal,\
        \ shippers.shipperVal AS shipperVal\
        \ FROM orders AS orders\
        \ CROSS JOIN customers AS customers\
        \ CROSS JOIN shippers AS shippers;"
    it "should render INNER JOINs" $ do
      let
        statement :: Query JoinTables '[] ValueColumns
        statement = select
          ( (#orders ! #orderVal) `As` #orderVal
            :* (#customers ! #customerVal) `As` #customerVal
            :* (#shippers ! #shipperVal) `As` #shipperVal :* Nil)
          ( from (Table (#orders `As` #orders)
            & InnerJoin (Table (#customers `As` #customers))
              ((#orders ! #customerID) .== (#customers ! #customerID))
            & InnerJoin (Table (#shippers `As` #shippers))
              ((#orders ! #shipperID) .== (#shippers ! #shipperID))))
      statement `queryRenders`
        "SELECT\
        \ orders.orderVal AS orderVal,\
        \ customers.customerVal AS customerVal,\
        \ shippers.shipperVal AS shipperVal\
        \ FROM orders AS orders\
        \ INNER JOIN customers AS customers\
        \ ON (orders.customerID = customers.customerID)\
        \ INNER JOIN shippers AS shippers\
        \ ON (orders.shipperID = shippers.shipperID);"
    it "should render self JOINs" $ do
      let
        statement :: Query JoinTables '[] OrderColumns
        statement = selectDotStar #orders1
          (from (Table (#orders `As` #orders1)
            & CrossJoin (Table (#orders `As` #orders2))))
      statement `queryRenders`
        "SELECT orders1.*\
        \ FROM orders AS orders1\
        \ CROSS JOIN orders AS orders2;"

type Columns =
  '[ "col1" ::: 'Required ('NotNull 'PGint4)
   , "col2" ::: 'Required ('NotNull 'PGint4)
   ]
type Tables = '[ "table1" ::: Columns ]
type SumAndCol1 =
  '[ "sum" ::: 'Required ('NotNull 'PGint4)
   , "col1" ::: 'Required ('NotNull 'PGint4)
   ]
type StudentsColumns = '["name" ::: 'Required ('NotNull 'PGtext)]
type StudentsTable = '["students" ::: StudentsColumns]
type OrderColumns =
  '[ "orderID"    ::: 'Required ('NotNull 'PGint4)
   , "orderVal"   ::: 'Required ('NotNull 'PGtext)
   , "customerID" ::: 'Required ('NotNull 'PGint4)
   , "shipperID"  ::: 'Required ('NotNull 'PGint4)
   ]
type CustomerColumns =
  '[ "customerID" ::: 'Required ('NotNull 'PGint4)
   , "customerVal" ::: 'Required ('NotNull 'PGfloat4)
   ]
type ShipperColumns =
  '[ "shipperID" ::: 'Required ('NotNull 'PGint4)
   , "shipperVal" ::: 'Required ('NotNull 'PGbool)
   ]
type JoinTables =
  '[ "orders"    ::: OrderColumns
   , "customers" ::: CustomerColumns
   , "shippers"  ::: ShipperColumns
   ]
type ValueColumns =
  '[ "orderVal"    ::: 'Required ('NotNull 'PGtext)
   , "customerVal" ::: 'Required ('NotNull 'PGfloat4)
   , "shipperVal"  ::: 'Required ('NotNull 'PGbool)
   ]