beam-postgres-0.6.2.0: Database/Beam/Postgres/Extensions/Copy/File.hs
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeFamilies #-}
module Database.Beam.Postgres.Extensions.Copy.File
( PgCopyToSyntax (..),
PgCopyToSourceSyntax (..),
PgCopyFromSyntax (..),
PgCopyFromSourceSyntax (..),
-- * COPY options
PgCopyToOptions,
PgCopyFromOptions,
-- ** text
copyToText,
copyToTextWith,
PgTextCopyToOptions (..),
defaultPgTextCopyToOptions,
copyFromText,
copyFromTextWith,
PgTextCopyFromOptions (..),
defaultPgTextCopyFromOptions,
-- ** CSV
copyToCSV,
copyToCSVWith,
PgCSVCopyToOptions (..),
defaultPgCSVCopyToOptions,
copyFromCSV,
copyFromCSVWith,
PgCSVCopyFromOptions (..),
defaultPgCSVCopyFromOptions,
-- * Internal — exposed for use by sibling modules
emitOptionList,
textCopyToFields,
csvCopyToFields,
textCopyFromFields,
csvCopyFromFields,
)
where
import qualified Data.List.NonEmpty as NE
import Data.Maybe (catMaybes)
import Data.Text (Text)
import qualified Data.Text as T
import Database.Beam.Backend.SQL.BeamExtensions
( IsSqlCopyFromSourceSyntax (..),
IsSqlCopyFromSyntax (..),
IsSqlCopyToSourceSyntax (..),
IsSqlCopyToSyntax (..),
)
import Database.Beam.Postgres.Syntax
( PgSelectSyntax (..),
PgSyntax,
emit,
pgBoolLit,
pgCharLit,
pgParens,
pgQuotedIdentifier,
pgSepBy,
pgStringLit,
)
-- | PostgreSQL @COPY ... TO@ source syntax. Wraps the table name (with
-- optional column list) or a @SELECT@ subquery.
--
-- @since 0.6.1.0
newtype PgCopyToSourceSyntax = PgCopyToSourceSyntax {fromPgCopyToSource :: PgSyntax}
-- | PostgreSQL @COPY ... TO@ statement syntax.
--
-- @since 0.6.1.0
newtype PgCopyToSyntax = PgCopyToSyntax {fromPgCopyTo :: PgSyntax}
-- | PostgreSQL @COPY ... FROM@ source syntax. Just the table name plus
-- optional column list — Postgres @COPY ... FROM@ does not accept a SELECT.
--
-- @since 0.6.1.0
newtype PgCopyFromSourceSyntax = PgCopyFromSourceSyntax {fromPgCopyFromSource :: PgSyntax}
-- | PostgreSQL @COPY ... FROM@ statement syntax.
--
-- @since 0.6.1.0
newtype PgCopyFromSyntax = PgCopyFromSyntax {fromPgCopyFrom :: PgSyntax}
instance IsSqlCopyToSourceSyntax PgCopyToSourceSyntax where
type SqlCopyToSourceSelectSyntax PgCopyToSourceSyntax = PgSelectSyntax
copyTableToSyntax mSchema tableName' mColumns =
PgCopyToSourceSyntax $
maybe mempty (\schema -> pgQuotedIdentifier schema <> emit ".") mSchema
<> pgQuotedIdentifier tableName'
<> case mColumns of
Nothing -> mempty
Just cols -> pgParens $ pgSepBy (emit ", ") (map pgQuotedIdentifier (NE.toList cols))
copySelectToSyntax (PgSelectSyntax select) = PgCopyToSourceSyntax $ pgParens select
instance IsSqlCopyToSyntax PgCopyToSyntax where
type SqlCopyToSourceSyntax PgCopyToSyntax = PgCopyToSourceSyntax
type SqlCopyToParams PgCopyToSyntax = PgCopyToOptions
copyToStmt (PgCopyToSourceSyntax source) options =
let (filepath, optionsSyntax) = emitPgCopyToOptions options
in PgCopyToSyntax $
mconcat
[ emit "COPY ",
source,
emit " TO ",
pgStringLit (T.pack filepath),
optionsSyntax
]
instance IsSqlCopyFromSourceSyntax PgCopyFromSourceSyntax where
copyTableFromSyntax mSchema tableName' mColumns =
PgCopyFromSourceSyntax $
maybe mempty (\schema -> pgQuotedIdentifier schema <> emit ".") mSchema
<> pgQuotedIdentifier tableName'
<> case mColumns of
Nothing -> mempty
Just cols -> pgParens $ pgSepBy (emit ", ") (map pgQuotedIdentifier (NE.toList cols))
instance IsSqlCopyFromSyntax PgCopyFromSyntax where
type SqlCopyFromSourceSyntax PgCopyFromSyntax = PgCopyFromSourceSyntax
type SqlCopyFromParams PgCopyFromSyntax = PgCopyFromOptions
copyFromStmt (PgCopyFromSourceSyntax source) options =
let (filepath, optionsSyntax) = emitPgCopyFromOptions options
in PgCopyFromSyntax $
mconcat
[ emit "COPY ",
source,
emit " FROM ",
pgStringLit (T.pack filepath),
optionsSyntax
]
-- | Options for PostgreSQL's @COPY ... TO@ statement, tagged by the output
-- format. Use 'copyToText' or 'copyToCSV' to construct.
--
-- See <https://www.postgresql.org/docs/current/sql-copy.html the PostgreSQL COPY documentation>
-- for the full set of options each format supports.
--
-- @since 0.6.1.0
data PgCopyToOptions
= PgCopyToText !FilePath !PgTextCopyToOptions
| PgCopyToCSV !FilePath !PgCSVCopyToOptions
deriving (Eq, Show)
-- | Copy to a text file at the given path with default options.
-- Use 'copyToTextWith' to override.
--
-- @since 0.6.1.0
copyToText :: FilePath -> PgCopyToOptions
copyToText path = PgCopyToText path defaultPgTextCopyToOptions
-- | Copy to a text file at the given path with the given options.
--
-- @since 0.6.1.0
copyToTextWith :: FilePath -> PgTextCopyToOptions -> PgCopyToOptions
copyToTextWith = PgCopyToText
-- | Copy to a CSV file at the given path with default options.
-- Use 'copyToCSVWith' to override.
--
-- @since 0.6.1.0
copyToCSV :: FilePath -> PgCopyToOptions
copyToCSV path = PgCopyToCSV path defaultPgCSVCopyToOptions
-- | Copy to a CSV file at the given path with the given options.
--
-- @since 0.6.1.0
copyToCSVWith :: FilePath -> PgCSVCopyToOptions -> PgCopyToOptions
copyToCSVWith = PgCopyToCSV
-- | Options for PostgreSQL's @COPY ... FROM@ statement, tagged by the input
-- format. Use 'copyFromText' or 'copyFromCSV' to construct.
--
-- @since 0.6.1.0
data PgCopyFromOptions
= PgCopyFromText !FilePath !PgTextCopyFromOptions
| PgCopyFromCSV !FilePath !PgCSVCopyFromOptions
deriving (Eq, Show)
-- | Copy from a text file at the given path with default options.
-- Use 'copyFromTextWith' to override.
--
-- @since 0.6.1.0
copyFromText :: FilePath -> PgCopyFromOptions
copyFromText path = PgCopyFromText path defaultPgTextCopyFromOptions
-- | Copy from a text file at the given path with the given options.
--
-- @since 0.6.1.0
copyFromTextWith :: FilePath -> PgTextCopyFromOptions -> PgCopyFromOptions
copyFromTextWith = PgCopyFromText
-- | Copy from a CSV file at the given path with default options.
-- Use 'copyFromCSVWith' to override.
--
-- @since 0.6.1.0
copyFromCSV :: FilePath -> PgCopyFromOptions
copyFromCSV path = PgCopyFromCSV path defaultPgCSVCopyFromOptions
-- | Copy from a CSV file at the given path with the given options.
--
-- @since 0.6.1.0
copyFromCSVWith :: FilePath -> PgCSVCopyFromOptions -> PgCopyFromOptions
copyFromCSVWith = PgCopyFromCSV
-- | Options for the @text@ format on @COPY ... TO@.
--
-- @since 0.6.1.0
data PgTextCopyToOptions = PgTextCopyToOptions
{ -- | @DELIMITER@: single-character column delimiter. Defaults to TAB.
pgTextCopyToDelimiter :: Maybe Char,
-- | @NULL@: token written for SQL @NULL@ values. Defaults to @\\N@.
pgTextCopyToNullStr :: Maybe Text,
-- | @HEADER@: emit a header row with column names.
pgTextCopyToHeader :: Maybe Bool,
-- | @ENCODING@: client-encoding override for the file.
pgTextCopyToEncoding :: Maybe Text
}
deriving (Eq, Show)
-- | All-'Nothing' 'PgTextCopyToOptions'; a sensible starting point for
-- record-update overrides.
--
-- @since 0.6.1.0
defaultPgTextCopyToOptions :: PgTextCopyToOptions
defaultPgTextCopyToOptions =
PgTextCopyToOptions
{ pgTextCopyToDelimiter = Nothing,
pgTextCopyToNullStr = Nothing,
pgTextCopyToHeader = Nothing,
pgTextCopyToEncoding = Nothing
}
-- | Options for the @csv@ format on @COPY ... TO@.
--
-- @since 0.6.1.0
data PgCSVCopyToOptions = PgCSVCopyToOptions
{ -- | @DELIMITER@: single-character column delimiter. Defaults to a comma.
pgCsvCopyToDelimiter :: Maybe Char,
-- | @NULL@: token written for SQL @NULL@ values. Defaults to the empty
-- string.
pgCsvCopyToNullStr :: Maybe Text,
-- | @HEADER@: emit a header row with column names.
pgCsvCopyToHeader :: Maybe Bool,
-- | @QUOTE@: single-character quote character. Defaults to @\"@.
pgCsvCopyToQuote :: Maybe Char,
-- | @ESCAPE@: single-character escape character. Defaults to the
-- value of 'pgCsvCopyToQuote'.
pgCsvCopyToEscape :: Maybe Char,
-- | @ENCODING@: client-encoding override for the file.
pgCsvCopyToEncoding :: Maybe Text
}
deriving (Eq, Show)
-- | All-'Nothing' 'PgCSVCopyToOptions'.
--
-- @since 0.6.1.0
defaultPgCSVCopyToOptions :: PgCSVCopyToOptions
defaultPgCSVCopyToOptions =
PgCSVCopyToOptions
{ pgCsvCopyToDelimiter = Nothing,
pgCsvCopyToNullStr = Nothing,
pgCsvCopyToHeader = Nothing,
pgCsvCopyToQuote = Nothing,
pgCsvCopyToEscape = Nothing,
pgCsvCopyToEncoding = Nothing
}
-- | Options for the @text@ format on @COPY ... FROM@.
--
-- @since 0.6.1.0
data PgTextCopyFromOptions = PgTextCopyFromOptions
{ -- | @DELIMITER@: single-character column delimiter. Defaults to TAB.
pgTextCopyFromDelimiter :: Maybe Char,
-- | @NULL@: token to interpret as SQL @NULL@. Defaults to @\\N@.
pgTextCopyFromNullStr :: Maybe Text,
-- | @HEADER@: skip the first line as a header row.
pgTextCopyFromHeader :: Maybe Bool,
-- | @ENCODING@: client-encoding override for the file.
pgTextCopyFromEncoding :: Maybe Text,
-- | @FREEZE@: skip WAL when loading; only legal under specific
-- conditions (see the PostgreSQL @COPY@ documentation).
pgTextCopyFromFreeze :: Maybe Bool
}
deriving (Eq, Show)
-- | All-'Nothing' 'PgTextCopyFromOptions'.
--
-- @since 0.6.1.0
defaultPgTextCopyFromOptions :: PgTextCopyFromOptions
defaultPgTextCopyFromOptions =
PgTextCopyFromOptions
{ pgTextCopyFromDelimiter = Nothing,
pgTextCopyFromNullStr = Nothing,
pgTextCopyFromHeader = Nothing,
pgTextCopyFromEncoding = Nothing,
pgTextCopyFromFreeze = Nothing
}
-- | Options for the @csv@ format on @COPY ... FROM@.
--
-- @since 0.6.1.0
data PgCSVCopyFromOptions = PgCSVCopyFromOptions
{ -- | @DELIMITER@: single-character column delimiter. Defaults to a comma.
pgCsvCopyFromDelimiter :: Maybe Char,
-- | @NULL@: token to interpret as SQL @NULL@. Defaults to the empty
-- string.
pgCsvCopyFromNullStr :: Maybe Text,
-- | @HEADER@: skip the first line as a header row.
pgCsvCopyFromHeader :: Maybe Bool,
-- | @QUOTE@: single-character quote character. Defaults to @\"@.
pgCsvCopyFromQuote :: Maybe Char,
-- | @ESCAPE@: single-character escape character. Defaults to the
-- value of 'pgCsvCopyFromQuote'.
pgCsvCopyFromEscape :: Maybe Char,
-- | @ENCODING@: client-encoding override for the file.
pgCsvCopyFromEncoding :: Maybe Text,
-- | @FREEZE@: skip WAL when loading; only legal under specific
-- conditions (see the PostgreSQL @COPY@ documentation).
pgCsvCopyFromFreeze :: Maybe Bool
}
deriving (Eq, Show)
-- | All-'Nothing' 'PgCSVCopyFromOptions'; a sensible starting point for
-- record-update overrides.
--
-- @since 0.6.1.0
defaultPgCSVCopyFromOptions :: PgCSVCopyFromOptions
defaultPgCSVCopyFromOptions =
PgCSVCopyFromOptions
{ pgCsvCopyFromDelimiter = Nothing,
pgCsvCopyFromNullStr = Nothing,
pgCsvCopyFromHeader = Nothing,
pgCsvCopyFromQuote = Nothing,
pgCsvCopyFromEscape = Nothing,
pgCsvCopyFromEncoding = Nothing,
pgCsvCopyFromFreeze = Nothing
}
-- * Emission
emitPgCopyToOptions :: PgCopyToOptions -> (FilePath, PgSyntax)
emitPgCopyToOptions (PgCopyToText fp o) = (fp, emitOptionList (emit "text") (textCopyToFields o))
emitPgCopyToOptions (PgCopyToCSV fp o) = (fp, emitOptionList (emit "csv") (csvCopyToFields o))
emitPgCopyFromOptions :: PgCopyFromOptions -> (FilePath, PgSyntax)
emitPgCopyFromOptions (PgCopyFromText fp o) = (fp, emitOptionList (emit "text") (textCopyFromFields o))
emitPgCopyFromOptions (PgCopyFromCSV fp o) = (fp, emitOptionList (emit "csv") (csvCopyFromFields o))
-- | Render @ (FORMAT FMT, OPT1 …, OPT2 …)@. The @FORMAT@ entry is always
-- present since the constructor of the options value pins it.
--
-- @since 0.6.1.0
emitOptionList :: PgSyntax -> [PgSyntax] -> PgSyntax
emitOptionList fmt items =
emit " ("
<> pgSepBy (emit ", ") ((emit "FORMAT " <> fmt) : items)
<> emit ")"
-- | Render the per-option items for a 'PgTextCopyToOptions' value.
--
-- @since 0.6.1.0
textCopyToFields :: PgTextCopyToOptions -> [PgSyntax]
textCopyToFields o =
catMaybes
[ fmap (\c -> emit "DELIMITER " <> pgCharLit c) (pgTextCopyToDelimiter o),
fmap (\s -> emit "NULL " <> pgStringLit s) (pgTextCopyToNullStr o),
fmap (\b -> emit "HEADER " <> pgBoolLit b) (pgTextCopyToHeader o),
fmap (\s -> emit "ENCODING " <> pgStringLit s) (pgTextCopyToEncoding o)
]
-- | Render the per-option items for a 'PgCSVCopyToOptions' value.
--
-- @since 0.6.1.0
csvCopyToFields :: PgCSVCopyToOptions -> [PgSyntax]
csvCopyToFields o =
catMaybes
[ fmap (\c -> emit "DELIMITER " <> pgCharLit c) (pgCsvCopyToDelimiter o),
fmap (\s -> emit "NULL " <> pgStringLit s) (pgCsvCopyToNullStr o),
fmap (\b -> emit "HEADER " <> pgBoolLit b) (pgCsvCopyToHeader o),
fmap (\c -> emit "QUOTE " <> pgCharLit c) (pgCsvCopyToQuote o),
fmap (\c -> emit "ESCAPE " <> pgCharLit c) (pgCsvCopyToEscape o),
fmap (\s -> emit "ENCODING " <> pgStringLit s) (pgCsvCopyToEncoding o)
]
-- | Render the per-option items for a 'PgTextCopyFromOptions' value.
--
-- @since 0.6.1.0
textCopyFromFields :: PgTextCopyFromOptions -> [PgSyntax]
textCopyFromFields o =
catMaybes
[ fmap (\c -> emit "DELIMITER " <> pgCharLit c) (pgTextCopyFromDelimiter o),
fmap (\s -> emit "NULL " <> pgStringLit s) (pgTextCopyFromNullStr o),
fmap (\b -> emit "HEADER " <> pgBoolLit b) (pgTextCopyFromHeader o),
fmap (\s -> emit "ENCODING " <> pgStringLit s) (pgTextCopyFromEncoding o),
fmap (\b -> emit "FREEZE " <> pgBoolLit b) (pgTextCopyFromFreeze o)
]
-- | Render the per-option items for a 'PgCSVCopyFromOptions' value.
--
-- @since 0.6.1.0
csvCopyFromFields :: PgCSVCopyFromOptions -> [PgSyntax]
csvCopyFromFields o =
catMaybes
[ fmap (\c -> emit "DELIMITER " <> pgCharLit c) (pgCsvCopyFromDelimiter o),
fmap (\s -> emit "NULL " <> pgStringLit s) (pgCsvCopyFromNullStr o),
fmap (\b -> emit "HEADER " <> pgBoolLit b) (pgCsvCopyFromHeader o),
fmap (\c -> emit "QUOTE " <> pgCharLit c) (pgCsvCopyFromQuote o),
fmap (\c -> emit "ESCAPE " <> pgCharLit c) (pgCsvCopyFromEscape o),
fmap (\s -> emit "ENCODING " <> pgStringLit s) (pgCsvCopyFromEncoding o),
fmap (\b -> emit "FREEZE " <> pgBoolLit b) (pgCsvCopyFromFreeze o)
]