{-|
Module: Squeal.PostgreSQL.Definition.View
Description: create and drop views
Copyright: (c) Eitan Chatav, 2019
Maintainer: eitan@morphism.tech
Stability: experimental

create and drop views
-}

{-# LANGUAGE
    AllowAmbiguousTypes
  , ConstraintKinds
  , DeriveAnyClass
  , DeriveGeneric
  , DerivingStrategies
  , FlexibleContexts
  , FlexibleInstances
  , GADTs
  , LambdaCase
  , MultiParamTypeClasses
  , OverloadedLabels
  , OverloadedStrings
  , RankNTypes
  , ScopedTypeVariables
  , TypeApplications
  , TypeInType
  , TypeOperators
  , UndecidableSuperClasses
#-}

module Squeal.PostgreSQL.Definition.View
  ( -- * Create
    createView
  , createOrReplaceView
    -- * Drop
  , dropView
  , dropViewIfExists
    -- * Alter
  , alterViewRename
  , alterViewSetSchema
  ) where

import GHC.TypeLits

import Squeal.PostgreSQL.Type.Alias
import Squeal.PostgreSQL.Definition
import Squeal.PostgreSQL.Query
import Squeal.PostgreSQL.Render
import Squeal.PostgreSQL.Type.Schema

-- $setup
-- >>> import Squeal.PostgreSQL

{- | Create a view.

>>> type ABC = '["a" ::: 'NoDef :=> 'Null 'PGint4, "b" ::: 'NoDef :=> 'Null 'PGint4, "c" ::: 'NoDef :=> 'Null 'PGint4]
>>> type BC = '["b" ::: 'Null 'PGint4, "c" ::: 'Null 'PGint4]
>>> :{
let
  definition :: Definition
    '[ "public" ::: '["abc" ::: 'Table ('[] :=> ABC)]]
    '[ "public" ::: '["abc" ::: 'Table ('[] :=> ABC), "bc"  ::: 'View BC]]
  definition =
    createView #bc (select_ (#b :* #c) (from (table #abc)))
in printSQL definition
:}
CREATE VIEW "bc" AS SELECT "b" AS "b", "c" AS "c" FROM "abc" AS "abc";
-}
createView
  :: (Has sch db schema, KnownSymbol vw)
  => QualifiedAlias sch vw -- ^ the name of the view to add
  -> Query '[] '[] db '[] view -- ^ query
  -> Definition db (Alter sch (Create vw ('View view) schema) db)
createView :: QualifiedAlias sch vw
-> Query '[] '[] db '[] view
-> Definition db (Alter sch (Create vw ('View view) schema) db)
createView QualifiedAlias sch vw
alias Query '[] '[] db '[] view
query = ByteString
-> Definition db (Alter sch (Create vw ('View view) schema) db)
forall (db0 :: SchemasType) (db1 :: SchemasType).
ByteString -> Definition db0 db1
UnsafeDefinition (ByteString
 -> Definition db (Alter sch (Create vw ('View view) schema) db))
-> ByteString
-> Definition db (Alter sch (Create vw ('View view) schema) db)
forall a b. (a -> b) -> a -> b
$
  ByteString
"CREATE" ByteString -> ByteString -> ByteString
<+> ByteString
"VIEW" ByteString -> ByteString -> ByteString
<+> QualifiedAlias sch vw -> ByteString
forall sql. RenderSQL sql => sql -> ByteString
renderSQL QualifiedAlias sch vw
alias ByteString -> ByteString -> ByteString
<+> ByteString
"AS"
  ByteString -> ByteString -> ByteString
<+> Query '[] '[] db '[] view -> ByteString
forall (lat :: FromType) (with :: FromType) (db :: SchemasType)
       (params :: [NullType]) (row :: RowType).
Query lat with db params row -> ByteString
renderQuery Query '[] '[] db '[] view
query ByteString -> ByteString -> ByteString
forall a. Semigroup a => a -> a -> a
<> ByteString
";"

{- | Create or replace a view.

>>> type ABC = '["a" ::: 'NoDef :=> 'Null 'PGint4, "b" ::: 'NoDef :=> 'Null 'PGint4, "c" ::: 'NoDef :=> 'Null 'PGint4]
>>> type BC = '["b" ::: 'Null 'PGint4, "c" ::: 'Null 'PGint4]
>>> :{
let
  definition :: Definition
    '[ "public" ::: '["abc" ::: 'Table ('[] :=> ABC)]]
    '[ "public" ::: '["abc" ::: 'Table ('[] :=> ABC), "bc"  ::: 'View BC]]
  definition =
    createOrReplaceView #bc (select_ (#b :* #c) (from (table #abc)))
in printSQL definition
:}
CREATE OR REPLACE VIEW "bc" AS SELECT "b" AS "b", "c" AS "c" FROM "abc" AS "abc";
-}
createOrReplaceView
  :: (Has sch db schema, KnownSymbol vw)
  => QualifiedAlias sch vw -- ^ the name of the view to add
  -> Query '[] '[] db '[] view -- ^ query
  -> Definition db (Alter sch (CreateOrReplace vw ('View view) schema) db)
createOrReplaceView :: QualifiedAlias sch vw
-> Query '[] '[] db '[] view
-> Definition
     db (Alter sch (CreateOrReplace vw ('View view) schema) db)
createOrReplaceView QualifiedAlias sch vw
alias Query '[] '[] db '[] view
query = ByteString
-> Definition
     db (Alter sch (CreateOrReplace vw ('View view) schema) db)
forall (db0 :: SchemasType) (db1 :: SchemasType).
ByteString -> Definition db0 db1
UnsafeDefinition (ByteString
 -> Definition
      db (Alter sch (CreateOrReplace vw ('View view) schema) db))
-> ByteString
-> Definition
     db (Alter sch (CreateOrReplace vw ('View view) schema) db)
forall a b. (a -> b) -> a -> b
$
  ByteString
"CREATE OR REPLACE VIEW" ByteString -> ByteString -> ByteString
<+> QualifiedAlias sch vw -> ByteString
forall sql. RenderSQL sql => sql -> ByteString
renderSQL QualifiedAlias sch vw
alias ByteString -> ByteString -> ByteString
<+> ByteString
"AS"
  ByteString -> ByteString -> ByteString
<+> Query '[] '[] db '[] view -> ByteString
forall (lat :: FromType) (with :: FromType) (db :: SchemasType)
       (params :: [NullType]) (row :: RowType).
Query lat with db params row -> ByteString
renderQuery Query '[] '[] db '[] view
query ByteString -> ByteString -> ByteString
forall a. Semigroup a => a -> a -> a
<> ByteString
";"

-- | Drop a view.
--
-- >>> :{
-- let
--   definition :: Definition
--     '[ "public" ::: '["abc" ::: 'Table ('[] :=> '["a" ::: 'NoDef :=> 'Null 'PGint4, "b" ::: 'NoDef :=> 'Null 'PGint4, "c" ::: 'NoDef :=> 'Null 'PGint4])
--      , "bc"  ::: 'View ('["b" ::: 'Null 'PGint4, "c" ::: 'Null 'PGint4])]]
--     '[ "public" ::: '["abc" ::: 'Table ('[] :=> '["a" ::: 'NoDef :=> 'Null 'PGint4, "b" ::: 'NoDef :=> 'Null 'PGint4, "c" ::: 'NoDef :=> 'Null 'PGint4])]]
--   definition = dropView #bc
-- in printSQL definition
-- :}
-- DROP VIEW "bc";
dropView
  :: (Has sch db schema, KnownSymbol vw)
  => QualifiedAlias sch vw -- ^ view to remove
  -> Definition db (Alter sch (DropSchemum vw 'View schema) db)
dropView :: QualifiedAlias sch vw
-> Definition db (Alter sch (DropSchemum vw 'View schema) db)
dropView QualifiedAlias sch vw
vw = ByteString
-> Definition db (Alter sch (DropSchemum vw 'View schema) db)
forall (db0 :: SchemasType) (db1 :: SchemasType).
ByteString -> Definition db0 db1
UnsafeDefinition (ByteString
 -> Definition db (Alter sch (DropSchemum vw 'View schema) db))
-> ByteString
-> Definition db (Alter sch (DropSchemum vw 'View schema) db)
forall a b. (a -> b) -> a -> b
$ ByteString
"DROP VIEW" ByteString -> ByteString -> ByteString
<+> QualifiedAlias sch vw -> ByteString
forall sql. RenderSQL sql => sql -> ByteString
renderSQL QualifiedAlias sch vw
vw ByteString -> ByteString -> ByteString
forall a. Semigroup a => a -> a -> a
<> ByteString
";"

-- | Drop a view if it exists.
--
-- >>> :{
-- let
--   definition :: Definition
--     '[ "public" ::: '["abc" ::: 'Table ('[] :=> '["a" ::: 'NoDef :=> 'Null 'PGint4, "b" ::: 'NoDef :=> 'Null 'PGint4, "c" ::: 'NoDef :=> 'Null 'PGint4])
--      , "bc"  ::: 'View ('["b" ::: 'Null 'PGint4, "c" ::: 'Null 'PGint4])]]
--     '[ "public" ::: '["abc" ::: 'Table ('[] :=> '["a" ::: 'NoDef :=> 'Null 'PGint4, "b" ::: 'NoDef :=> 'Null 'PGint4, "c" ::: 'NoDef :=> 'Null 'PGint4])]]
--   definition = dropViewIfExists #bc
-- in printSQL definition
-- :}
-- DROP VIEW IF EXISTS "bc";
dropViewIfExists
  :: (Has sch db schema, KnownSymbol vw)
  => QualifiedAlias sch vw -- ^ view to remove
  -> Definition db (Alter sch (DropIfExists vw schema) db)
dropViewIfExists :: QualifiedAlias sch vw
-> Definition db (Alter sch (DropIfExists vw schema) db)
dropViewIfExists QualifiedAlias sch vw
vw = ByteString -> Definition db (Alter sch (DropIfExists vw schema) db)
forall (db0 :: SchemasType) (db1 :: SchemasType).
ByteString -> Definition db0 db1
UnsafeDefinition (ByteString
 -> Definition db (Alter sch (DropIfExists vw schema) db))
-> ByteString
-> Definition db (Alter sch (DropIfExists vw schema) db)
forall a b. (a -> b) -> a -> b
$
  ByteString
"DROP VIEW IF EXISTS" ByteString -> ByteString -> ByteString
<+> QualifiedAlias sch vw -> ByteString
forall sql. RenderSQL sql => sql -> ByteString
renderSQL QualifiedAlias sch vw
vw ByteString -> ByteString -> ByteString
forall a. Semigroup a => a -> a -> a
<> ByteString
";"

-- | `alterViewRename` changes the name of a view from the schema.
--
-- >>> type DB = '[ "public" ::: '[ "foo" ::: 'View '[] ] ]
-- >>> :{
--  let def :: Definition DB '["public" ::: '["bar" ::: 'View '[] ] ]
--      def = alterViewRename #foo #bar
--  in printSQL def
-- :}
-- ALTER VIEW "foo" RENAME TO "bar";
alterViewRename
  :: ( Has sch db schema
     , KnownSymbol ty1
     , Has ty0 schema ('View vw))
  => QualifiedAlias sch ty0 -- ^ view to rename
  -> Alias ty1 -- ^ what to rename it
  -> Definition db (Alter sch (Rename ty0 ty1 schema) db )
alterViewRename :: QualifiedAlias sch ty0
-> Alias ty1
-> Definition db (Alter sch (Rename ty0 ty1 schema) db)
alterViewRename QualifiedAlias sch ty0
vw0 Alias ty1
vw1 = ByteString -> Definition db (Alter sch (Rename ty0 ty1 schema) db)
forall (db0 :: SchemasType) (db1 :: SchemasType).
ByteString -> Definition db0 db1
UnsafeDefinition (ByteString
 -> Definition db (Alter sch (Rename ty0 ty1 schema) db))
-> ByteString
-> Definition db (Alter sch (Rename ty0 ty1 schema) db)
forall a b. (a -> b) -> a -> b
$
  ByteString
"ALTER VIEW" ByteString -> ByteString -> ByteString
<+> QualifiedAlias sch ty0 -> ByteString
forall sql. RenderSQL sql => sql -> ByteString
renderSQL QualifiedAlias sch ty0
vw0
  ByteString -> ByteString -> ByteString
<+> ByteString
"RENAME TO" ByteString -> ByteString -> ByteString
<+> Alias ty1 -> ByteString
forall sql. RenderSQL sql => sql -> ByteString
renderSQL Alias ty1
vw1 ByteString -> ByteString -> ByteString
forall a. Semigroup a => a -> a -> a
<> ByteString
";"

{- | This form moves the view into another schema.

>>> type DB0 = '[ "sch0" ::: '[ "vw" ::: 'View '[] ], "sch1" ::: '[] ]
>>> type DB1 = '[ "sch0" ::: '[], "sch1" ::: '[ "vw" ::: 'View '[] ] ]
>>> :{
let def :: Definition DB0 DB1
    def = alterViewSetSchema (#sch0 ! #vw) #sch1
in printSQL def
:}
ALTER VIEW "sch0"."vw" SET SCHEMA "sch1";
-}
alterViewSetSchema
  :: ( Has sch0 db schema0
     , Has vw schema0 ('View view)
     , Has sch1 db schema1 )
  => QualifiedAlias sch0 vw -- ^ view to move
  -> Alias sch1 -- ^ where to move it
  -> Definition db (SetSchema sch0 sch1 schema0 schema1 vw 'View view db)
alterViewSetSchema :: QualifiedAlias sch0 vw
-> Alias sch1
-> Definition
     db (SetSchema sch0 sch1 schema0 schema1 vw 'View view db)
alterViewSetSchema QualifiedAlias sch0 vw
ty Alias sch1
sch = ByteString
-> Definition
     db
     (Alter
        sch1
        (Create vw ('View view) schema1)
        (Alter sch0 (DropSchemum vw 'View schema0) db))
forall (db0 :: SchemasType) (db1 :: SchemasType).
ByteString -> Definition db0 db1
UnsafeDefinition (ByteString
 -> Definition
      db
      (Alter
         sch1
         (Create vw ('View view) schema1)
         (Alter sch0 (DropSchemum vw 'View schema0) db)))
-> ByteString
-> Definition
     db
     (Alter
        sch1
        (Create vw ('View view) schema1)
        (Alter sch0 (DropSchemum vw 'View schema0) db))
forall a b. (a -> b) -> a -> b
$
  ByteString
"ALTER VIEW" ByteString -> ByteString -> ByteString
<+> QualifiedAlias sch0 vw -> ByteString
forall sql. RenderSQL sql => sql -> ByteString
renderSQL QualifiedAlias sch0 vw
ty ByteString -> ByteString -> ByteString
<+> ByteString
"SET SCHEMA" ByteString -> ByteString -> ByteString
<+> Alias sch1 -> ByteString
forall sql. RenderSQL sql => sql -> ByteString
renderSQL Alias sch1
sch ByteString -> ByteString -> ByteString
forall a. Semigroup a => a -> a -> a
<> ByteString
";"