packages feed

hasql-1.10: src/library/Hasql/Connection/Settings.hs

module Hasql.Connection.Settings
  ( -- * Settings
    Settings,

    -- * Constructors
    host,
    hostAndPort,
    user,
    password,
    dbname,
    applicationName,
    other,
    noPreparedStatements,
    connectionString,
  )
where

import Data.Text qualified as Text
import Hasql.Connection.Config qualified as Config
import Hasql.Platform.Prelude
import PostgresqlConnectionString qualified as ConnectionString

-- | Connection settings.
--
-- This is a monoid, so you can combine multiple settings using 'mappend' ('<>').
-- The rightmost setting takes precedence in case of conflicts.
--
-- With OverloadedStrings, you can declare settings using connection strings directly.
--
-- For example, using a key-value format:
--
-- >>> "host=localhost port=5432 user=myuser dbname=mydb" :: Settings
-- "postgresql://myuser@localhost:5432/mydb"
--
-- Or using a URI format:
--
-- >>> "postgresql://myuser@localhost:5432/mydb" :: Settings
-- "postgresql://myuser@localhost:5432/mydb"
--
-- You can achieve the same effect by constructing from a 'Text' value:
--
-- >>> connectionString "host=localhost port=5432 user=myuser dbname=mydb"
-- "postgresql://myuser@localhost:5432/mydb"
--
-- Or use the provided constructors for better type safety and clarity:
--
-- >>> hostAndPort "localhost" 5432 <> user "myuser" <> dbname "mydb"
-- "postgresql://myuser@localhost:5432/mydb"
newtype Settings
  = Settings ConnectionString.ConnectionString
  deriving newtype (Eq, IsString, Show, Semigroup, Monoid)

-- | This instance allows interfacing with the internal Config.Config type without affecting the public API or requiring complex module hierarchies.
instance Config.Constructs Settings where
  construct (Settings connectionString) =
    case ConnectionString.interceptParam "no_prepared_statements" connectionString of
      Just (value, connectionString) ->
        let noPreparedStatements = interpretTextAsBool value
         in pack connectionString noPreparedStatements
      Nothing -> pack connectionString False
    where
      interpretTextAsBool value = case Text.toLower value of
        "1" -> True
        "true" -> True
        "t" -> True
        "yes" -> True
        "y" -> True
        "on" -> True
        _ -> False

      pack connectionString noPreparedStatements =
        Config.Config
          { connectionString =
              let textUrl = ConnectionString.toUrl connectionString
               in encodeUtf8 textUrl,
            noPreparedStatements
          }

-- * Constructors

-- | Whether prepared statements are disabled.
--
-- 'False' by default.
--
-- When 'True', even the statements marked as preparable will be executed without preparation at the cost of reduced performance.
--
-- This is useful when dealing with proxying applications like @pgbouncer@, which may be incompatible with prepared statements.
-- Consult their docs or just provide this setting to stay on the safe side.
-- It should be noted that starting from version @1.21.0@ @pgbouncer@ now does provide support for prepared statements.
noPreparedStatements :: Bool -> Settings
noPreparedStatements =
  Settings . ConnectionString.param "no_prepared_statements" . bool "false" "true"

-- | Host domain name or IP-address.
--
-- To specify multiple alternate hosts, combine the produced settings via 'Monoid'.
host :: Text -> Settings
host host =
  Settings (ConnectionString.host host)

-- | Host domain name or IP-address and port.
--
-- Specifying a port without a host is not allowed due to how PostgreSQL handles connection strings.
--
-- This function creates a single host-port pair. To specify multiple alternate hosts, combine the results of multiple 'hostAndPort' calls using the 'Monoid' instance.
hostAndPort :: Text -> Word16 -> Settings
hostAndPort host port =
  Settings (ConnectionString.hostAndPort host port)

-- | User name.
user :: Text -> Settings
user = Settings . ConnectionString.user

-- | Password.
password :: Text -> Settings
password = Settings . ConnectionString.password

-- | Database name.
dbname :: Text -> Settings
dbname = Settings . ConnectionString.dbname

-- | Application name.
applicationName :: Text -> Settings
applicationName = Settings . ConnectionString.param "application_name"

-- | Other param.
other :: Text -> Text -> Settings
other key = Settings . ConnectionString.param key

-- | Construct from a connection string in either URI or key-value format.
--
-- See the [PostgreSQL documentation](https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING) for details on the format.
--
-- If the connection string is invalid, it will be treated as empty.
connectionString :: Text -> Settings
connectionString = Settings . either (const mempty) id . ConnectionString.parse