packages feed

orville-postgresql-1.1.0.0: src/Orville/PostgreSQL/PgCatalog/PgIndex.hs

{- |
Copyright : Flipstone Technology Partners 2023
License   : MIT
Stability : Stable

@since 1.0.0.0
-}
module Orville.PostgreSQL.PgCatalog.PgIndex
  ( PgIndex (..)
  , pgIndexTable
  , indexRelationOidField
  , indexIsLiveField
  )
where

import qualified Data.Attoparsec.Text as AttoText
import qualified Data.List as List
import qualified Data.Text as T
import qualified Data.Text.Lazy as LT
import qualified Data.Text.Lazy.Builder as LTB
import qualified Database.PostgreSQL.LibPQ as LibPQ

import qualified Orville.PostgreSQL as Orville
import Orville.PostgreSQL.PgCatalog.OidField (oidTypeField)
import Orville.PostgreSQL.PgCatalog.PgAttribute (AttributeNumber, attributeNumberParser, attributeNumberTextBuilder)

{- | The Haskell representation of data read from the @pg_catalog.pg_index@ table.
  Rows in this table contain extended information about indices. Information
  about indices is also contained in the @pg_catalog.pg_class@ table as well.

@since 1.0.0.0
-}
data PgIndex = PgIndex
  { pgIndexPgClassOid :: LibPQ.Oid
  -- ^ The PostgreSQL @oid@ of the @pg_class@ entry for this index.
  , pgIndexRelationOid :: LibPQ.Oid
  -- ^ The PostgreSQL @oid@ of the @pg_class@ entry for the table that this
  -- index is for.
  , pgIndexAttributeNumbers :: [AttributeNumber]
  -- ^ An array of attribute number references for the columns of the table
  -- that are included in the index. An attribute number of @0@ indicates an
  -- expression over the table's columns rather than just a reference to a
  -- column.
  --
  -- In PostgreSQL 11+ this includes both key columns and non-key-included
  -- columns. Orville is currently not aware of this distinction, however.
  , pgIndexIsUnique :: Bool
  -- ^ Indicates whether this is a unique index.
  , pgIndexIsPrimary :: Bool
  -- ^ Indicates whether this is the primary key index for the table.
  , pgIndexIsLive :: Bool
  -- ^ When @False@, indicates that this index is in the process of being
  -- dropped and should be ignored.
  }

{- | An Orville 'Orville.TableDefinition' for querying the
  @pg_catalog.pg_index@ table.

@since 1.0.0.0
-}
pgIndexTable :: Orville.TableDefinition Orville.NoKey PgIndex PgIndex
pgIndexTable =
  Orville.setTableSchema "pg_catalog" $
    Orville.mkTableDefinitionWithoutKey
      "pg_index"
      pgIndexMarshaller

pgIndexMarshaller :: Orville.SqlMarshaller PgIndex PgIndex
pgIndexMarshaller =
  PgIndex
    <$> Orville.marshallField pgIndexPgClassOid indexPgClassOidField
    <*> Orville.marshallField pgIndexRelationOid indexRelationOidField
    <*> Orville.marshallField pgIndexAttributeNumbers indexAttributeNumbersField
    <*> Orville.marshallField pgIndexIsUnique indexIsUniqueField
    <*> Orville.marshallField pgIndexIsPrimary indexIsPrimaryField
    <*> Orville.marshallField pgIndexIsLive indexIsLiveField

{- | The @indexrelid@ column of the @pg_index@ table.

@since 1.0.0.0
-}
indexPgClassOidField :: Orville.FieldDefinition Orville.NotNull LibPQ.Oid
indexPgClassOidField =
  oidTypeField "indexrelid"

{- | The @indrelid@ column of the @pg_index@ table.

@since 1.0.0.0
-}
indexRelationOidField :: Orville.FieldDefinition Orville.NotNull LibPQ.Oid
indexRelationOidField =
  oidTypeField "indrelid"

{- | The @indkey@ column of the @pg_index@ table.

@since 1.0.0.0
-}
indexAttributeNumbersField :: Orville.FieldDefinition Orville.NotNull [AttributeNumber]
indexAttributeNumbersField =
  Orville.convertField
    (Orville.tryConvertSqlType attributeNumberListToPgVectorText pgVectorTextToAttributeNumberList)
    (Orville.unboundedTextField "indkey")

{- | The @indisunique@ column of the @pg_index@ table.

@since 1.0.0.0
-}
indexIsUniqueField :: Orville.FieldDefinition Orville.NotNull Bool
indexIsUniqueField =
  Orville.booleanField "indisunique"

{- | The @indisprimary@ column of the @pg_index@ table.

@since 1.0.0.0
-}
indexIsPrimaryField :: Orville.FieldDefinition Orville.NotNull Bool
indexIsPrimaryField =
  Orville.booleanField "indisprimary"

{- | The @indislive@ column of the @pg_index@ table.

@since 1.0.0.0
-}
indexIsLiveField :: Orville.FieldDefinition Orville.NotNull Bool
indexIsLiveField =
  Orville.booleanField "indislive"

pgVectorTextToAttributeNumberList :: T.Text -> Either String [AttributeNumber]
pgVectorTextToAttributeNumberList text =
  let
    parser = do
      attNums <- AttoText.sepBy attributeNumberParser (AttoText.char ' ')
      AttoText.endOfInput
      pure attNums
  in
    case AttoText.parseOnly parser text of
      Left err -> Left ("Unable to decode PostgreSQL Vector as AttributeNumber list: " <> err)
      Right nums -> Right nums

attributeNumberListToPgVectorText :: [AttributeNumber] -> T.Text
attributeNumberListToPgVectorText attNums =
  let
    spaceDelimitedAttributeNumbers =
      mconcat $
        List.intersperse (LTB.singleton ' ') (map attributeNumberTextBuilder attNums)
  in
    LT.toStrict . LTB.toLazyText $
      spaceDelimitedAttributeNumbers