{-| Module: Squeal.PostgreSQL.Definition.Index Description: create and drop indexes Copyright: (c) Eitan Chatav, 2019 Maintainer: eitan@morphism.tech Stability: experimental create and drop indexes -} {-# LANGUAGE AllowAmbiguousTypes , ConstraintKinds , DeriveAnyClass , DeriveGeneric , DerivingStrategies , FlexibleContexts , FlexibleInstances , GADTs , LambdaCase , MultiParamTypeClasses , OverloadedLabels , OverloadedStrings , RankNTypes , ScopedTypeVariables , TypeApplications , TypeInType , TypeOperators , UndecidableSuperClasses #-} module Squeal.PostgreSQL.Definition.Index ( -- * Create createIndex , createIndexIfNotExists -- * Drop , dropIndex , dropIndexIfExists -- * Index Method , IndexMethod (..) , btree , hash , gist , spgist , gin , brin ) where import Data.ByteString import GHC.TypeLits import qualified GHC.Generics as GHC import Squeal.PostgreSQL.Type.Alias import Squeal.PostgreSQL.Definition import Squeal.PostgreSQL.Expression.Sort import Squeal.PostgreSQL.Render import Squeal.PostgreSQL.Type.Schema -- $setup -- >>> import Squeal.PostgreSQL -- >>> :set -XPolyKinds {- | Create an index. >>> :{ type Table = '[] :=> '[ "a" ::: 'NoDef :=> 'Null 'PGint4 , "b" ::: 'NoDef :=> 'Null 'PGfloat4 ] :} >>> :{ let setup :: Definition (Public '[]) (Public '["tab" ::: 'Table Table, "ix" ::: 'Index 'Btree]) setup = createTable #tab (nullable int `as` #a :* nullable real `as` #b) Nil >>> createIndex #ix #tab btree [#a & AscNullsFirst, #b & AscNullsLast] in printSQL setup :} CREATE TABLE "tab" ("a" int NULL, "b" real NULL); CREATE INDEX "ix" ON "tab" USING btree (("a") ASC NULLS FIRST, ("b") ASC NULLS LAST); -} createIndex :: (Has sch db schema, Has tab schema ('Table table), KnownSymbol ix) => Alias ix -- ^ index alias -> QualifiedAlias sch tab -- ^ table alias -> IndexMethod method -- ^ index method -> [SortExpression 'Ungrouped '[] '[] db '[] '[tab ::: TableToRow table]] -- ^ sorted columns -> Definition db (Alter sch (Create ix ('Index method) schema) db) createIndex ix tab method cols = UnsafeDefinition $ "CREATE" <+> "INDEX" <+> renderSQL ix <+> "ON" <+> renderSQL tab <+> "USING" <+> renderSQL method <+> parenthesized (commaSeparated (renderIndex <$> cols)) <> ";" where renderIndex = \case Asc expression -> parenthesized (renderSQL expression) <+> "ASC" Desc expression -> parenthesized (renderSQL expression) <+> "DESC" AscNullsFirst expression -> parenthesized (renderSQL expression) <+> "ASC NULLS FIRST" DescNullsFirst expression -> parenthesized (renderSQL expression) <+> "DESC NULLS FIRST" AscNullsLast expression -> parenthesized (renderSQL expression) <+> "ASC NULLS LAST" DescNullsLast expression -> parenthesized (renderSQL expression) <+> "DESC NULLS LAST" -- | Create an index if it doesn't exist. createIndexIfNotExists :: (Has sch db schema, Has tab schema ('Table table), KnownSymbol ix) => Alias ix -- ^ index alias -> QualifiedAlias sch tab -- ^ table alias -> IndexMethod method -- ^ index method -> [SortExpression 'Ungrouped '[] '[] db '[] '[tab ::: TableToRow table]] -- ^ sorted columns -> Definition db (Alter sch (CreateIfNotExists ix ('Index method) schema) db) createIndexIfNotExists ix tab method cols = UnsafeDefinition $ "CREATE INDEX IF NOT EXISTS" <+> renderSQL ix <+> "ON" <+> renderSQL tab <+> "USING" <+> renderSQL method <+> parenthesized (commaSeparated (renderIndex <$> cols)) <> ";" where renderIndex = \case Asc expression -> parenthesized (renderSQL expression) <+> "ASC" Desc expression -> parenthesized (renderSQL expression) <+> "DESC" AscNullsFirst expression -> parenthesized (renderSQL expression) <+> "ASC NULLS FIRST" DescNullsFirst expression -> parenthesized (renderSQL expression) <+> "DESC NULLS FIRST" AscNullsLast expression -> parenthesized (renderSQL expression) <+> "ASC NULLS LAST" DescNullsLast expression -> parenthesized (renderSQL expression) <+> "DESC NULLS LAST" {- | PostgreSQL provides several index types: B-tree, Hash, GiST, SP-GiST, GIN and BRIN. Each index type uses a different algorithm that is best suited to different types of queries. -} newtype IndexMethod ty = UnsafeIndexMethod {renderIndexMethod :: ByteString} deriving stock (Eq, Ord, Show, GHC.Generic) instance RenderSQL (IndexMethod ty) where renderSQL = renderIndexMethod -- | B-trees can handle equality and range queries on data -- that can be sorted into some ordering. btree :: IndexMethod 'Btree btree = UnsafeIndexMethod "btree" -- | Hash indexes can only handle simple equality comparisons. hash :: IndexMethod 'Hash hash = UnsafeIndexMethod "hash" -- | GiST indexes are not a single kind of index, -- but rather an infrastructure within which many different -- indexing strategies can be implemented. gist :: IndexMethod 'Gist gist = UnsafeIndexMethod "gist" -- | SP-GiST indexes, like GiST indexes, -- offer an infrastructure that supports various kinds of searches. spgist :: IndexMethod 'Spgist spgist = UnsafeIndexMethod "spgist" -- | GIN indexes are “inverted indexes” which are appropriate for -- data values that contain multiple component values, such as arrays. gin :: IndexMethod 'Gin gin = UnsafeIndexMethod "gin" -- | BRIN indexes (a shorthand for Block Range INdexes) store summaries -- about the values stored in consecutive physical block ranges of a table. brin :: IndexMethod 'Brin brin = UnsafeIndexMethod "brin" -- | Drop an index. -- -- >>> printSQL (dropIndex #ix :: Definition (Public '["ix" ::: 'Index 'Btree]) (Public '[])) -- DROP INDEX "ix"; dropIndex :: (Has sch db schema, KnownSymbol ix) => QualifiedAlias sch ix -- ^ index alias -> Definition db (Alter sch (DropSchemum ix 'Index schema) db) dropIndex ix = UnsafeDefinition $ "DROP INDEX" <+> renderSQL ix <> ";" -- | Drop an index if it exists. dropIndexIfExists :: (Has sch db schema, KnownSymbol ix) => QualifiedAlias sch ix -- ^ index alias -> Definition db (Alter sch (DropSchemumIfExists ix 'Index schema) db) dropIndexIfExists ix = UnsafeDefinition $ "DROP INDEX IF EXISTS" <+> renderSQL ix <> ";"