diff --git a/Database/PostgreSQL/Typed.hs b/Database/PostgreSQL/Typed.hs
--- a/Database/PostgreSQL/Typed.hs
+++ b/Database/PostgreSQL/Typed.hs
@@ -115,7 +115,7 @@
 -- There are two steps to running a query: a Template Haskell quasiquoter to perform type-inference at compile time and create a 'PGQuery'; and a run-time function to execute the query ('pgRunQuery', 'pgQuery', 'pgExecute').
 
 -- $compile
--- Both TH functions take a single SQL string, which may contain in-line placeholders of the form @${expr}@ (where @expr@ is any valid Haskell expression that does not contain @{}@) and/or PostgreSQL placeholders of the form @$1@, @$2@, etc.
+-- Both TH functions take a single SQL string, which may contain in-line placeholders of the form @${expr}@ (where @expr@ is any valid Haskell expression) and/or PostgreSQL placeholders of the form @$1@, @$2@, etc.
 --
 -- > let q = [pgSQL|SELECT id, name, address FROM people WHERE name LIKE ${query++"%"} OR email LIKE $1|] :: PGSimpleQuery [(Int32, String, Maybe String)]
 --
@@ -125,7 +125,7 @@
 --
 -- > [pgSQL|SELECT id FROM people WHERE name = $1|] :: String -> PGSimpleQuery [Int32]
 --
--- To produce 'PGPreparedQuery' objects instead, put a single @$@ at the beginning of the query.
+-- To produce 'PGPreparedQuery' objects instead of 'PGSimpleQuery', put a single @$@ at the beginning of the query.
 -- You can also create queries at run-time using 'rawPGSimpleQuery' or 'rawPGPreparedQuery'.
 
 -- $run
diff --git a/Database/PostgreSQL/Typed/Array.hs b/Database/PostgreSQL/Typed/Array.hs
--- a/Database/PostgreSQL/Typed/Array.hs
+++ b/Database/PostgreSQL/Typed/Array.hs
@@ -1,5 +1,8 @@
 {-# LANGUAGE CPP, MultiParamTypeClasses, FlexibleInstances, FunctionalDependencies, UndecidableInstances, DataKinds, OverloadedStrings #-}
 {-# OPTIONS_GHC -fno-warn-orphans #-}
+#if __GLASGOW_HASKELL__ >= 800
+{-# OPTIONS_GHC -fno-warn-redundant-constraints #-}
+#endif
 -- |
 -- Module: Database.PostgreSQL.Typed.Array
 -- Copyright: 2015 Dylan Simon
diff --git a/Database/PostgreSQL/Typed/Dynamic.hs b/Database/PostgreSQL/Typed/Dynamic.hs
--- a/Database/PostgreSQL/Typed/Dynamic.hs
+++ b/Database/PostgreSQL/Typed/Dynamic.hs
@@ -1,4 +1,4 @@
-{-# LANGUAGE CPP, FlexibleInstances, MultiParamTypeClasses, ScopedTypeVariables, FunctionalDependencies, UndecidableInstances, DataKinds, DefaultSignatures, PatternGuards, GADTs, TemplateHaskell #-}
+{-# LANGUAGE CPP, FlexibleInstances, MultiParamTypeClasses, ScopedTypeVariables, FunctionalDependencies, UndecidableInstances, DataKinds, DefaultSignatures, PatternGuards, GADTs, TemplateHaskell, AllowAmbiguousTypes #-}
 -- |
 -- Module: Database.PostgreSQL.Typed.Dynamic
 -- Copyright: 2015 Dylan Simon
@@ -36,8 +36,8 @@
 import Language.Haskell.Meta.Parse (parseExp)
 import qualified Language.Haskell.TH as TH
 
-import Database.PostgreSQL.Typed.Internal
 import Database.PostgreSQL.Typed.Types
+import Database.PostgreSQL.Typed.SQLToken
 
 -- |Represents canonical/default PostgreSQL representation for various Haskell types, allowing convenient type-driven marshalling.
 class PGType t => PGRep t a | a -> t where
@@ -95,6 +95,7 @@
 #endif
 instance PGRep "date" Time.Day
 instance PGRep "time without time zone" Time.TimeOfDay
+instance PGRep "time with time zone" (Time.TimeOfDay, Time.TimeZone)
 instance PGRep "timestamp without time zone" Time.LocalTime
 instance PGRep "timestamp with time zone" Time.UTCTime
 instance PGRep "interval" Time.DiffTime
@@ -110,12 +111,8 @@
 -- This lets you do safe, type-driven literal substitution into SQL fragments without needing a full query, bypassing placeholder inference and any prepared queries, for example when using 'Database.PostgreSQL.Typed.Protocol.pgSimpleQuery' or 'Database.PostgreSQL.Typed.Protocol.pgSimpleQueries_'.
 -- Unlike most other TH functions, this does not require any database connection.
 pgSubstituteLiterals :: String -> TH.ExpQ
-pgSubstituteLiterals sql = TH.AppE (TH.VarE 'BSL.fromChunks) . TH.ListE <$> ssl (sqlSplitExprs sql) where
-  ssl :: SQLSplit String 'True -> TH.Q [TH.Exp]
-  ssl (SQLLiteral s l) = (TH.VarE 'fromString `TH.AppE` stringE s :) <$> ssp l
-  ssl SQLSplitEnd = return []
-  ssp :: SQLSplit String 'False -> TH.Q [TH.Exp]
-  ssp (SQLPlaceholder e l) = do
+pgSubstituteLiterals sql = TH.AppE (TH.VarE 'BSL.fromChunks) . TH.ListE <$> mapM sst (sqlTokens sql) where
+  sst (SQLExpr e) = do
     v <- either (fail . (++) ("Failed to parse expression {" ++ e ++ "}: ")) return $ parseExp e
-    (TH.VarE 'pgSafeLiteral `TH.AppE` v :) <$> ssl l
-  ssp SQLSplitEnd = return []
+    return $ TH.VarE 'pgSafeLiteral `TH.AppE` v
+  sst t = return $ TH.VarE 'fromString `TH.AppE` TH.LitE (TH.StringL $ show t)
diff --git a/Database/PostgreSQL/Typed/Enum.hs b/Database/PostgreSQL/Typed/Enum.hs
--- a/Database/PostgreSQL/Typed/Enum.hs
+++ b/Database/PostgreSQL/Typed/Enum.hs
@@ -1,4 +1,4 @@
-{-# LANGUAGE TemplateHaskell, FlexibleInstances, MultiParamTypeClasses, DataKinds #-}
+{-# LANGUAGE CPP, TemplateHaskell, FlexibleInstances, MultiParamTypeClasses, DataKinds #-}
 -- |
 -- Module: Database.PostgreSQL.Typed.Enum
 -- Copyright: 2015 Dylan Simon
@@ -57,18 +57,25 @@
     valn = map (\[PGTextValue v] -> let u = BSC.unpack v in (TH.mkName $ valnf u, map (TH.IntegerL . fromIntegral) $ BS.unpack v, TH.StringL u)) vals
   dv <- TH.newName "x"
   return
-    [ TH.DataD [] typn [] (map (\(n, _, _) -> TH.NormalC n []) valn)
+    [ TH.DataD [] typn []
+#if MIN_VERSION_template_haskell(2,11,0)
+      Nothing
+#endif
+      (map (\(n, _, _) -> TH.NormalC n []) valn) $
+#if MIN_VERSION_template_haskell(2,11,0)
+      map TH.ConT
+#endif
       [''Eq, ''Ord, ''Enum, ''Ix, ''Bounded, ''Typeable]
-    , TH.InstanceD [] (TH.ConT ''Show `TH.AppT` typt)
+    , instanceD [] (TH.ConT ''Show `TH.AppT` typt)
       [ TH.FunD 'show $ map (\(n, _, v) -> TH.Clause [TH.ConP n []]
         (TH.NormalB $ TH.LitE v) []) valn
       ]
-    , TH.InstanceD [] (TH.ConT ''PGType `TH.AppT` typl) []
-    , TH.InstanceD [] (TH.ConT ''PGParameter `TH.AppT` typl `TH.AppT` typt)
+    , instanceD [] (TH.ConT ''PGType `TH.AppT` typl) []
+    , instanceD [] (TH.ConT ''PGParameter `TH.AppT` typl `TH.AppT` typt)
       [ TH.FunD 'pgEncode $ map (\(n, l, _) -> TH.Clause [TH.WildP, TH.ConP n []]
         (TH.NormalB $ TH.VarE 'BS.pack `TH.AppE` TH.ListE (map TH.LitE l)) []) valn
       ]
-    , TH.InstanceD [] (TH.ConT ''PGColumn `TH.AppT` typl `TH.AppT` typt)
+    , instanceD [] (TH.ConT ''PGColumn `TH.AppT` typl `TH.AppT` typt)
       [ TH.FunD 'pgDecode [TH.Clause [TH.WildP, TH.VarP dv]
         (TH.NormalB $ TH.CaseE (TH.VarE 'BS.unpack `TH.AppE` TH.VarE dv) $ map (\(n, l, _) ->
           TH.Match (TH.ListP (map TH.LitP l)) (TH.NormalB $ TH.ConE n) []) valn ++
@@ -77,10 +84,14 @@
             []])
         []] 
       ]
-    , TH.InstanceD [] (TH.ConT ''PGRep `TH.AppT` typl `TH.AppT` typt) []
-    , TH.InstanceD [] (TH.ConT ''PGEnum `TH.AppT` typt) []
+    , instanceD [] (TH.ConT ''PGRep `TH.AppT` typl `TH.AppT` typt) []
+    , instanceD [] (TH.ConT ''PGEnum `TH.AppT` typt) []
     ]
   where
   typn = TH.mkName typs
   typt = TH.ConT typn
   typl = TH.LitT (TH.StrTyLit name)
+  instanceD = TH.InstanceD
+#if MIN_VERSION_template_haskell(2,11,0)
+      Nothing
+#endif
diff --git a/Database/PostgreSQL/Typed/ErrCodes.hs b/Database/PostgreSQL/Typed/ErrCodes.hs
new file mode 100644
--- /dev/null
+++ b/Database/PostgreSQL/Typed/ErrCodes.hs
@@ -0,0 +1,1506 @@
+-- Automatically generated from /src/postgresql-9.5.3/src/src/backend/utils/errcodes.txt using errcodes.hs 2016-09-28 20:17:05.706135604 UTC.
+{-# LANGUAGE OverloadedStrings #-}
+-- |PostgreSQL error codes.
+module Database.PostgreSQL.Typed.ErrCodes (names
+  -- * Class 00 - Successful Completion
+  , successful_completion
+  -- * Class 01 - Warning
+  , warning
+  , warning_dynamic_result_sets_returned
+  , warning_implicit_zero_bit_padding
+  , warning_null_value_eliminated_in_set_function
+  , warning_privilege_not_granted
+  , warning_privilege_not_revoked
+  , warning_string_data_right_truncation
+  , warning_deprecated_feature
+  -- * Class 02 - No Data (this is also a warning class per the SQL standard)
+  , no_data
+  , no_additional_dynamic_result_sets_returned
+  -- * Class 03 - SQL Statement Not Yet Complete
+  , sql_statement_not_yet_complete
+  -- * Class 08 - Connection Exception
+  , connection_exception
+  , connection_does_not_exist
+  , connection_failure
+  , sqlclient_unable_to_establish_sqlconnection
+  , sqlserver_rejected_establishment_of_sqlconnection
+  , transaction_resolution_unknown
+  , protocol_violation
+  -- * Class 09 - Triggered Action Exception
+  , triggered_action_exception
+  -- * Class 0A - Feature Not Supported
+  , feature_not_supported
+  -- * Class 0B - Invalid Transaction Initiation
+  , invalid_transaction_initiation
+  -- * Class 0F - Locator Exception
+  , locator_exception
+  , invalid_locator_specification
+  -- * Class 0L - Invalid Grantor
+  , invalid_grantor
+  , invalid_grant_operation
+  -- * Class 0P - Invalid Role Specification
+  , invalid_role_specification
+  -- * Class 0Z - Diagnostics Exception
+  , diagnostics_exception
+  , stacked_diagnostics_accessed_without_active_handler
+  -- * Class 20 - Case Not Found
+  , case_not_found
+  -- * Class 21 - Cardinality Violation
+  , cardinality_violation
+  -- * Class 22 - Data Exception
+  , data_exception
+  , _ARRAY_ELEMENT_ERROR
+  , array_subscript_error
+  , character_not_in_repertoire
+  , datetime_field_overflow
+  , _DATETIME_VALUE_OUT_OF_RANGE
+  , division_by_zero
+  , error_in_assignment
+  , escape_character_conflict
+  , indicator_overflow
+  , interval_field_overflow
+  , invalid_argument_for_logarithm
+  , invalid_argument_for_ntile_function
+  , invalid_argument_for_nth_value_function
+  , invalid_argument_for_power_function
+  , invalid_argument_for_width_bucket_function
+  , invalid_character_value_for_cast
+  , invalid_datetime_format
+  , invalid_escape_character
+  , invalid_escape_octet
+  , invalid_escape_sequence
+  , nonstandard_use_of_escape_character
+  , invalid_indicator_parameter_value
+  , invalid_parameter_value
+  , invalid_regular_expression
+  , invalid_row_count_in_limit_clause
+  , invalid_row_count_in_result_offset_clause
+  , invalid_tablesample_argument
+  , invalid_tablesample_repeat
+  , invalid_time_zone_displacement_value
+  , invalid_use_of_escape_character
+  , most_specific_type_mismatch
+  , null_value_not_allowed
+  , null_value_no_indicator_parameter
+  , numeric_value_out_of_range
+  , string_data_length_mismatch
+  , string_data_right_truncation
+  , substring_error
+  , trim_error
+  , unterminated_c_string
+  , zero_length_character_string
+  , floating_point_exception
+  , invalid_text_representation
+  , invalid_binary_representation
+  , bad_copy_file_format
+  , untranslatable_character
+  , not_an_xml_document
+  , invalid_xml_document
+  , invalid_xml_content
+  , invalid_xml_comment
+  , invalid_xml_processing_instruction
+  -- * Class 23 - Integrity Constraint Violation
+  , integrity_constraint_violation
+  , restrict_violation
+  , not_null_violation
+  , foreign_key_violation
+  , unique_violation
+  , check_violation
+  , exclusion_violation
+  -- * Class 24 - Invalid Cursor State
+  , invalid_cursor_state
+  -- * Class 25 - Invalid Transaction State
+  , invalid_transaction_state
+  , active_sql_transaction
+  , branch_transaction_already_active
+  , held_cursor_requires_same_isolation_level
+  , inappropriate_access_mode_for_branch_transaction
+  , inappropriate_isolation_level_for_branch_transaction
+  , no_active_sql_transaction_for_branch_transaction
+  , read_only_sql_transaction
+  , schema_and_data_statement_mixing_not_supported
+  , no_active_sql_transaction
+  , in_failed_sql_transaction
+  -- * Class 26 - Invalid SQL Statement Name
+  , invalid_sql_statement_name
+  -- * Class 27 - Triggered Data Change Violation
+  , triggered_data_change_violation
+  -- * Class 28 - Invalid Authorization Specification
+  , invalid_authorization_specification
+  , invalid_password
+  -- * Class 2B - Dependent Privilege Descriptors Still Exist
+  , dependent_privilege_descriptors_still_exist
+  , dependent_objects_still_exist
+  -- * Class 2D - Invalid Transaction Termination
+  , invalid_transaction_termination
+  -- * Class 2F - SQL Routine Exception
+  , sql_routine_exception
+  , s_r_e_function_executed_no_return_statement
+  , s_r_e_modifying_sql_data_not_permitted
+  , s_r_e_prohibited_sql_statement_attempted
+  , s_r_e_reading_sql_data_not_permitted
+  -- * Class 34 - Invalid Cursor Name
+  , invalid_cursor_name
+  -- * Class 38 - External Routine Exception
+  , external_routine_exception
+  , e_r_e_containing_sql_not_permitted
+  , e_r_e_modifying_sql_data_not_permitted
+  , e_r_e_prohibited_sql_statement_attempted
+  , e_r_e_reading_sql_data_not_permitted
+  -- * Class 39 - External Routine Invocation Exception
+  , external_routine_invocation_exception
+  , e_r_i_e_invalid_sqlstate_returned
+  , e_r_i_e_null_value_not_allowed
+  , e_r_i_e_trigger_protocol_violated
+  , e_r_i_e_srf_protocol_violated
+  , e_r_i_e_event_trigger_protocol_violated
+  -- * Class 3B - Savepoint Exception
+  , savepoint_exception
+  , invalid_savepoint_specification
+  -- * Class 3D - Invalid Catalog Name
+  , invalid_catalog_name
+  -- * Class 3F - Invalid Schema Name
+  , invalid_schema_name
+  -- * Class 40 - Transaction Rollback
+  , transaction_rollback
+  , transaction_integrity_constraint_violation
+  , serialization_failure
+  , statement_completion_unknown
+  , deadlock_detected
+  -- * Class 42 - Syntax Error or Access Rule Violation
+  , syntax_error_or_access_rule_violation
+  , syntax_error
+  , insufficient_privilege
+  , cannot_coerce
+  , grouping_error
+  , windowing_error
+  , invalid_recursion
+  , invalid_foreign_key
+  , invalid_name
+  , name_too_long
+  , reserved_name
+  , datatype_mismatch
+  , indeterminate_datatype
+  , collation_mismatch
+  , indeterminate_collation
+  , wrong_object_type
+  , undefined_column
+  , _UNDEFINED_CURSOR
+  , _UNDEFINED_DATABASE
+  , undefined_function
+  , _UNDEFINED_PSTATEMENT
+  , _UNDEFINED_SCHEMA
+  , undefined_table
+  , undefined_parameter
+  , undefined_object
+  , duplicate_column
+  , duplicate_cursor
+  , duplicate_database
+  , duplicate_function
+  , duplicate_prepared_statement
+  , duplicate_schema
+  , duplicate_table
+  , duplicate_alias
+  , duplicate_object
+  , ambiguous_column
+  , ambiguous_function
+  , ambiguous_parameter
+  , ambiguous_alias
+  , invalid_column_reference
+  , invalid_column_definition
+  , invalid_cursor_definition
+  , invalid_database_definition
+  , invalid_function_definition
+  , invalid_prepared_statement_definition
+  , invalid_schema_definition
+  , invalid_table_definition
+  , invalid_object_definition
+  -- * Class 44 - WITH CHECK OPTION Violation
+  , with_check_option_violation
+  -- * Class 53 - Insufficient Resources
+  , insufficient_resources
+  , disk_full
+  , out_of_memory
+  , too_many_connections
+  , configuration_limit_exceeded
+  -- * Class 54 - Program Limit Exceeded
+  , program_limit_exceeded
+  , statement_too_complex
+  , too_many_columns
+  , too_many_arguments
+  -- * Class 55 - Object Not In Prerequisite State
+  , object_not_in_prerequisite_state
+  , object_in_use
+  , cant_change_runtime_param
+  , lock_not_available
+  -- * Class 57 - Operator Intervention
+  , operator_intervention
+  , query_canceled
+  , admin_shutdown
+  , crash_shutdown
+  , cannot_connect_now
+  , database_dropped
+  -- * Class 58 - System Error (errors external to PostgreSQL itself)
+  , system_error
+  , io_error
+  , undefined_file
+  , duplicate_file
+  -- * Class F0 - Configuration File Error
+  , config_file_error
+  , lock_file_exists
+  -- * Class HV - Foreign Data Wrapper Error (SQL/MED)
+  , fdw_error
+  , fdw_column_name_not_found
+  , fdw_dynamic_parameter_value_needed
+  , fdw_function_sequence_error
+  , fdw_inconsistent_descriptor_information
+  , fdw_invalid_attribute_value
+  , fdw_invalid_column_name
+  , fdw_invalid_column_number
+  , fdw_invalid_data_type
+  , fdw_invalid_data_type_descriptors
+  , fdw_invalid_descriptor_field_identifier
+  , fdw_invalid_handle
+  , fdw_invalid_option_index
+  , fdw_invalid_option_name
+  , fdw_invalid_string_length_or_buffer_length
+  , fdw_invalid_string_format
+  , fdw_invalid_use_of_null_pointer
+  , fdw_too_many_handles
+  , fdw_out_of_memory
+  , fdw_no_schemas
+  , fdw_option_name_not_found
+  , fdw_reply_handle
+  , fdw_schema_not_found
+  , fdw_table_not_found
+  , fdw_unable_to_create_execution
+  , fdw_unable_to_create_reply
+  , fdw_unable_to_establish_connection
+  -- * Class P0 - PL/pgSQL Error
+  , plpgsql_error
+  , raise_exception
+  , no_data_found
+  , too_many_rows
+  , assert_failure
+  -- * Class XX - Internal Error
+  , internal_error
+  , data_corrupted
+  , index_corrupted
+) where
+
+import Data.ByteString (ByteString)
+import Data.Map.Strict (Map, fromDistinctAscList)
+
+-- |@SUCCESSFUL_COMPLETION@: 00000 (Success)
+successful_completion :: ByteString
+successful_completion = "00000"
+
+-- |@WARNING@: 01000 (Warning)
+warning :: ByteString
+warning = "01000"
+
+-- |@WARNING_DYNAMIC_RESULT_SETS_RETURNED@: 0100C (Warning)
+warning_dynamic_result_sets_returned :: ByteString
+warning_dynamic_result_sets_returned = "0100C"
+
+-- |@WARNING_IMPLICIT_ZERO_BIT_PADDING@: 01008 (Warning)
+warning_implicit_zero_bit_padding :: ByteString
+warning_implicit_zero_bit_padding = "01008"
+
+-- |@WARNING_NULL_VALUE_ELIMINATED_IN_SET_FUNCTION@: 01003 (Warning)
+warning_null_value_eliminated_in_set_function :: ByteString
+warning_null_value_eliminated_in_set_function = "01003"
+
+-- |@WARNING_PRIVILEGE_NOT_GRANTED@: 01007 (Warning)
+warning_privilege_not_granted :: ByteString
+warning_privilege_not_granted = "01007"
+
+-- |@WARNING_PRIVILEGE_NOT_REVOKED@: 01006 (Warning)
+warning_privilege_not_revoked :: ByteString
+warning_privilege_not_revoked = "01006"
+
+-- |@WARNING_STRING_DATA_RIGHT_TRUNCATION@: 01004 (Warning)
+warning_string_data_right_truncation :: ByteString
+warning_string_data_right_truncation = "01004"
+
+-- |@WARNING_DEPRECATED_FEATURE@: 01P01 (Warning)
+warning_deprecated_feature :: ByteString
+warning_deprecated_feature = "01P01"
+
+-- |@NO_DATA@: 02000 (Warning)
+no_data :: ByteString
+no_data = "02000"
+
+-- |@NO_ADDITIONAL_DYNAMIC_RESULT_SETS_RETURNED@: 02001 (Warning)
+no_additional_dynamic_result_sets_returned :: ByteString
+no_additional_dynamic_result_sets_returned = "02001"
+
+-- |@SQL_STATEMENT_NOT_YET_COMPLETE@: 03000 (Error)
+sql_statement_not_yet_complete :: ByteString
+sql_statement_not_yet_complete = "03000"
+
+-- |@CONNECTION_EXCEPTION@: 08000 (Error)
+connection_exception :: ByteString
+connection_exception = "08000"
+
+-- |@CONNECTION_DOES_NOT_EXIST@: 08003 (Error)
+connection_does_not_exist :: ByteString
+connection_does_not_exist = "08003"
+
+-- |@CONNECTION_FAILURE@: 08006 (Error)
+connection_failure :: ByteString
+connection_failure = "08006"
+
+-- |@SQLCLIENT_UNABLE_TO_ESTABLISH_SQLCONNECTION@: 08001 (Error)
+sqlclient_unable_to_establish_sqlconnection :: ByteString
+sqlclient_unable_to_establish_sqlconnection = "08001"
+
+-- |@SQLSERVER_REJECTED_ESTABLISHMENT_OF_SQLCONNECTION@: 08004 (Error)
+sqlserver_rejected_establishment_of_sqlconnection :: ByteString
+sqlserver_rejected_establishment_of_sqlconnection = "08004"
+
+-- |@TRANSACTION_RESOLUTION_UNKNOWN@: 08007 (Error)
+transaction_resolution_unknown :: ByteString
+transaction_resolution_unknown = "08007"
+
+-- |@PROTOCOL_VIOLATION@: 08P01 (Error)
+protocol_violation :: ByteString
+protocol_violation = "08P01"
+
+-- |@TRIGGERED_ACTION_EXCEPTION@: 09000 (Error)
+triggered_action_exception :: ByteString
+triggered_action_exception = "09000"
+
+-- |@FEATURE_NOT_SUPPORTED@: 0A000 (Error)
+feature_not_supported :: ByteString
+feature_not_supported = "0A000"
+
+-- |@INVALID_TRANSACTION_INITIATION@: 0B000 (Error)
+invalid_transaction_initiation :: ByteString
+invalid_transaction_initiation = "0B000"
+
+-- |@LOCATOR_EXCEPTION@: 0F000 (Error)
+locator_exception :: ByteString
+locator_exception = "0F000"
+
+-- |@L_E_INVALID_SPECIFICATION@: 0F001 (Error)
+invalid_locator_specification :: ByteString
+invalid_locator_specification = "0F001"
+
+-- |@INVALID_GRANTOR@: 0L000 (Error)
+invalid_grantor :: ByteString
+invalid_grantor = "0L000"
+
+-- |@INVALID_GRANT_OPERATION@: 0LP01 (Error)
+invalid_grant_operation :: ByteString
+invalid_grant_operation = "0LP01"
+
+-- |@INVALID_ROLE_SPECIFICATION@: 0P000 (Error)
+invalid_role_specification :: ByteString
+invalid_role_specification = "0P000"
+
+-- |@DIAGNOSTICS_EXCEPTION@: 0Z000 (Error)
+diagnostics_exception :: ByteString
+diagnostics_exception = "0Z000"
+
+-- |@STACKED_DIAGNOSTICS_ACCESSED_WITHOUT_ACTIVE_HANDLER@: 0Z002 (Error)
+stacked_diagnostics_accessed_without_active_handler :: ByteString
+stacked_diagnostics_accessed_without_active_handler = "0Z002"
+
+-- |@CASE_NOT_FOUND@: 20000 (Error)
+case_not_found :: ByteString
+case_not_found = "20000"
+
+-- |@CARDINALITY_VIOLATION@: 21000 (Error)
+cardinality_violation :: ByteString
+cardinality_violation = "21000"
+
+-- |@DATA_EXCEPTION@: 22000 (Error)
+data_exception :: ByteString
+data_exception = "22000"
+
+-- |@ARRAY_ELEMENT_ERROR@: 2202E (Error)
+_ARRAY_ELEMENT_ERROR :: ByteString
+_ARRAY_ELEMENT_ERROR = "2202E"
+
+-- |@ARRAY_SUBSCRIPT_ERROR@: 2202E (Error)
+array_subscript_error :: ByteString
+array_subscript_error = "2202E"
+
+-- |@CHARACTER_NOT_IN_REPERTOIRE@: 22021 (Error)
+character_not_in_repertoire :: ByteString
+character_not_in_repertoire = "22021"
+
+-- |@DATETIME_FIELD_OVERFLOW@: 22008 (Error)
+datetime_field_overflow :: ByteString
+datetime_field_overflow = "22008"
+
+-- |@DATETIME_VALUE_OUT_OF_RANGE@: 22008 (Error)
+_DATETIME_VALUE_OUT_OF_RANGE :: ByteString
+_DATETIME_VALUE_OUT_OF_RANGE = "22008"
+
+-- |@DIVISION_BY_ZERO@: 22012 (Error)
+division_by_zero :: ByteString
+division_by_zero = "22012"
+
+-- |@ERROR_IN_ASSIGNMENT@: 22005 (Error)
+error_in_assignment :: ByteString
+error_in_assignment = "22005"
+
+-- |@ESCAPE_CHARACTER_CONFLICT@: 2200B (Error)
+escape_character_conflict :: ByteString
+escape_character_conflict = "2200B"
+
+-- |@INDICATOR_OVERFLOW@: 22022 (Error)
+indicator_overflow :: ByteString
+indicator_overflow = "22022"
+
+-- |@INTERVAL_FIELD_OVERFLOW@: 22015 (Error)
+interval_field_overflow :: ByteString
+interval_field_overflow = "22015"
+
+-- |@INVALID_ARGUMENT_FOR_LOG@: 2201E (Error)
+invalid_argument_for_logarithm :: ByteString
+invalid_argument_for_logarithm = "2201E"
+
+-- |@INVALID_ARGUMENT_FOR_NTILE@: 22014 (Error)
+invalid_argument_for_ntile_function :: ByteString
+invalid_argument_for_ntile_function = "22014"
+
+-- |@INVALID_ARGUMENT_FOR_NTH_VALUE@: 22016 (Error)
+invalid_argument_for_nth_value_function :: ByteString
+invalid_argument_for_nth_value_function = "22016"
+
+-- |@INVALID_ARGUMENT_FOR_POWER_FUNCTION@: 2201F (Error)
+invalid_argument_for_power_function :: ByteString
+invalid_argument_for_power_function = "2201F"
+
+-- |@INVALID_ARGUMENT_FOR_WIDTH_BUCKET_FUNCTION@: 2201G (Error)
+invalid_argument_for_width_bucket_function :: ByteString
+invalid_argument_for_width_bucket_function = "2201G"
+
+-- |@INVALID_CHARACTER_VALUE_FOR_CAST@: 22018 (Error)
+invalid_character_value_for_cast :: ByteString
+invalid_character_value_for_cast = "22018"
+
+-- |@INVALID_DATETIME_FORMAT@: 22007 (Error)
+invalid_datetime_format :: ByteString
+invalid_datetime_format = "22007"
+
+-- |@INVALID_ESCAPE_CHARACTER@: 22019 (Error)
+invalid_escape_character :: ByteString
+invalid_escape_character = "22019"
+
+-- |@INVALID_ESCAPE_OCTET@: 2200D (Error)
+invalid_escape_octet :: ByteString
+invalid_escape_octet = "2200D"
+
+-- |@INVALID_ESCAPE_SEQUENCE@: 22025 (Error)
+invalid_escape_sequence :: ByteString
+invalid_escape_sequence = "22025"
+
+-- |@NONSTANDARD_USE_OF_ESCAPE_CHARACTER@: 22P06 (Error)
+nonstandard_use_of_escape_character :: ByteString
+nonstandard_use_of_escape_character = "22P06"
+
+-- |@INVALID_INDICATOR_PARAMETER_VALUE@: 22010 (Error)
+invalid_indicator_parameter_value :: ByteString
+invalid_indicator_parameter_value = "22010"
+
+-- |@INVALID_PARAMETER_VALUE@: 22023 (Error)
+invalid_parameter_value :: ByteString
+invalid_parameter_value = "22023"
+
+-- |@INVALID_REGULAR_EXPRESSION@: 2201B (Error)
+invalid_regular_expression :: ByteString
+invalid_regular_expression = "2201B"
+
+-- |@INVALID_ROW_COUNT_IN_LIMIT_CLAUSE@: 2201W (Error)
+invalid_row_count_in_limit_clause :: ByteString
+invalid_row_count_in_limit_clause = "2201W"
+
+-- |@INVALID_ROW_COUNT_IN_RESULT_OFFSET_CLAUSE@: 2201X (Error)
+invalid_row_count_in_result_offset_clause :: ByteString
+invalid_row_count_in_result_offset_clause = "2201X"
+
+-- |@INVALID_TABLESAMPLE_ARGUMENT@: 2202H (Error)
+invalid_tablesample_argument :: ByteString
+invalid_tablesample_argument = "2202H"
+
+-- |@INVALID_TABLESAMPLE_REPEAT@: 2202G (Error)
+invalid_tablesample_repeat :: ByteString
+invalid_tablesample_repeat = "2202G"
+
+-- |@INVALID_TIME_ZONE_DISPLACEMENT_VALUE@: 22009 (Error)
+invalid_time_zone_displacement_value :: ByteString
+invalid_time_zone_displacement_value = "22009"
+
+-- |@INVALID_USE_OF_ESCAPE_CHARACTER@: 2200C (Error)
+invalid_use_of_escape_character :: ByteString
+invalid_use_of_escape_character = "2200C"
+
+-- |@MOST_SPECIFIC_TYPE_MISMATCH@: 2200G (Error)
+most_specific_type_mismatch :: ByteString
+most_specific_type_mismatch = "2200G"
+
+-- |@NULL_VALUE_NOT_ALLOWED@: 22004 (Error)
+null_value_not_allowed :: ByteString
+null_value_not_allowed = "22004"
+
+-- |@NULL_VALUE_NO_INDICATOR_PARAMETER@: 22002 (Error)
+null_value_no_indicator_parameter :: ByteString
+null_value_no_indicator_parameter = "22002"
+
+-- |@NUMERIC_VALUE_OUT_OF_RANGE@: 22003 (Error)
+numeric_value_out_of_range :: ByteString
+numeric_value_out_of_range = "22003"
+
+-- |@STRING_DATA_LENGTH_MISMATCH@: 22026 (Error)
+string_data_length_mismatch :: ByteString
+string_data_length_mismatch = "22026"
+
+-- |@STRING_DATA_RIGHT_TRUNCATION@: 22001 (Error)
+string_data_right_truncation :: ByteString
+string_data_right_truncation = "22001"
+
+-- |@SUBSTRING_ERROR@: 22011 (Error)
+substring_error :: ByteString
+substring_error = "22011"
+
+-- |@TRIM_ERROR@: 22027 (Error)
+trim_error :: ByteString
+trim_error = "22027"
+
+-- |@UNTERMINATED_C_STRING@: 22024 (Error)
+unterminated_c_string :: ByteString
+unterminated_c_string = "22024"
+
+-- |@ZERO_LENGTH_CHARACTER_STRING@: 2200F (Error)
+zero_length_character_string :: ByteString
+zero_length_character_string = "2200F"
+
+-- |@FLOATING_POINT_EXCEPTION@: 22P01 (Error)
+floating_point_exception :: ByteString
+floating_point_exception = "22P01"
+
+-- |@INVALID_TEXT_REPRESENTATION@: 22P02 (Error)
+invalid_text_representation :: ByteString
+invalid_text_representation = "22P02"
+
+-- |@INVALID_BINARY_REPRESENTATION@: 22P03 (Error)
+invalid_binary_representation :: ByteString
+invalid_binary_representation = "22P03"
+
+-- |@BAD_COPY_FILE_FORMAT@: 22P04 (Error)
+bad_copy_file_format :: ByteString
+bad_copy_file_format = "22P04"
+
+-- |@UNTRANSLATABLE_CHARACTER@: 22P05 (Error)
+untranslatable_character :: ByteString
+untranslatable_character = "22P05"
+
+-- |@NOT_AN_XML_DOCUMENT@: 2200L (Error)
+not_an_xml_document :: ByteString
+not_an_xml_document = "2200L"
+
+-- |@INVALID_XML_DOCUMENT@: 2200M (Error)
+invalid_xml_document :: ByteString
+invalid_xml_document = "2200M"
+
+-- |@INVALID_XML_CONTENT@: 2200N (Error)
+invalid_xml_content :: ByteString
+invalid_xml_content = "2200N"
+
+-- |@INVALID_XML_COMMENT@: 2200S (Error)
+invalid_xml_comment :: ByteString
+invalid_xml_comment = "2200S"
+
+-- |@INVALID_XML_PROCESSING_INSTRUCTION@: 2200T (Error)
+invalid_xml_processing_instruction :: ByteString
+invalid_xml_processing_instruction = "2200T"
+
+-- |@INTEGRITY_CONSTRAINT_VIOLATION@: 23000 (Error)
+integrity_constraint_violation :: ByteString
+integrity_constraint_violation = "23000"
+
+-- |@RESTRICT_VIOLATION@: 23001 (Error)
+restrict_violation :: ByteString
+restrict_violation = "23001"
+
+-- |@NOT_NULL_VIOLATION@: 23502 (Error)
+not_null_violation :: ByteString
+not_null_violation = "23502"
+
+-- |@FOREIGN_KEY_VIOLATION@: 23503 (Error)
+foreign_key_violation :: ByteString
+foreign_key_violation = "23503"
+
+-- |@UNIQUE_VIOLATION@: 23505 (Error)
+unique_violation :: ByteString
+unique_violation = "23505"
+
+-- |@CHECK_VIOLATION@: 23514 (Error)
+check_violation :: ByteString
+check_violation = "23514"
+
+-- |@EXCLUSION_VIOLATION@: 23P01 (Error)
+exclusion_violation :: ByteString
+exclusion_violation = "23P01"
+
+-- |@INVALID_CURSOR_STATE@: 24000 (Error)
+invalid_cursor_state :: ByteString
+invalid_cursor_state = "24000"
+
+-- |@INVALID_TRANSACTION_STATE@: 25000 (Error)
+invalid_transaction_state :: ByteString
+invalid_transaction_state = "25000"
+
+-- |@ACTIVE_SQL_TRANSACTION@: 25001 (Error)
+active_sql_transaction :: ByteString
+active_sql_transaction = "25001"
+
+-- |@BRANCH_TRANSACTION_ALREADY_ACTIVE@: 25002 (Error)
+branch_transaction_already_active :: ByteString
+branch_transaction_already_active = "25002"
+
+-- |@HELD_CURSOR_REQUIRES_SAME_ISOLATION_LEVEL@: 25008 (Error)
+held_cursor_requires_same_isolation_level :: ByteString
+held_cursor_requires_same_isolation_level = "25008"
+
+-- |@INAPPROPRIATE_ACCESS_MODE_FOR_BRANCH_TRANSACTION@: 25003 (Error)
+inappropriate_access_mode_for_branch_transaction :: ByteString
+inappropriate_access_mode_for_branch_transaction = "25003"
+
+-- |@INAPPROPRIATE_ISOLATION_LEVEL_FOR_BRANCH_TRANSACTION@: 25004 (Error)
+inappropriate_isolation_level_for_branch_transaction :: ByteString
+inappropriate_isolation_level_for_branch_transaction = "25004"
+
+-- |@NO_ACTIVE_SQL_TRANSACTION_FOR_BRANCH_TRANSACTION@: 25005 (Error)
+no_active_sql_transaction_for_branch_transaction :: ByteString
+no_active_sql_transaction_for_branch_transaction = "25005"
+
+-- |@READ_ONLY_SQL_TRANSACTION@: 25006 (Error)
+read_only_sql_transaction :: ByteString
+read_only_sql_transaction = "25006"
+
+-- |@SCHEMA_AND_DATA_STATEMENT_MIXING_NOT_SUPPORTED@: 25007 (Error)
+schema_and_data_statement_mixing_not_supported :: ByteString
+schema_and_data_statement_mixing_not_supported = "25007"
+
+-- |@NO_ACTIVE_SQL_TRANSACTION@: 25P01 (Error)
+no_active_sql_transaction :: ByteString
+no_active_sql_transaction = "25P01"
+
+-- |@IN_FAILED_SQL_TRANSACTION@: 25P02 (Error)
+in_failed_sql_transaction :: ByteString
+in_failed_sql_transaction = "25P02"
+
+-- |@INVALID_SQL_STATEMENT_NAME@: 26000 (Error)
+invalid_sql_statement_name :: ByteString
+invalid_sql_statement_name = "26000"
+
+-- |@TRIGGERED_DATA_CHANGE_VIOLATION@: 27000 (Error)
+triggered_data_change_violation :: ByteString
+triggered_data_change_violation = "27000"
+
+-- |@INVALID_AUTHORIZATION_SPECIFICATION@: 28000 (Error)
+invalid_authorization_specification :: ByteString
+invalid_authorization_specification = "28000"
+
+-- |@INVALID_PASSWORD@: 28P01 (Error)
+invalid_password :: ByteString
+invalid_password = "28P01"
+
+-- |@DEPENDENT_PRIVILEGE_DESCRIPTORS_STILL_EXIST@: 2B000 (Error)
+dependent_privilege_descriptors_still_exist :: ByteString
+dependent_privilege_descriptors_still_exist = "2B000"
+
+-- |@DEPENDENT_OBJECTS_STILL_EXIST@: 2BP01 (Error)
+dependent_objects_still_exist :: ByteString
+dependent_objects_still_exist = "2BP01"
+
+-- |@INVALID_TRANSACTION_TERMINATION@: 2D000 (Error)
+invalid_transaction_termination :: ByteString
+invalid_transaction_termination = "2D000"
+
+-- |@SQL_ROUTINE_EXCEPTION@: 2F000 (Error)
+sql_routine_exception :: ByteString
+sql_routine_exception = "2F000"
+
+-- |@S_R_E_FUNCTION_EXECUTED_NO_RETURN_STATEMENT@: 2F005 (Error)
+s_r_e_function_executed_no_return_statement :: ByteString
+s_r_e_function_executed_no_return_statement = "2F005"
+
+-- |@S_R_E_MODIFYING_SQL_DATA_NOT_PERMITTED@: 2F002 (Error)
+s_r_e_modifying_sql_data_not_permitted :: ByteString
+s_r_e_modifying_sql_data_not_permitted = "2F002"
+
+-- |@S_R_E_PROHIBITED_SQL_STATEMENT_ATTEMPTED@: 2F003 (Error)
+s_r_e_prohibited_sql_statement_attempted :: ByteString
+s_r_e_prohibited_sql_statement_attempted = "2F003"
+
+-- |@S_R_E_READING_SQL_DATA_NOT_PERMITTED@: 2F004 (Error)
+s_r_e_reading_sql_data_not_permitted :: ByteString
+s_r_e_reading_sql_data_not_permitted = "2F004"
+
+-- |@INVALID_CURSOR_NAME@: 34000 (Error)
+invalid_cursor_name :: ByteString
+invalid_cursor_name = "34000"
+
+-- |@EXTERNAL_ROUTINE_EXCEPTION@: 38000 (Error)
+external_routine_exception :: ByteString
+external_routine_exception = "38000"
+
+-- |@E_R_E_CONTAINING_SQL_NOT_PERMITTED@: 38001 (Error)
+e_r_e_containing_sql_not_permitted :: ByteString
+e_r_e_containing_sql_not_permitted = "38001"
+
+-- |@E_R_E_MODIFYING_SQL_DATA_NOT_PERMITTED@: 38002 (Error)
+e_r_e_modifying_sql_data_not_permitted :: ByteString
+e_r_e_modifying_sql_data_not_permitted = "38002"
+
+-- |@E_R_E_PROHIBITED_SQL_STATEMENT_ATTEMPTED@: 38003 (Error)
+e_r_e_prohibited_sql_statement_attempted :: ByteString
+e_r_e_prohibited_sql_statement_attempted = "38003"
+
+-- |@E_R_E_READING_SQL_DATA_NOT_PERMITTED@: 38004 (Error)
+e_r_e_reading_sql_data_not_permitted :: ByteString
+e_r_e_reading_sql_data_not_permitted = "38004"
+
+-- |@EXTERNAL_ROUTINE_INVOCATION_EXCEPTION@: 39000 (Error)
+external_routine_invocation_exception :: ByteString
+external_routine_invocation_exception = "39000"
+
+-- |@E_R_I_E_INVALID_SQLSTATE_RETURNED@: 39001 (Error)
+e_r_i_e_invalid_sqlstate_returned :: ByteString
+e_r_i_e_invalid_sqlstate_returned = "39001"
+
+-- |@E_R_I_E_NULL_VALUE_NOT_ALLOWED@: 39004 (Error)
+e_r_i_e_null_value_not_allowed :: ByteString
+e_r_i_e_null_value_not_allowed = "39004"
+
+-- |@E_R_I_E_TRIGGER_PROTOCOL_VIOLATED@: 39P01 (Error)
+e_r_i_e_trigger_protocol_violated :: ByteString
+e_r_i_e_trigger_protocol_violated = "39P01"
+
+-- |@E_R_I_E_SRF_PROTOCOL_VIOLATED@: 39P02 (Error)
+e_r_i_e_srf_protocol_violated :: ByteString
+e_r_i_e_srf_protocol_violated = "39P02"
+
+-- |@E_R_I_E_EVENT_TRIGGER_PROTOCOL_VIOLATED@: 39P03 (Error)
+e_r_i_e_event_trigger_protocol_violated :: ByteString
+e_r_i_e_event_trigger_protocol_violated = "39P03"
+
+-- |@SAVEPOINT_EXCEPTION@: 3B000 (Error)
+savepoint_exception :: ByteString
+savepoint_exception = "3B000"
+
+-- |@S_E_INVALID_SPECIFICATION@: 3B001 (Error)
+invalid_savepoint_specification :: ByteString
+invalid_savepoint_specification = "3B001"
+
+-- |@INVALID_CATALOG_NAME@: 3D000 (Error)
+invalid_catalog_name :: ByteString
+invalid_catalog_name = "3D000"
+
+-- |@INVALID_SCHEMA_NAME@: 3F000 (Error)
+invalid_schema_name :: ByteString
+invalid_schema_name = "3F000"
+
+-- |@TRANSACTION_ROLLBACK@: 40000 (Error)
+transaction_rollback :: ByteString
+transaction_rollback = "40000"
+
+-- |@T_R_INTEGRITY_CONSTRAINT_VIOLATION@: 40002 (Error)
+transaction_integrity_constraint_violation :: ByteString
+transaction_integrity_constraint_violation = "40002"
+
+-- |@T_R_SERIALIZATION_FAILURE@: 40001 (Error)
+serialization_failure :: ByteString
+serialization_failure = "40001"
+
+-- |@T_R_STATEMENT_COMPLETION_UNKNOWN@: 40003 (Error)
+statement_completion_unknown :: ByteString
+statement_completion_unknown = "40003"
+
+-- |@T_R_DEADLOCK_DETECTED@: 40P01 (Error)
+deadlock_detected :: ByteString
+deadlock_detected = "40P01"
+
+-- |@SYNTAX_ERROR_OR_ACCESS_RULE_VIOLATION@: 42000 (Error)
+syntax_error_or_access_rule_violation :: ByteString
+syntax_error_or_access_rule_violation = "42000"
+
+-- |@SYNTAX_ERROR@: 42601 (Error)
+syntax_error :: ByteString
+syntax_error = "42601"
+
+-- |@INSUFFICIENT_PRIVILEGE@: 42501 (Error)
+insufficient_privilege :: ByteString
+insufficient_privilege = "42501"
+
+-- |@CANNOT_COERCE@: 42846 (Error)
+cannot_coerce :: ByteString
+cannot_coerce = "42846"
+
+-- |@GROUPING_ERROR@: 42803 (Error)
+grouping_error :: ByteString
+grouping_error = "42803"
+
+-- |@WINDOWING_ERROR@: 42P20 (Error)
+windowing_error :: ByteString
+windowing_error = "42P20"
+
+-- |@INVALID_RECURSION@: 42P19 (Error)
+invalid_recursion :: ByteString
+invalid_recursion = "42P19"
+
+-- |@INVALID_FOREIGN_KEY@: 42830 (Error)
+invalid_foreign_key :: ByteString
+invalid_foreign_key = "42830"
+
+-- |@INVALID_NAME@: 42602 (Error)
+invalid_name :: ByteString
+invalid_name = "42602"
+
+-- |@NAME_TOO_LONG@: 42622 (Error)
+name_too_long :: ByteString
+name_too_long = "42622"
+
+-- |@RESERVED_NAME@: 42939 (Error)
+reserved_name :: ByteString
+reserved_name = "42939"
+
+-- |@DATATYPE_MISMATCH@: 42804 (Error)
+datatype_mismatch :: ByteString
+datatype_mismatch = "42804"
+
+-- |@INDETERMINATE_DATATYPE@: 42P18 (Error)
+indeterminate_datatype :: ByteString
+indeterminate_datatype = "42P18"
+
+-- |@COLLATION_MISMATCH@: 42P21 (Error)
+collation_mismatch :: ByteString
+collation_mismatch = "42P21"
+
+-- |@INDETERMINATE_COLLATION@: 42P22 (Error)
+indeterminate_collation :: ByteString
+indeterminate_collation = "42P22"
+
+-- |@WRONG_OBJECT_TYPE@: 42809 (Error)
+wrong_object_type :: ByteString
+wrong_object_type = "42809"
+
+-- |@UNDEFINED_COLUMN@: 42703 (Error)
+undefined_column :: ByteString
+undefined_column = "42703"
+
+-- |@UNDEFINED_CURSOR@: 34000 (Error)
+_UNDEFINED_CURSOR :: ByteString
+_UNDEFINED_CURSOR = "34000"
+
+-- |@UNDEFINED_DATABASE@: 3D000 (Error)
+_UNDEFINED_DATABASE :: ByteString
+_UNDEFINED_DATABASE = "3D000"
+
+-- |@UNDEFINED_FUNCTION@: 42883 (Error)
+undefined_function :: ByteString
+undefined_function = "42883"
+
+-- |@UNDEFINED_PSTATEMENT@: 26000 (Error)
+_UNDEFINED_PSTATEMENT :: ByteString
+_UNDEFINED_PSTATEMENT = "26000"
+
+-- |@UNDEFINED_SCHEMA@: 3F000 (Error)
+_UNDEFINED_SCHEMA :: ByteString
+_UNDEFINED_SCHEMA = "3F000"
+
+-- |@UNDEFINED_TABLE@: 42P01 (Error)
+undefined_table :: ByteString
+undefined_table = "42P01"
+
+-- |@UNDEFINED_PARAMETER@: 42P02 (Error)
+undefined_parameter :: ByteString
+undefined_parameter = "42P02"
+
+-- |@UNDEFINED_OBJECT@: 42704 (Error)
+undefined_object :: ByteString
+undefined_object = "42704"
+
+-- |@DUPLICATE_COLUMN@: 42701 (Error)
+duplicate_column :: ByteString
+duplicate_column = "42701"
+
+-- |@DUPLICATE_CURSOR@: 42P03 (Error)
+duplicate_cursor :: ByteString
+duplicate_cursor = "42P03"
+
+-- |@DUPLICATE_DATABASE@: 42P04 (Error)
+duplicate_database :: ByteString
+duplicate_database = "42P04"
+
+-- |@DUPLICATE_FUNCTION@: 42723 (Error)
+duplicate_function :: ByteString
+duplicate_function = "42723"
+
+-- |@DUPLICATE_PSTATEMENT@: 42P05 (Error)
+duplicate_prepared_statement :: ByteString
+duplicate_prepared_statement = "42P05"
+
+-- |@DUPLICATE_SCHEMA@: 42P06 (Error)
+duplicate_schema :: ByteString
+duplicate_schema = "42P06"
+
+-- |@DUPLICATE_TABLE@: 42P07 (Error)
+duplicate_table :: ByteString
+duplicate_table = "42P07"
+
+-- |@DUPLICATE_ALIAS@: 42712 (Error)
+duplicate_alias :: ByteString
+duplicate_alias = "42712"
+
+-- |@DUPLICATE_OBJECT@: 42710 (Error)
+duplicate_object :: ByteString
+duplicate_object = "42710"
+
+-- |@AMBIGUOUS_COLUMN@: 42702 (Error)
+ambiguous_column :: ByteString
+ambiguous_column = "42702"
+
+-- |@AMBIGUOUS_FUNCTION@: 42725 (Error)
+ambiguous_function :: ByteString
+ambiguous_function = "42725"
+
+-- |@AMBIGUOUS_PARAMETER@: 42P08 (Error)
+ambiguous_parameter :: ByteString
+ambiguous_parameter = "42P08"
+
+-- |@AMBIGUOUS_ALIAS@: 42P09 (Error)
+ambiguous_alias :: ByteString
+ambiguous_alias = "42P09"
+
+-- |@INVALID_COLUMN_REFERENCE@: 42P10 (Error)
+invalid_column_reference :: ByteString
+invalid_column_reference = "42P10"
+
+-- |@INVALID_COLUMN_DEFINITION@: 42611 (Error)
+invalid_column_definition :: ByteString
+invalid_column_definition = "42611"
+
+-- |@INVALID_CURSOR_DEFINITION@: 42P11 (Error)
+invalid_cursor_definition :: ByteString
+invalid_cursor_definition = "42P11"
+
+-- |@INVALID_DATABASE_DEFINITION@: 42P12 (Error)
+invalid_database_definition :: ByteString
+invalid_database_definition = "42P12"
+
+-- |@INVALID_FUNCTION_DEFINITION@: 42P13 (Error)
+invalid_function_definition :: ByteString
+invalid_function_definition = "42P13"
+
+-- |@INVALID_PSTATEMENT_DEFINITION@: 42P14 (Error)
+invalid_prepared_statement_definition :: ByteString
+invalid_prepared_statement_definition = "42P14"
+
+-- |@INVALID_SCHEMA_DEFINITION@: 42P15 (Error)
+invalid_schema_definition :: ByteString
+invalid_schema_definition = "42P15"
+
+-- |@INVALID_TABLE_DEFINITION@: 42P16 (Error)
+invalid_table_definition :: ByteString
+invalid_table_definition = "42P16"
+
+-- |@INVALID_OBJECT_DEFINITION@: 42P17 (Error)
+invalid_object_definition :: ByteString
+invalid_object_definition = "42P17"
+
+-- |@WITH_CHECK_OPTION_VIOLATION@: 44000 (Error)
+with_check_option_violation :: ByteString
+with_check_option_violation = "44000"
+
+-- |@INSUFFICIENT_RESOURCES@: 53000 (Error)
+insufficient_resources :: ByteString
+insufficient_resources = "53000"
+
+-- |@DISK_FULL@: 53100 (Error)
+disk_full :: ByteString
+disk_full = "53100"
+
+-- |@OUT_OF_MEMORY@: 53200 (Error)
+out_of_memory :: ByteString
+out_of_memory = "53200"
+
+-- |@TOO_MANY_CONNECTIONS@: 53300 (Error)
+too_many_connections :: ByteString
+too_many_connections = "53300"
+
+-- |@CONFIGURATION_LIMIT_EXCEEDED@: 53400 (Error)
+configuration_limit_exceeded :: ByteString
+configuration_limit_exceeded = "53400"
+
+-- |@PROGRAM_LIMIT_EXCEEDED@: 54000 (Error)
+program_limit_exceeded :: ByteString
+program_limit_exceeded = "54000"
+
+-- |@STATEMENT_TOO_COMPLEX@: 54001 (Error)
+statement_too_complex :: ByteString
+statement_too_complex = "54001"
+
+-- |@TOO_MANY_COLUMNS@: 54011 (Error)
+too_many_columns :: ByteString
+too_many_columns = "54011"
+
+-- |@TOO_MANY_ARGUMENTS@: 54023 (Error)
+too_many_arguments :: ByteString
+too_many_arguments = "54023"
+
+-- |@OBJECT_NOT_IN_PREREQUISITE_STATE@: 55000 (Error)
+object_not_in_prerequisite_state :: ByteString
+object_not_in_prerequisite_state = "55000"
+
+-- |@OBJECT_IN_USE@: 55006 (Error)
+object_in_use :: ByteString
+object_in_use = "55006"
+
+-- |@CANT_CHANGE_RUNTIME_PARAM@: 55P02 (Error)
+cant_change_runtime_param :: ByteString
+cant_change_runtime_param = "55P02"
+
+-- |@LOCK_NOT_AVAILABLE@: 55P03 (Error)
+lock_not_available :: ByteString
+lock_not_available = "55P03"
+
+-- |@OPERATOR_INTERVENTION@: 57000 (Error)
+operator_intervention :: ByteString
+operator_intervention = "57000"
+
+-- |@QUERY_CANCELED@: 57014 (Error)
+query_canceled :: ByteString
+query_canceled = "57014"
+
+-- |@ADMIN_SHUTDOWN@: 57P01 (Error)
+admin_shutdown :: ByteString
+admin_shutdown = "57P01"
+
+-- |@CRASH_SHUTDOWN@: 57P02 (Error)
+crash_shutdown :: ByteString
+crash_shutdown = "57P02"
+
+-- |@CANNOT_CONNECT_NOW@: 57P03 (Error)
+cannot_connect_now :: ByteString
+cannot_connect_now = "57P03"
+
+-- |@DATABASE_DROPPED@: 57P04 (Error)
+database_dropped :: ByteString
+database_dropped = "57P04"
+
+-- |@SYSTEM_ERROR@: 58000 (Error)
+system_error :: ByteString
+system_error = "58000"
+
+-- |@IO_ERROR@: 58030 (Error)
+io_error :: ByteString
+io_error = "58030"
+
+-- |@UNDEFINED_FILE@: 58P01 (Error)
+undefined_file :: ByteString
+undefined_file = "58P01"
+
+-- |@DUPLICATE_FILE@: 58P02 (Error)
+duplicate_file :: ByteString
+duplicate_file = "58P02"
+
+-- |@CONFIG_FILE_ERROR@: F0000 (Error)
+config_file_error :: ByteString
+config_file_error = "F0000"
+
+-- |@LOCK_FILE_EXISTS@: F0001 (Error)
+lock_file_exists :: ByteString
+lock_file_exists = "F0001"
+
+-- |@FDW_ERROR@: HV000 (Error)
+fdw_error :: ByteString
+fdw_error = "HV000"
+
+-- |@FDW_COLUMN_NAME_NOT_FOUND@: HV005 (Error)
+fdw_column_name_not_found :: ByteString
+fdw_column_name_not_found = "HV005"
+
+-- |@FDW_DYNAMIC_PARAMETER_VALUE_NEEDED@: HV002 (Error)
+fdw_dynamic_parameter_value_needed :: ByteString
+fdw_dynamic_parameter_value_needed = "HV002"
+
+-- |@FDW_FUNCTION_SEQUENCE_ERROR@: HV010 (Error)
+fdw_function_sequence_error :: ByteString
+fdw_function_sequence_error = "HV010"
+
+-- |@FDW_INCONSISTENT_DESCRIPTOR_INFORMATION@: HV021 (Error)
+fdw_inconsistent_descriptor_information :: ByteString
+fdw_inconsistent_descriptor_information = "HV021"
+
+-- |@FDW_INVALID_ATTRIBUTE_VALUE@: HV024 (Error)
+fdw_invalid_attribute_value :: ByteString
+fdw_invalid_attribute_value = "HV024"
+
+-- |@FDW_INVALID_COLUMN_NAME@: HV007 (Error)
+fdw_invalid_column_name :: ByteString
+fdw_invalid_column_name = "HV007"
+
+-- |@FDW_INVALID_COLUMN_NUMBER@: HV008 (Error)
+fdw_invalid_column_number :: ByteString
+fdw_invalid_column_number = "HV008"
+
+-- |@FDW_INVALID_DATA_TYPE@: HV004 (Error)
+fdw_invalid_data_type :: ByteString
+fdw_invalid_data_type = "HV004"
+
+-- |@FDW_INVALID_DATA_TYPE_DESCRIPTORS@: HV006 (Error)
+fdw_invalid_data_type_descriptors :: ByteString
+fdw_invalid_data_type_descriptors = "HV006"
+
+-- |@FDW_INVALID_DESCRIPTOR_FIELD_IDENTIFIER@: HV091 (Error)
+fdw_invalid_descriptor_field_identifier :: ByteString
+fdw_invalid_descriptor_field_identifier = "HV091"
+
+-- |@FDW_INVALID_HANDLE@: HV00B (Error)
+fdw_invalid_handle :: ByteString
+fdw_invalid_handle = "HV00B"
+
+-- |@FDW_INVALID_OPTION_INDEX@: HV00C (Error)
+fdw_invalid_option_index :: ByteString
+fdw_invalid_option_index = "HV00C"
+
+-- |@FDW_INVALID_OPTION_NAME@: HV00D (Error)
+fdw_invalid_option_name :: ByteString
+fdw_invalid_option_name = "HV00D"
+
+-- |@FDW_INVALID_STRING_LENGTH_OR_BUFFER_LENGTH@: HV090 (Error)
+fdw_invalid_string_length_or_buffer_length :: ByteString
+fdw_invalid_string_length_or_buffer_length = "HV090"
+
+-- |@FDW_INVALID_STRING_FORMAT@: HV00A (Error)
+fdw_invalid_string_format :: ByteString
+fdw_invalid_string_format = "HV00A"
+
+-- |@FDW_INVALID_USE_OF_NULL_POINTER@: HV009 (Error)
+fdw_invalid_use_of_null_pointer :: ByteString
+fdw_invalid_use_of_null_pointer = "HV009"
+
+-- |@FDW_TOO_MANY_HANDLES@: HV014 (Error)
+fdw_too_many_handles :: ByteString
+fdw_too_many_handles = "HV014"
+
+-- |@FDW_OUT_OF_MEMORY@: HV001 (Error)
+fdw_out_of_memory :: ByteString
+fdw_out_of_memory = "HV001"
+
+-- |@FDW_NO_SCHEMAS@: HV00P (Error)
+fdw_no_schemas :: ByteString
+fdw_no_schemas = "HV00P"
+
+-- |@FDW_OPTION_NAME_NOT_FOUND@: HV00J (Error)
+fdw_option_name_not_found :: ByteString
+fdw_option_name_not_found = "HV00J"
+
+-- |@FDW_REPLY_HANDLE@: HV00K (Error)
+fdw_reply_handle :: ByteString
+fdw_reply_handle = "HV00K"
+
+-- |@FDW_SCHEMA_NOT_FOUND@: HV00Q (Error)
+fdw_schema_not_found :: ByteString
+fdw_schema_not_found = "HV00Q"
+
+-- |@FDW_TABLE_NOT_FOUND@: HV00R (Error)
+fdw_table_not_found :: ByteString
+fdw_table_not_found = "HV00R"
+
+-- |@FDW_UNABLE_TO_CREATE_EXECUTION@: HV00L (Error)
+fdw_unable_to_create_execution :: ByteString
+fdw_unable_to_create_execution = "HV00L"
+
+-- |@FDW_UNABLE_TO_CREATE_REPLY@: HV00M (Error)
+fdw_unable_to_create_reply :: ByteString
+fdw_unable_to_create_reply = "HV00M"
+
+-- |@FDW_UNABLE_TO_ESTABLISH_CONNECTION@: HV00N (Error)
+fdw_unable_to_establish_connection :: ByteString
+fdw_unable_to_establish_connection = "HV00N"
+
+-- |@PLPGSQL_ERROR@: P0000 (Error)
+plpgsql_error :: ByteString
+plpgsql_error = "P0000"
+
+-- |@RAISE_EXCEPTION@: P0001 (Error)
+raise_exception :: ByteString
+raise_exception = "P0001"
+
+-- |@NO_DATA_FOUND@: P0002 (Error)
+no_data_found :: ByteString
+no_data_found = "P0002"
+
+-- |@TOO_MANY_ROWS@: P0003 (Error)
+too_many_rows :: ByteString
+too_many_rows = "P0003"
+
+-- |@ASSERT_FAILURE@: P0004 (Error)
+assert_failure :: ByteString
+assert_failure = "P0004"
+
+-- |@INTERNAL_ERROR@: XX000 (Error)
+internal_error :: ByteString
+internal_error = "XX000"
+
+-- |@DATA_CORRUPTED@: XX001 (Error)
+data_corrupted :: ByteString
+data_corrupted = "XX001"
+
+-- |@INDEX_CORRUPTED@: XX002 (Error)
+index_corrupted :: ByteString
+index_corrupted = "XX002"
+
+-- |All known error code names by code.
+names :: Map ByteString String
+names = fromDistinctAscList
+  [(successful_completion,"successful_completion")
+  ,(warning,"warning")
+  ,(warning_null_value_eliminated_in_set_function,"null_value_eliminated_in_set_function")
+  ,(warning_string_data_right_truncation,"string_data_right_truncation")
+  ,(warning_privilege_not_revoked,"privilege_not_revoked")
+  ,(warning_privilege_not_granted,"privilege_not_granted")
+  ,(warning_implicit_zero_bit_padding,"implicit_zero_bit_padding")
+  ,(warning_dynamic_result_sets_returned,"dynamic_result_sets_returned")
+  ,(warning_deprecated_feature,"deprecated_feature")
+  ,(no_data,"no_data")
+  ,(no_additional_dynamic_result_sets_returned,"no_additional_dynamic_result_sets_returned")
+  ,(sql_statement_not_yet_complete,"sql_statement_not_yet_complete")
+  ,(connection_exception,"connection_exception")
+  ,(sqlclient_unable_to_establish_sqlconnection,"sqlclient_unable_to_establish_sqlconnection")
+  ,(connection_does_not_exist,"connection_does_not_exist")
+  ,(sqlserver_rejected_establishment_of_sqlconnection,"sqlserver_rejected_establishment_of_sqlconnection")
+  ,(connection_failure,"connection_failure")
+  ,(transaction_resolution_unknown,"transaction_resolution_unknown")
+  ,(protocol_violation,"protocol_violation")
+  ,(triggered_action_exception,"triggered_action_exception")
+  ,(feature_not_supported,"feature_not_supported")
+  ,(invalid_transaction_initiation,"invalid_transaction_initiation")
+  ,(locator_exception,"locator_exception")
+  ,(invalid_locator_specification,"invalid_locator_specification")
+  ,(invalid_grantor,"invalid_grantor")
+  ,(invalid_grant_operation,"invalid_grant_operation")
+  ,(invalid_role_specification,"invalid_role_specification")
+  ,(diagnostics_exception,"diagnostics_exception")
+  ,(stacked_diagnostics_accessed_without_active_handler,"stacked_diagnostics_accessed_without_active_handler")
+  ,(case_not_found,"case_not_found")
+  ,(cardinality_violation,"cardinality_violation")
+  ,(data_exception,"data_exception")
+  ,(string_data_right_truncation,"string_data_right_truncation")
+  ,(null_value_no_indicator_parameter,"null_value_no_indicator_parameter")
+  ,(numeric_value_out_of_range,"numeric_value_out_of_range")
+  ,(null_value_not_allowed,"null_value_not_allowed")
+  ,(error_in_assignment,"error_in_assignment")
+  ,(invalid_datetime_format,"invalid_datetime_format")
+  ,(datetime_field_overflow,"datetime_field_overflow")
+  ,(_DATETIME_VALUE_OUT_OF_RANGE,"DATETIME_VALUE_OUT_OF_RANGE")
+  ,(invalid_time_zone_displacement_value,"invalid_time_zone_displacement_value")
+  ,(escape_character_conflict,"escape_character_conflict")
+  ,(invalid_use_of_escape_character,"invalid_use_of_escape_character")
+  ,(invalid_escape_octet,"invalid_escape_octet")
+  ,(zero_length_character_string,"zero_length_character_string")
+  ,(most_specific_type_mismatch,"most_specific_type_mismatch")
+  ,(not_an_xml_document,"not_an_xml_document")
+  ,(invalid_xml_document,"invalid_xml_document")
+  ,(invalid_xml_content,"invalid_xml_content")
+  ,(invalid_xml_comment,"invalid_xml_comment")
+  ,(invalid_xml_processing_instruction,"invalid_xml_processing_instruction")
+  ,(invalid_indicator_parameter_value,"invalid_indicator_parameter_value")
+  ,(substring_error,"substring_error")
+  ,(division_by_zero,"division_by_zero")
+  ,(invalid_argument_for_ntile_function,"invalid_argument_for_ntile_function")
+  ,(interval_field_overflow,"interval_field_overflow")
+  ,(invalid_argument_for_nth_value_function,"invalid_argument_for_nth_value_function")
+  ,(invalid_character_value_for_cast,"invalid_character_value_for_cast")
+  ,(invalid_escape_character,"invalid_escape_character")
+  ,(invalid_regular_expression,"invalid_regular_expression")
+  ,(invalid_argument_for_logarithm,"invalid_argument_for_logarithm")
+  ,(invalid_argument_for_power_function,"invalid_argument_for_power_function")
+  ,(invalid_argument_for_width_bucket_function,"invalid_argument_for_width_bucket_function")
+  ,(invalid_row_count_in_limit_clause,"invalid_row_count_in_limit_clause")
+  ,(invalid_row_count_in_result_offset_clause,"invalid_row_count_in_result_offset_clause")
+  ,(character_not_in_repertoire,"character_not_in_repertoire")
+  ,(indicator_overflow,"indicator_overflow")
+  ,(invalid_parameter_value,"invalid_parameter_value")
+  ,(unterminated_c_string,"unterminated_c_string")
+  ,(invalid_escape_sequence,"invalid_escape_sequence")
+  ,(string_data_length_mismatch,"string_data_length_mismatch")
+  ,(trim_error,"trim_error")
+  ,(_ARRAY_ELEMENT_ERROR,"ARRAY_ELEMENT_ERROR")
+  ,(array_subscript_error,"array_subscript_error")
+  ,(invalid_tablesample_repeat,"invalid_tablesample_repeat")
+  ,(invalid_tablesample_argument,"invalid_tablesample_argument")
+  ,(floating_point_exception,"floating_point_exception")
+  ,(invalid_text_representation,"invalid_text_representation")
+  ,(invalid_binary_representation,"invalid_binary_representation")
+  ,(bad_copy_file_format,"bad_copy_file_format")
+  ,(untranslatable_character,"untranslatable_character")
+  ,(nonstandard_use_of_escape_character,"nonstandard_use_of_escape_character")
+  ,(integrity_constraint_violation,"integrity_constraint_violation")
+  ,(restrict_violation,"restrict_violation")
+  ,(not_null_violation,"not_null_violation")
+  ,(foreign_key_violation,"foreign_key_violation")
+  ,(unique_violation,"unique_violation")
+  ,(check_violation,"check_violation")
+  ,(exclusion_violation,"exclusion_violation")
+  ,(invalid_cursor_state,"invalid_cursor_state")
+  ,(invalid_transaction_state,"invalid_transaction_state")
+  ,(active_sql_transaction,"active_sql_transaction")
+  ,(branch_transaction_already_active,"branch_transaction_already_active")
+  ,(inappropriate_access_mode_for_branch_transaction,"inappropriate_access_mode_for_branch_transaction")
+  ,(inappropriate_isolation_level_for_branch_transaction,"inappropriate_isolation_level_for_branch_transaction")
+  ,(no_active_sql_transaction_for_branch_transaction,"no_active_sql_transaction_for_branch_transaction")
+  ,(read_only_sql_transaction,"read_only_sql_transaction")
+  ,(schema_and_data_statement_mixing_not_supported,"schema_and_data_statement_mixing_not_supported")
+  ,(held_cursor_requires_same_isolation_level,"held_cursor_requires_same_isolation_level")
+  ,(no_active_sql_transaction,"no_active_sql_transaction")
+  ,(in_failed_sql_transaction,"in_failed_sql_transaction")
+  ,(invalid_sql_statement_name,"invalid_sql_statement_name")
+  ,(_UNDEFINED_PSTATEMENT,"UNDEFINED_PSTATEMENT")
+  ,(triggered_data_change_violation,"triggered_data_change_violation")
+  ,(invalid_authorization_specification,"invalid_authorization_specification")
+  ,(invalid_password,"invalid_password")
+  ,(dependent_privilege_descriptors_still_exist,"dependent_privilege_descriptors_still_exist")
+  ,(dependent_objects_still_exist,"dependent_objects_still_exist")
+  ,(invalid_transaction_termination,"invalid_transaction_termination")
+  ,(sql_routine_exception,"sql_routine_exception")
+  ,(s_r_e_modifying_sql_data_not_permitted,"modifying_sql_data_not_permitted")
+  ,(s_r_e_prohibited_sql_statement_attempted,"prohibited_sql_statement_attempted")
+  ,(s_r_e_reading_sql_data_not_permitted,"reading_sql_data_not_permitted")
+  ,(s_r_e_function_executed_no_return_statement,"function_executed_no_return_statement")
+  ,(invalid_cursor_name,"invalid_cursor_name")
+  ,(_UNDEFINED_CURSOR,"UNDEFINED_CURSOR")
+  ,(external_routine_exception,"external_routine_exception")
+  ,(e_r_e_containing_sql_not_permitted,"containing_sql_not_permitted")
+  ,(e_r_e_modifying_sql_data_not_permitted,"modifying_sql_data_not_permitted")
+  ,(e_r_e_prohibited_sql_statement_attempted,"prohibited_sql_statement_attempted")
+  ,(e_r_e_reading_sql_data_not_permitted,"reading_sql_data_not_permitted")
+  ,(external_routine_invocation_exception,"external_routine_invocation_exception")
+  ,(e_r_i_e_invalid_sqlstate_returned,"invalid_sqlstate_returned")
+  ,(e_r_i_e_null_value_not_allowed,"null_value_not_allowed")
+  ,(e_r_i_e_trigger_protocol_violated,"trigger_protocol_violated")
+  ,(e_r_i_e_srf_protocol_violated,"srf_protocol_violated")
+  ,(e_r_i_e_event_trigger_protocol_violated,"event_trigger_protocol_violated")
+  ,(savepoint_exception,"savepoint_exception")
+  ,(invalid_savepoint_specification,"invalid_savepoint_specification")
+  ,(invalid_catalog_name,"invalid_catalog_name")
+  ,(_UNDEFINED_DATABASE,"UNDEFINED_DATABASE")
+  ,(invalid_schema_name,"invalid_schema_name")
+  ,(_UNDEFINED_SCHEMA,"UNDEFINED_SCHEMA")
+  ,(transaction_rollback,"transaction_rollback")
+  ,(serialization_failure,"serialization_failure")
+  ,(transaction_integrity_constraint_violation,"transaction_integrity_constraint_violation")
+  ,(statement_completion_unknown,"statement_completion_unknown")
+  ,(deadlock_detected,"deadlock_detected")
+  ,(syntax_error_or_access_rule_violation,"syntax_error_or_access_rule_violation")
+  ,(insufficient_privilege,"insufficient_privilege")
+  ,(syntax_error,"syntax_error")
+  ,(invalid_name,"invalid_name")
+  ,(invalid_column_definition,"invalid_column_definition")
+  ,(name_too_long,"name_too_long")
+  ,(duplicate_column,"duplicate_column")
+  ,(ambiguous_column,"ambiguous_column")
+  ,(undefined_column,"undefined_column")
+  ,(undefined_object,"undefined_object")
+  ,(duplicate_object,"duplicate_object")
+  ,(duplicate_alias,"duplicate_alias")
+  ,(duplicate_function,"duplicate_function")
+  ,(ambiguous_function,"ambiguous_function")
+  ,(grouping_error,"grouping_error")
+  ,(datatype_mismatch,"datatype_mismatch")
+  ,(wrong_object_type,"wrong_object_type")
+  ,(invalid_foreign_key,"invalid_foreign_key")
+  ,(cannot_coerce,"cannot_coerce")
+  ,(undefined_function,"undefined_function")
+  ,(reserved_name,"reserved_name")
+  ,(undefined_table,"undefined_table")
+  ,(undefined_parameter,"undefined_parameter")
+  ,(duplicate_cursor,"duplicate_cursor")
+  ,(duplicate_database,"duplicate_database")
+  ,(duplicate_prepared_statement,"duplicate_prepared_statement")
+  ,(duplicate_schema,"duplicate_schema")
+  ,(duplicate_table,"duplicate_table")
+  ,(ambiguous_parameter,"ambiguous_parameter")
+  ,(ambiguous_alias,"ambiguous_alias")
+  ,(invalid_column_reference,"invalid_column_reference")
+  ,(invalid_cursor_definition,"invalid_cursor_definition")
+  ,(invalid_database_definition,"invalid_database_definition")
+  ,(invalid_function_definition,"invalid_function_definition")
+  ,(invalid_prepared_statement_definition,"invalid_prepared_statement_definition")
+  ,(invalid_schema_definition,"invalid_schema_definition")
+  ,(invalid_table_definition,"invalid_table_definition")
+  ,(invalid_object_definition,"invalid_object_definition")
+  ,(indeterminate_datatype,"indeterminate_datatype")
+  ,(invalid_recursion,"invalid_recursion")
+  ,(windowing_error,"windowing_error")
+  ,(collation_mismatch,"collation_mismatch")
+  ,(indeterminate_collation,"indeterminate_collation")
+  ,(with_check_option_violation,"with_check_option_violation")
+  ,(insufficient_resources,"insufficient_resources")
+  ,(disk_full,"disk_full")
+  ,(out_of_memory,"out_of_memory")
+  ,(too_many_connections,"too_many_connections")
+  ,(configuration_limit_exceeded,"configuration_limit_exceeded")
+  ,(program_limit_exceeded,"program_limit_exceeded")
+  ,(statement_too_complex,"statement_too_complex")
+  ,(too_many_columns,"too_many_columns")
+  ,(too_many_arguments,"too_many_arguments")
+  ,(object_not_in_prerequisite_state,"object_not_in_prerequisite_state")
+  ,(object_in_use,"object_in_use")
+  ,(cant_change_runtime_param,"cant_change_runtime_param")
+  ,(lock_not_available,"lock_not_available")
+  ,(operator_intervention,"operator_intervention")
+  ,(query_canceled,"query_canceled")
+  ,(admin_shutdown,"admin_shutdown")
+  ,(crash_shutdown,"crash_shutdown")
+  ,(cannot_connect_now,"cannot_connect_now")
+  ,(database_dropped,"database_dropped")
+  ,(system_error,"system_error")
+  ,(io_error,"io_error")
+  ,(undefined_file,"undefined_file")
+  ,(duplicate_file,"duplicate_file")
+  ,(config_file_error,"config_file_error")
+  ,(lock_file_exists,"lock_file_exists")
+  ,(fdw_error,"fdw_error")
+  ,(fdw_out_of_memory,"fdw_out_of_memory")
+  ,(fdw_dynamic_parameter_value_needed,"fdw_dynamic_parameter_value_needed")
+  ,(fdw_invalid_data_type,"fdw_invalid_data_type")
+  ,(fdw_column_name_not_found,"fdw_column_name_not_found")
+  ,(fdw_invalid_data_type_descriptors,"fdw_invalid_data_type_descriptors")
+  ,(fdw_invalid_column_name,"fdw_invalid_column_name")
+  ,(fdw_invalid_column_number,"fdw_invalid_column_number")
+  ,(fdw_invalid_use_of_null_pointer,"fdw_invalid_use_of_null_pointer")
+  ,(fdw_invalid_string_format,"fdw_invalid_string_format")
+  ,(fdw_invalid_handle,"fdw_invalid_handle")
+  ,(fdw_invalid_option_index,"fdw_invalid_option_index")
+  ,(fdw_invalid_option_name,"fdw_invalid_option_name")
+  ,(fdw_option_name_not_found,"fdw_option_name_not_found")
+  ,(fdw_reply_handle,"fdw_reply_handle")
+  ,(fdw_unable_to_create_execution,"fdw_unable_to_create_execution")
+  ,(fdw_unable_to_create_reply,"fdw_unable_to_create_reply")
+  ,(fdw_unable_to_establish_connection,"fdw_unable_to_establish_connection")
+  ,(fdw_no_schemas,"fdw_no_schemas")
+  ,(fdw_schema_not_found,"fdw_schema_not_found")
+  ,(fdw_table_not_found,"fdw_table_not_found")
+  ,(fdw_function_sequence_error,"fdw_function_sequence_error")
+  ,(fdw_too_many_handles,"fdw_too_many_handles")
+  ,(fdw_inconsistent_descriptor_information,"fdw_inconsistent_descriptor_information")
+  ,(fdw_invalid_attribute_value,"fdw_invalid_attribute_value")
+  ,(fdw_invalid_string_length_or_buffer_length,"fdw_invalid_string_length_or_buffer_length")
+  ,(fdw_invalid_descriptor_field_identifier,"fdw_invalid_descriptor_field_identifier")
+  ,(plpgsql_error,"plpgsql_error")
+  ,(raise_exception,"raise_exception")
+  ,(no_data_found,"no_data_found")
+  ,(too_many_rows,"too_many_rows")
+  ,(assert_failure,"assert_failure")
+  ,(internal_error,"internal_error")
+  ,(data_corrupted,"data_corrupted")
+  ,(index_corrupted,"index_corrupted")]
diff --git a/Database/PostgreSQL/Typed/HDBC.hs b/Database/PostgreSQL/Typed/HDBC.hs
new file mode 100644
--- /dev/null
+++ b/Database/PostgreSQL/Typed/HDBC.hs
@@ -0,0 +1,352 @@
+-- |
+-- Module: Database.PostgreSQL.Typed.HDBC
+-- Copyright: 2016 Dylan Simon
+-- 
+-- Use postgresql-typed as a backend for HDBC.
+{-# LANGUAGE CPP #-}
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE RecordWildCards #-}
+module Database.PostgreSQL.Typed.HDBC
+  ( Connection, connectionPG
+  , connect
+  , fromPGConnection
+  , reloadTypes
+  , connectionFetchSize
+  , setFetchSize
+  ) where
+
+import Control.Arrow ((&&&))
+import Control.Concurrent.MVar (MVar, newMVar, withMVar)
+import Control.Exception (handle, throwIO)
+import Control.Monad (void, guard)
+import qualified Data.ByteString.Char8 as BSC
+import qualified Data.ByteString.Lazy.Char8 as BSLC
+import Data.IORef (newIORef, readIORef, writeIORef, modifyIORef')
+import Data.Int (Int16)
+import qualified Data.IntMap.Lazy as IntMap
+import Data.List (uncons)
+import qualified Data.Map.Lazy as Map
+import Data.Maybe (fromMaybe, isNothing)
+import Data.Time.Clock (DiffTime)
+import Data.Time.Clock.POSIX (posixSecondsToUTCTime)
+import Data.Time.LocalTime (zonedTimeToUTC)
+import Data.Word (Word32)
+import qualified Database.HDBC.Types as HDBC
+import qualified Database.HDBC.ColTypes as HDBC
+import System.Mem.Weak (addFinalizer)
+import Text.Read (readMaybe)
+
+import Database.PostgreSQL.Typed.Protocol
+import Database.PostgreSQL.Typed.Types
+import Database.PostgreSQL.Typed.Dynamic
+import Database.PostgreSQL.Typed.TH
+import Database.PostgreSQL.Typed.SQLToken
+import Paths_postgresql_typed (version)
+
+-- |A wrapped 'PGConnection'.
+-- This differs from a bare 'PGConnection' in a few ways:
+--
+--   1. It always has exactly one active transaction (with 'pgBegin')
+--   2. It automatically disconnects on GC
+--   3. It provides a mutex around the underlying 'PGConnection' for thread-safety
+--
+data Connection = Connection
+  { connectionPG :: MVar PGConnection -- ^Access the underlying 'PGConnection' directly. You must be careful to ensure that the first invariant is preserved: you should not call 'pgBegin', 'pgCommit', or 'pgRollback' on it. All other operations should be safe.
+  , connectionServerVer :: String
+  , connectionTypes :: IntMap.IntMap SqlType
+  , connectionFetchSize :: Word32 -- ^Number of rows to fetch (and cache) with 'HDBC.execute' and each time 'HDBC.fetchRow' requires more rows. A higher value will result in fewer round-trips to the database but potentially more wasted data. Defaults to 1. 0 means fetch all rows.
+  }
+
+sqlError :: IO a -> IO a
+sqlError = handle $ \(PGError m) -> 
+  let f c = BSC.unpack $ Map.findWithDefault BSC.empty c m
+      fC = f 'C'
+      fD = f 'D' in
+  throwIO HDBC.SqlError 
+    { HDBC.seState = fC
+    , HDBC.seNativeError = if null fC then -1 else fromMaybe 0 $ readMaybe (f 'P')
+    , HDBC.seErrorMsg = f 'S' ++ ": " ++ f 'M' ++ if null fD then fD else '\n':fD
+    }
+
+withPG :: Connection -> (PGConnection -> IO a) -> IO a
+withPG c = sqlError . withMVar (connectionPG c)
+
+takePGConnection :: PGConnection -> IO (MVar PGConnection)
+takePGConnection pg = do
+  addFinalizer pg (pgDisconnectOnce pg)
+  pgBegin pg
+  newMVar pg
+
+-- |Convert an existing 'PGConnection' to an HDBC-compatible 'Connection'.
+-- The caveats under 'connectionPG' apply if you plan to continue using the original 'PGConnection'.
+fromPGConnection :: PGConnection -> IO Connection
+fromPGConnection pg = do
+  pgv <- takePGConnection pg
+  reloadTypes Connection
+    { connectionPG = pgv
+    , connectionServerVer = maybe "" BSC.unpack $ pgServerVersion pg
+    , connectionTypes = mempty
+    , connectionFetchSize = 1
+    }
+
+-- |Connect to a database for HDBC use (equivalent to 'pgConnect' and 'pgBegin').
+connect :: PGDatabase -> IO Connection
+connect d = sqlError $ do
+  pg <- pgConnect d
+  fromPGConnection pg
+
+-- |Reload the table of all types from the database.
+-- This may be needed if you make structural changes to the database.
+reloadTypes :: Connection -> IO Connection
+reloadTypes c = withPG c $ \pg -> do
+  t <- pgLoadTypes pg
+  return c{ connectionTypes = IntMap.map (sqlType $ pgTypeEnv pg) t }
+
+-- |Change the 'connectionFetchSize' for new 'HDBC.Statement's created with 'HDBC.prepare'.
+-- Ideally this could be set with each call to 'HDBC.execute' and 'HDBC.fetchRow', but the HDBC interface provides no way to do this.
+setFetchSize :: Word32 -> Connection -> Connection
+setFetchSize i c = c{ connectionFetchSize = i }
+
+sqls :: String -> BSLC.ByteString
+sqls = BSLC.pack
+
+placeholders :: Int -> [SQLToken] -> [SQLToken]
+placeholders n (SQLQMark False : l) = SQLParam n : placeholders (succ n) l
+placeholders n (SQLQMark True : l) = SQLQMark False : placeholders n l
+placeholders n (t : l) = t : placeholders n l
+placeholders _ [] = []
+
+data ColDesc = ColDesc
+  { colDescName :: String
+  , colDesc :: HDBC.SqlColDesc
+  , colDescDecode :: PGValue -> HDBC.SqlValue
+  }
+
+data Cursor = Cursor
+  { cursorDesc :: [ColDesc]
+  , cursorRow :: [PGValues]
+  , cursorActive :: Bool
+  , _cursorStatement :: HDBC.Statement -- keep a handle to prevent GC
+  }
+
+noCursor :: HDBC.Statement -> Cursor
+noCursor = Cursor [] [] False
+
+getType :: Connection -> PGConnection -> Maybe Bool -> PGColDescription -> ColDesc
+getType c pg nul PGColDescription{..} = ColDesc
+  { colDescName = BSC.unpack colName
+  , colDesc = HDBC.SqlColDesc
+    { HDBC.colType = sqlTypeId t
+    , HDBC.colSize = fromIntegral colModifier <$ guard (colModifier >= 0)
+    , HDBC.colOctetLength = fromIntegral colSize <$ guard (colSize >= 0)
+    , HDBC.colDecDigits = Nothing
+    , HDBC.colNullable = nul
+    }
+  , colDescDecode = sqlTypeDecode t
+  } where t = IntMap.findWithDefault (sqlType (pgTypeEnv pg) $ show colType) (fromIntegral colType) (connectionTypes c)
+
+instance HDBC.IConnection Connection where
+  disconnect c = withPG c
+    pgDisconnectOnce
+  commit c = withPG c $ \pg -> do
+    pgCommitAll pg
+    pgBegin pg
+  rollback c = withPG c $ \pg -> do
+    pgRollbackAll pg
+    pgBegin pg
+  runRaw c q = withPG c $ \pg ->
+    pgSimpleQueries_ pg $ sqls q
+  run c q v = withPG c $ \pg -> do
+    let q' = sqls $ show $ placeholders 1 $ sqlTokens q
+        v' = map encode v
+    fromMaybe 0 <$> pgRun pg q' [] v'
+  prepare c q = do
+    let q' = sqls $ show $ placeholders 1 $ sqlTokens q
+    n <- withPG c $ \pg -> pgPrepare pg q' []
+    cr <- newIORef $ error "Cursor"
+    let
+      execute v = withPG c $ \pg -> do
+        d <- pgBind pg n (map encode v)
+        (r, e) <- pgFetch pg n (connectionFetchSize c)
+        modifyIORef' cr $ \p -> p
+          { cursorDesc = map (getType c pg Nothing) d
+          , cursorRow = r
+          , cursorActive = isNothing e
+          }
+        return $ fromMaybe 0 e
+      stmt = HDBC.Statement
+        { HDBC.execute = execute
+        , HDBC.executeRaw = void $ execute []
+        , HDBC.executeMany = mapM_ execute
+        , HDBC.finish = withPG c $ \pg -> do
+          writeIORef cr $ noCursor stmt
+          pgClose pg n
+        , HDBC.fetchRow = withPG c $ \pg -> do
+          p <- readIORef cr
+          fmap (zipWith colDescDecode (cursorDesc p)) <$> case cursorRow p of
+            [] | cursorActive p -> do
+                (rl, e) <- pgFetch pg n (connectionFetchSize c)
+                let rl' = uncons rl
+                writeIORef cr p
+                  { cursorRow = maybe [] snd rl'
+                  , cursorActive = isNothing e
+                  }
+                return $ fst <$> rl'
+               | otherwise ->
+                return Nothing
+            (r:l) -> do
+              writeIORef cr p{ cursorRow = l }
+              return $ Just r
+        , HDBC.getColumnNames =
+          map colDescName . cursorDesc <$> readIORef cr
+        , HDBC.originalQuery = q
+        , HDBC.describeResult =
+          map (colDescName &&& colDesc) . cursorDesc <$> readIORef cr
+        }
+    writeIORef cr $ noCursor stmt
+    addFinalizer stmt $ withPG c $ \pg -> pgClose pg n
+    return stmt
+  clone c = withPG c $ \pg -> do
+    pg' <- pgConnect $ pgConnectionDatabase pg
+    pgv <- takePGConnection pg'
+    return c{ connectionPG = pgv }
+  hdbcDriverName _ = "postgresql-typed"
+  hdbcClientVer _ = show version
+  proxiedClientName = HDBC.hdbcDriverName
+  proxiedClientVer = HDBC.hdbcClientVer
+  dbServerVer = connectionServerVer
+  dbTransactionSupport _ = True
+  getTables c = withPG c $ \pg ->
+    map (pgDecodeRep . head) . snd <$> pgSimpleQuery pg (BSLC.fromChunks
+      [ "SELECT relname "
+      ,   "FROM pg_class "
+      ,   "JOIN pg_namespace "
+      ,     "ON relnamespace = pg_namespace.oid "
+      ,  "WHERE nspname = ANY (current_schemas(false)) "
+      ,    "AND relkind IN ('r','v','m','f')"
+      ])
+  describeTable c t = withPG c $ \pg -> do
+    let makecol ~[attname, attrelid, attnum, atttypid, attlen, atttypmod, attnotnull] =
+          colDescName &&& colDesc $ getType c pg (Just $ not $ pgDecodeRep attnotnull) PGColDescription
+            { colName = pgDecodeRep attname
+            , colTable = pgDecodeRep attrelid
+            , colNumber = pgDecodeRep attnum
+            , colType = pgDecodeRep atttypid
+            , colSize = pgDecodeRep attlen
+            , colModifier = pgDecodeRep atttypmod
+            , colBinary = False
+            }
+    map makecol . snd <$> pgSimpleQuery pg (BSLC.fromChunks
+      [ "SELECT attname, attrelid, attnum, atttypid, attlen, atttypmod, attnotnull "
+      ,   "FROM pg_attribute "
+      ,   "JOIN pg_class "
+      ,     "ON attrelid = pg_class.oid "
+      ,   "JOIN pg_namespace "
+      ,     "ON relnamespace = pg_namespace.oid "
+      ,  "WHERE nspname = ANY (current_schemas(false)) "
+      ,    "AND relkind IN ('r','v','m','f') "
+      ,    "AND relname = ", pgLiteralRep t
+      ,   " AND attnum > 0 AND NOT attisdropped "
+      ,    "ORDER BY attnum"
+      ])
+
+encodeRep :: (PGParameter t a, PGRep t a) => a -> PGValue
+encodeRep x = PGTextValue $ pgEncode (pgTypeOf x) x
+
+encode :: HDBC.SqlValue -> PGValue
+encode (HDBC.SqlString x)                 = encodeRep x
+encode (HDBC.SqlByteString x)             = encodeRep x
+encode (HDBC.SqlWord32 x)                 = encodeRep x
+encode (HDBC.SqlWord64 x)                 = encodeRep (fromIntegral x :: Rational)
+encode (HDBC.SqlInt32 x)                  = encodeRep x
+encode (HDBC.SqlInt64 x)                  = encodeRep x
+encode (HDBC.SqlInteger x)                = encodeRep (fromInteger x :: Rational)
+encode (HDBC.SqlChar x)                   = encodeRep x
+encode (HDBC.SqlBool x)                   = encodeRep x
+encode (HDBC.SqlDouble x)                 = encodeRep x
+encode (HDBC.SqlRational x)               = encodeRep x
+encode (HDBC.SqlLocalDate x)              = encodeRep x
+encode (HDBC.SqlLocalTimeOfDay x)         = encodeRep x
+encode (HDBC.SqlZonedLocalTimeOfDay t z)  = encodeRep (t, z)
+encode (HDBC.SqlLocalTime x)              = encodeRep x
+encode (HDBC.SqlZonedTime x)              = encodeRep (zonedTimeToUTC x)
+encode (HDBC.SqlUTCTime x)                = encodeRep x
+encode (HDBC.SqlDiffTime x)               = encodeRep (realToFrac x :: DiffTime)
+encode (HDBC.SqlPOSIXTime x)              = encodeRep (realToFrac x :: Rational) -- (posixSecondsToUTCTime x)
+encode (HDBC.SqlEpochTime x)              = encodeRep (posixSecondsToUTCTime (fromInteger x))
+encode (HDBC.SqlTimeDiff x)               = encodeRep (fromIntegral x :: DiffTime)
+encode HDBC.SqlNull = PGNullValue
+
+data SqlType = SqlType
+  { sqlTypeId :: HDBC.SqlTypeId
+  , sqlTypeDecode :: PGValue -> HDBC.SqlValue
+  }
+
+sqlType :: PGTypeEnv -> String -> SqlType
+sqlType e t = SqlType
+  { sqlTypeId = typeId t
+  , sqlTypeDecode = decode t e
+  }
+
+typeId :: String -> HDBC.SqlTypeId
+typeId "boolean"                      = HDBC.SqlBitT
+typeId "bytea"                        = HDBC.SqlVarBinaryT
+typeId "\"char\""                     = HDBC.SqlCharT
+typeId "name"                         = HDBC.SqlVarCharT
+typeId "bigint"                       = HDBC.SqlBigIntT
+typeId "smallint"                     = HDBC.SqlSmallIntT
+typeId "integer"                      = HDBC.SqlIntegerT
+typeId "text"                         = HDBC.SqlLongVarCharT
+typeId "oid"                          = HDBC.SqlIntegerT
+typeId "real"                         = HDBC.SqlFloatT
+typeId "double precision"             = HDBC.SqlDoubleT
+typeId "abstime"                      = HDBC.SqlUTCDateTimeT
+typeId "reltime"                      = HDBC.SqlIntervalT HDBC.SqlIntervalSecondT
+typeId "tinterval"                    = HDBC.SqlIntervalT HDBC.SqlIntervalDayToSecondT
+typeId "bpchar"                       = HDBC.SqlVarCharT
+typeId "character varying"            = HDBC.SqlVarCharT
+typeId "date"                         = HDBC.SqlDateT
+typeId "time without time zone"       = HDBC.SqlTimeT
+typeId "timestamp without time zone"  = HDBC.SqlTimestampT
+typeId "timestamp with time zone"     = HDBC.SqlTimestampWithZoneT -- XXX really SQLUTCDateTimeT
+typeId "interval"                     = HDBC.SqlIntervalT HDBC.SqlIntervalDayToSecondT
+typeId "time with time zone"          = HDBC.SqlTimeWithZoneT
+typeId "numeric"                      = HDBC.SqlDecimalT
+typeId "uuid"                         = HDBC.SqlGUIDT
+typeId t = HDBC.SqlUnknownT t
+
+decodeRep :: PGColumn t a => PGTypeName t -> PGTypeEnv -> (a -> HDBC.SqlValue) -> PGValue -> HDBC.SqlValue
+decodeRep t e f (PGBinaryValue v) = f $ pgDecodeBinary e t v
+decodeRep t _ f (PGTextValue v) = f $ pgDecode t v
+decodeRep _ _ _ PGNullValue = HDBC.SqlNull
+
+#define DECODE(T) \
+  decode T e = decodeRep (PGTypeProxy :: PGTypeName T) e
+
+decode :: String -> PGTypeEnv -> PGValue -> HDBC.SqlValue
+DECODE("boolean")                     HDBC.SqlBool
+DECODE("\"char\"")                    HDBC.SqlChar
+DECODE("name")                        HDBC.SqlString
+DECODE("bigint")                      HDBC.SqlInt64
+DECODE("smallint")                    (HDBC.SqlInt32 . fromIntegral :: Int16 -> HDBC.SqlValue)
+DECODE("integer")                     HDBC.SqlInt32
+DECODE("text")                        HDBC.SqlString
+DECODE("oid")                         HDBC.SqlWord32
+DECODE("real")                        HDBC.SqlDouble
+DECODE("double precision")            HDBC.SqlDouble
+DECODE("bpchar")                      HDBC.SqlString
+DECODE("character varying")           HDBC.SqlString
+DECODE("date")                        HDBC.SqlLocalDate
+DECODE("time without time zone")      HDBC.SqlLocalTimeOfDay
+DECODE("time with time zone")         (uncurry HDBC.SqlZonedLocalTimeOfDay)
+DECODE("timestamp without time zone") HDBC.SqlLocalTime
+DECODE("timestamp with time zone")    HDBC.SqlUTCTime
+DECODE("interval")                    (HDBC.SqlDiffTime . realToFrac :: DiffTime -> HDBC.SqlValue)
+DECODE("numeric")                     HDBC.SqlRational
+decode _ _ = decodeRaw where
+  decodeRaw (PGBinaryValue v) = HDBC.SqlByteString v
+  decodeRaw (PGTextValue v)   = HDBC.SqlByteString v
+  decodeRaw PGNullValue       = HDBC.SqlNull
diff --git a/Database/PostgreSQL/Typed/Internal.hs b/Database/PostgreSQL/Typed/Internal.hs
deleted file mode 100644
--- a/Database/PostgreSQL/Typed/Internal.hs
+++ /dev/null
@@ -1,51 +0,0 @@
-{-# LANGUAGE PatternSynonyms, PatternGuards, TemplateHaskell, GADTs, KindSignatures, DataKinds #-}
-{-# OPTIONS_GHC -fno-warn-orphans #-}
-module Database.PostgreSQL.Typed.Internal
-  ( stringE
-  , pattern StringE
-  , SQLSplit(..)
-  , sqlSplitExprs
-  , sqlSplitParams
-  ) where
-
-import Data.Char (isDigit)
-import Data.String (IsString(..))
-import qualified Language.Haskell.TH as TH
-import Numeric (readDec)
-
--- |@'TH.LitE' . 'TH.stringL'@
-stringE :: String -> TH.Exp
-stringE = TH.LitE . TH.StringL
-
--- |Pattern match for 'stringE'.
-pattern StringE s = TH.LitE (TH.StringL s)
-
-instance IsString TH.Exp where
-  fromString = stringE
-
--- |Parsed representation of a SQL statement containing placeholders.
-data SQLSplit a (literal :: Bool) where
-  SQLLiteral :: String -> SQLSplit a 'False -> SQLSplit a 'True
-  SQLPlaceholder :: a -> SQLSplit a 'True -> SQLSplit a 'False
-  SQLSplitEnd :: SQLSplit a any
-
-sqlCons :: Char -> SQLSplit a 'True -> SQLSplit a 'True
-sqlCons c (SQLLiteral s l) = SQLLiteral (c : s) l
-sqlCons c SQLSplitEnd = SQLLiteral [c] SQLSplitEnd
-
--- |Parse a SQL stamement with ${string} placeholders into a 'SQLSplit'.
-sqlSplitExprs :: String -> SQLSplit String 'True
-sqlSplitExprs ('$':'$':'{':s) = sqlCons '$' $ sqlCons '{' $ sqlSplitExprs s
-sqlSplitExprs ('$':'{':s)
-  | (e, '}':r) <- break (\c -> c == '{' || c == '}') s = SQLLiteral "" $ SQLPlaceholder e $ sqlSplitExprs r
-  | otherwise = error $ "Error parsing SQL: could not find end of expression: ${" ++ s
-sqlSplitExprs (c:s) = sqlCons c $ sqlSplitExprs s
-sqlSplitExprs [] = SQLSplitEnd
-
--- |Parse a SQL stamement with $numeric placeholders into a 'SQLSplit'.
-sqlSplitParams :: String -> SQLSplit Int 'True
-sqlSplitParams ('$':'$':d:s) | isDigit d = sqlCons '$' $ sqlCons d $ sqlSplitParams s
-sqlSplitParams ('$':s@(d:_)) | isDigit d, [(n, r)] <- readDec s = SQLLiteral "" $ SQLPlaceholder n $ sqlSplitParams r
-sqlSplitParams (c:s) = sqlCons c $ sqlSplitParams s
-sqlSplitParams [] = SQLSplitEnd
-
diff --git a/Database/PostgreSQL/Typed/Protocol.hs b/Database/PostgreSQL/Typed/Protocol.hs
--- a/Database/PostgreSQL/Typed/Protocol.hs
+++ b/Database/PostgreSQL/Typed/Protocol.hs
@@ -12,7 +12,9 @@
   , PGConnection
   , PGError(..)
   , pgErrorCode
+  , pgConnectionDatabase
   , pgTypeEnv
+  , pgServerVersion
   , pgConnect
   , pgDisconnect
   , pgReconnect
@@ -27,13 +29,25 @@
   , pgBegin
   , pgCommit
   , pgRollback
+  , pgCommitAll
+  , pgRollbackAll
   , pgTransaction
+  -- * HDBC support
+  , pgDisconnectOnce
+  , pgRun
+  , PGPreparedStatement
+  , pgPrepare
+  , pgClose
+  , PGColDescription(..)
+  , PGRowDescription
+  , pgBind
+  , pgFetch
   ) where
 
 #if !MIN_VERSION_base(4,8,0)
 import Control.Applicative ((<$>), (<$))
 #endif
-import Control.Arrow ((&&&), second)
+import Control.Arrow ((&&&), first, second)
 import Control.Exception (Exception, throwIO, onException)
 import Control.Monad (void, liftM2, replicateM, when, unless)
 #ifdef USE_MD5
@@ -57,6 +71,7 @@
 #if !MIN_VERSION_base(4,8,0)
 import Data.Monoid (mempty)
 #endif
+import Data.Tuple (swap)
 import Data.Typeable (Typeable)
 #if !MIN_VERSION_base(4,8,0)
 import Data.Word (Word)
@@ -71,8 +86,7 @@
 import Database.PostgreSQL.Typed.Dynamic
 
 data PGState
-  = StateUnknown -- no Sync
-  | StateCommand -- was Sync, sent command
+  = StateUnsync -- no Sync
   | StatePending -- Sync sent
   -- ReadyForQuery received:
   | StateIdle
@@ -97,6 +111,12 @@
   PGDatabase h1 s1 n1 u1 p1 l1 _ _ == PGDatabase h2 s2 n2 u2 p2 l2 _ _ =
     h1 == h2 && s1 == s2 && n1 == n2 && u1 == u2 && p1 == p2 && l1 == l2
 
+newtype PGPreparedStatement = PGPreparedStatement Integer
+  deriving (Eq, Show)
+
+preparedStatementName :: PGPreparedStatement -> BS.ByteString
+preparedStatementName (PGPreparedStatement n) = BSC.pack $ show n
+
 -- |An established connection to the PostgreSQL server.
 -- These objects are not thread-safe and must only be used for a single request at a time.
 data PGConnection = PGConnection
@@ -106,20 +126,23 @@
   , connKey :: !Word32 -- unused
   , connParameters :: Map.Map BS.ByteString BS.ByteString
   , connTypeEnv :: PGTypeEnv
-  , connPreparedStatements :: IORef (Integer, Map.Map (BS.ByteString, [OID]) Integer)
+  , connPreparedStatementCount :: IORef Integer
+  , connPreparedStatementMap :: IORef (Map.Map (BS.ByteString, [OID]) PGPreparedStatement)
   , connState :: IORef PGState
   , connInput :: IORef (G.Decoder PGBackendMessage)
   , connTransaction :: IORef Word
   }
 
-data ColDescription = ColDescription
+data PGColDescription = PGColDescription
   { colName :: BS.ByteString
   , colTable :: !OID
   , colNumber :: !Int16
   , colType :: !OID
+  , colSize :: !Int16
   , colModifier :: !Int32
   , colBinary :: !Bool
   } deriving (Show)
+type PGRowDescription = [PGColDescription]
 
 type MessageFields = Map.Map Char BS.ByteString
 
@@ -128,12 +151,14 @@
 data PGFrontendMessage
   = StartupMessage [(BS.ByteString, BS.ByteString)] -- only sent first
   | CancelRequest !Word32 !Word32 -- sent first on separate connection
-  | Bind { statementName :: BS.ByteString, bindParameters :: PGValues, binaryColumns :: [Bool] }
-  | Close { statementName :: BS.ByteString }
+  | Bind { portalName :: BS.ByteString, statementName :: BS.ByteString, bindParameters :: PGValues, binaryColumns :: [Bool] }
+  | CloseStatement { statementName :: BS.ByteString }
+  | ClosePortal { portalName :: BS.ByteString }
   -- |Describe a SQL query/statement. The SQL string can contain
   --  parameters ($1, $2, etc.).
-  | Describe { statementName :: BS.ByteString }
-  | Execute !Word32
+  | DescribeStatement { statementName :: BS.ByteString }
+  | DescribePortal { portalName :: BS.ByteString }
+  | Execute { portalName :: BS.ByteString, executeRows :: !Word32 }
   | Flush
   -- |Parse SQL Destination (prepared statement)
   | Parse { statementName :: BS.ByteString, queryString :: BSL.ByteString, parseTypes :: [OID] }
@@ -177,7 +202,7 @@
   -- |A RowDescription contains the name, type, table OID, and
   --  column number of the resulting columns(s) of a query. The
   --  column number is useful for inferring nullability.
-  | RowDescription [ColDescription]
+  | RowDescription PGRowDescription
   deriving (Show)
 
 -- |PGException is thrown upon encountering an 'ErrorResponse' with severity of
@@ -192,8 +217,11 @@
 
 -- |Produce a human-readable string representing the message
 displayMessage :: MessageFields -> String
-displayMessage m = "PG" ++ f 'S' ++ " [" ++ f 'C' ++ "]: " ++ f 'M' ++ '\n' : f 'D'
-  where f c = BSC.unpack $ Map.findWithDefault BS.empty c m
+displayMessage m = "PG" ++ f 'S' ++ (if null fC then ": " else " [" ++ fC ++ "]: ") ++ f 'M' ++ (if null fD then fD else '\n' : fD)
+  where
+  fC = f 'C'
+  fD = f 'D'
+  f c = BSC.unpack $ Map.findWithDefault BS.empty c m
 
 makeMessage :: BS.ByteString -> BS.ByteString -> MessageFields
 makeMessage m d = Map.fromAscList [('D', d), ('M', m)]
@@ -217,9 +245,18 @@
 connLogMessage :: PGConnection -> MessageFields -> IO ()
 connLogMessage = pgDBLogMessage . connDatabase
 
+-- |The database information for this connection.
+pgConnectionDatabase :: PGConnection -> PGDatabase
+pgConnectionDatabase = connDatabase
+
+-- |The type environment for this connection.
 pgTypeEnv :: PGConnection -> PGTypeEnv
 pgTypeEnv = connTypeEnv
 
+-- |Retrieve the \"server_version\" parameter from the connection, if any.
+pgServerVersion :: PGConnection -> Maybe BS.ByteString
+pgServerVersion PGConnection{ connParameters = p } = Map.lookup (BSC.pack "server_version") p
+
 #ifdef USE_MD5
 md5 :: BS.ByteString -> BS.ByteString
 md5 = BA.convertToBase BA.Base16 . (Hash.hash :: BS.ByteString -> Hash.Digest Hash.MD5)
@@ -241,8 +278,9 @@
   <> Fold.foldMap (\(k, v) -> byteStringNul k <> byteStringNul v) kv <> nul)
 messageBody (CancelRequest pid key) = (Nothing, B.word32BE 80877102
   <> B.word32BE pid <> B.word32BE key)
-messageBody Bind{ statementName = n, bindParameters = p, binaryColumns = bc } = (Just 'B',
-  nul <> byteStringNul n
+messageBody Bind{ portalName = d, statementName = n, bindParameters = p, binaryColumns = bc } = (Just 'B',
+  byteStringNul d
+    <> byteStringNul n
     <> (if any fmt p
           then B.word16BE (fromIntegral $ length p) <> Fold.foldMap (B.word16BE . fromIntegral . fromEnum . fmt) p
           else B.word16BE 0)
@@ -256,12 +294,16 @@
   val PGNullValue = B.int32BE (-1)
   val (PGTextValue v) = B.word32BE (fromIntegral $ BS.length v) <> B.byteString v
   val (PGBinaryValue v) = B.word32BE (fromIntegral $ BS.length v) <> B.byteString v
-messageBody Close{ statementName = n } = (Just 'C', 
+messageBody CloseStatement{ statementName = n } = (Just 'C', 
   B.char7 'S' <> byteStringNul n)
-messageBody Describe{ statementName = n } = (Just 'D',
+messageBody ClosePortal{ portalName = n } = (Just 'C', 
+  B.char7 'P' <> byteStringNul n)
+messageBody DescribeStatement{ statementName = n } = (Just 'D',
   B.char7 'S' <> byteStringNul n)
-messageBody (Execute r) = (Just 'E',
-  nul <> B.word32BE r)
+messageBody DescribePortal{ portalName = n } = (Just 'D',
+  B.char7 'P' <> byteStringNul n)
+messageBody Execute{ portalName = n, executeRows = r } = (Just 'E',
+  byteStringNul n <> B.word32BE r)
 messageBody Flush = (Just 'H', mempty)
 messageBody Parse{ statementName = n, queryString = s, parseTypes = t } = (Just 'P',
   byteStringNul n <> lazyByteStringNul s
@@ -285,8 +327,7 @@
   state _ StateClosed = StateClosed
   state Sync _ = StatePending
   state Terminate _ = StateClosed
-  state _ StateUnknown = StateUnknown
-  state _ _ = StateCommand
+  state _ _ = StateUnsync
 
 pgFlush :: PGConnection -> IO ()
 pgFlush = hFlush . connHandle
@@ -318,14 +359,15 @@
     oid <- G.getWord32be -- table OID
     col <- G.getWord16be -- column number
     typ' <- G.getWord32be -- type
-    _ <- G.getWord16be -- type size
+    siz <- G.getWord16be -- type size
     tmod <- G.getWord32be -- type modifier
     fmt <- G.getWord16be -- format code
-    return $ ColDescription
+    return $ PGColDescription
       { colName = name
       , colTable = oid
       , colNumber = fromIntegral col
       , colType = typ'
+      , colSize = fromIntegral siz
       , colModifier = fromIntegral tmod
       , colBinary = toEnum (fromIntegral fmt)
       }
@@ -382,10 +424,8 @@
       else go $ r (Just b)
   got :: G.Decoder PGBackendMessage -> PGBackendMessage -> PGState -> IO (Maybe PGBackendMessage)
   got d (NoticeResponse m) _ = connLogMessage c m >> go d
-  got d (ReadyForQuery _) StateCommand = go d
   got d m@(ReadyForQuery s) _ = Just m <$ state s d
-  got d m@(ErrorResponse _) _ = Just m <$ state StateUnknown d
-  got d m StateCommand = Just m <$ state StateUnknown d
+  got d m@(ErrorResponse _) _ = Just m <$ state StateUnsync d
   got d m _ = Just m <$ next d
 
 -- |Receive the next message from PostgreSQL (low-level). Note that this will
@@ -403,8 +443,9 @@
 -- |Connect to a PostgreSQL server.
 pgConnect :: PGDatabase -> IO PGConnection
 pgConnect db = do
-  state <- newIORef StateUnknown
-  prep <- newIORef (0, Map.empty)
+  state <- newIORef StateUnsync
+  prepc <- newIORef 0
+  prepm <- newIORef Map.empty
   input <- newIORef getMessage
   tr <- newIORef 0
   h <- connectTo (pgDBHost db) (pgDBPort db)
@@ -415,7 +456,8 @@
         , connPid = 0
         , connKey = 0
         , connParameters = Map.empty
-        , connPreparedStatements = prep
+        , connPreparedStatementCount = prepc
+        , connPreparedStatementMap = prepm
         , connState = state
         , connTypeEnv = unknownPGTypeEnv
         , connInput = input
@@ -461,6 +503,14 @@
   pgSend c Terminate
   hClose h
 
+-- |Disconnect cleanly from the PostgreSQL server, but only if it's still connected.
+pgDisconnectOnce :: PGConnection -- ^ a handle from 'pgConnect'
+                 -> IO ()
+pgDisconnectOnce c@PGConnection{ connState = cs } = do
+  s <- readIORef cs
+  unless (s == StateClosed) $
+    pgDisconnect c
+
 -- |Possibly re-open a connection to a different database, either reusing the connection if the given database is already connected or closing it and opening a new one.
 -- Regardless, the input connection must not be used afterwards.
 pgReconnect :: PGConnection -> PGDatabase -> IO PGConnection
@@ -478,7 +528,7 @@
   case s of
     StateClosed -> fail "pgSync: operation on closed connection"
     StatePending -> wait True
-    StateUnknown -> wait False
+    StateUnsync -> wait False
     _ -> return ()
   where
   wait s = do
@@ -496,6 +546,11 @@
         connLogMessage c $ makeMessage (BSC.pack $ "Unexpected server message: " ++ show m) $ BSC.pack "Each statement should only contain a single query"
         wait s
     
+rowDescription :: PGBackendMessage -> PGRowDescription
+rowDescription (RowDescription d) = d
+rowDescription NoData = []
+rowDescription m = error $ "describe: unexpected response: " ++ show m
+
 -- |Describe a SQL statement/query. A statement description consists of 0 or
 -- more parameter descriptions (a PostgreSQL type) and zero or more result
 -- field descriptions (for queries) (consist of the name of the field, the
@@ -506,20 +561,15 @@
                   -> IO ([OID], [(BS.ByteString, OID, Bool)]) -- ^ a list of parameter types, and a list of result field names, types, and nullability indicators.
 pgDescribe h sql types nulls = do
   pgSync h
-  pgSend h $ Parse{ queryString = sql, statementName = BS.empty, parseTypes = types }
-  pgSend h $ Describe BS.empty
-  pgSend h Flush
+  pgSend h Parse{ queryString = sql, statementName = BS.empty, parseTypes = types }
+  pgSend h DescribeStatement{ statementName = BS.empty }
   pgSend h Sync
   pgFlush h
   ParseComplete <- pgReceive h
   ParameterDescription ps <- pgReceive h
-  m <- pgReceive h
-  (,) ps <$> case m of
-    NoData -> return []
-    RowDescription r -> mapM desc r
-    _ -> fail $ "describeStatement: unexpected response: " ++ show m
+  (,) ps <$> (mapM desc . rowDescription =<< pgReceive h)
   where
-  desc (ColDescription{ colName = name, colTable = tab, colNumber = col, colType = typ }) = do
+  desc (PGColDescription{ colName = name, colTable = tab, colNumber = col, colType = typ }) = do
     n <- nullable tab col
     return (name, typ, n)
   -- We don't get nullability indication from PostgreSQL, at least not directly.
@@ -536,12 +586,12 @@
         _ -> fail $ "Failed to determine nullability of column #" ++ show col
     | otherwise = return True
 
-rowsAffected :: BS.ByteString -> Int
+rowsAffected :: (Integral i, Read i) => BS.ByteString -> i
 rowsAffected = ra . BSC.words where
   ra [] = -1
   ra l = fromMaybe (-1) $ readMaybe $ BSC.unpack $ last l
 
--- Do we need to use the ColDescription here always, or are the request formats okay?
+-- Do we need to use the PGColDescription here always, or are the request formats okay?
 fixBinary :: [Bool] -> PGValues -> PGValues
 fixBinary (False:b) (PGBinaryValue x:r) = PGTextValue x : fixBinary b r
 fixBinary (True :b) (PGTextValue x:r) = PGBinaryValue x : fixBinary b r
@@ -588,22 +638,23 @@
   res m = fail $ "pgSimpleQueries_: unexpected response: " ++ show m
 
 pgPreparedBind :: PGConnection -> BS.ByteString -> [OID] -> PGValues -> [Bool] -> IO (IO ())
-pgPreparedBind c@PGConnection{ connPreparedStatements = psr } sql types bind bc = do
+pgPreparedBind c sql types bind bc = do
   pgSync c
-  (p, n) <- atomicModifyIORef' psr $ \(i, m) ->
-    maybe ((succ i, m), (False, i)) ((,) (i, m) . (,) True) $ Map.lookup key m
-  let sn = BSC.pack $ show n
+  m <- readIORef (connPreparedStatementMap c)
+  (p, n) <- maybe
+    (atomicModifyIORef' (connPreparedStatementCount c) (succ &&& (,) False . PGPreparedStatement))
+    (return . (,) True) $ Map.lookup key m
   unless p $
-    pgSend c $ Parse{ queryString = BSL.fromStrict sql, statementName = sn, parseTypes = types }
-  pgSend c $ Bind{ statementName = sn, bindParameters = bind, binaryColumns = bc }
+    pgSend c Parse{ queryString = BSL.fromStrict sql, statementName = preparedStatementName n, parseTypes = types }
+  pgSend c Bind{ portalName = BS.empty, statementName = preparedStatementName n, bindParameters = bind, binaryColumns = bc }
   let
     go = pgReceive c >>= start
     start ParseComplete = do
-      modifyIORef psr $ \(i, m) ->
-        (i, Map.insert key n m)
+      modifyIORef (connPreparedStatementMap c) $
+        Map.insert key n
       go
     start BindComplete = return ()
-    start m = fail $ "pgPrepared: unexpected response: " ++ show m
+    start r = fail $ "pgPrepared: unexpected response: " ++ show r
   return go
   where key = (sql, types)
 
@@ -616,8 +667,7 @@
   -> IO (Int, [PGValues])
 pgPreparedQuery c sql types bind bc = do
   start <- pgPreparedBind c sql types bind bc
-  pgSend c $ Execute 0
-  pgSend c Flush
+  pgSend c Execute{ portalName = BS.empty, executeRows = 0 }
   pgSend c Sync
   pgFlush c
   start
@@ -641,8 +691,8 @@
     go id
   where
   execute = do
-    pgSend c $ Execute count
-    pgSend c $ Flush
+    pgSend c Execute{ portalName = BS.empty, executeRows = count }
+    pgSend c Flush
     pgFlush c
   go r = pgReceive c >>= row r
   row r (DataRow fs) = go (r . (fixBinary bc fs :))
@@ -653,15 +703,10 @@
 
 -- |Close a previously prepared query (if necessary).
 pgCloseStatement :: PGConnection -> BS.ByteString -> [OID] -> IO ()
-pgCloseStatement c@PGConnection{ connPreparedStatements = psr } sql types = do
-  mn <- atomicModifyIORef psr $ \(i, m) ->
-    let (n, m') = Map.updateLookupWithKey (\_ _ -> Nothing) (sql, types) m in ((i, m'), n)
-  Fold.forM_ mn $ \n -> do
-    pgSync c
-    pgSend c $ Close{ statementName = BSC.pack $ show n }
-    pgFlush c
-    CloseComplete <- pgReceive c
-    return ()
+pgCloseStatement c sql types = do
+  mn <- atomicModifyIORef (connPreparedStatementMap c) $
+    swap . Map.updateLookupWithKey (\_ _ -> Nothing) (sql, types)
+  Fold.mapM_ (pgClose c) mn
 
 -- |Begin a new transaction. If there is already a transaction in progress (created with 'pgBegin' or 'pgTransaction') instead creates a savepoint.
 pgBegin :: PGConnection -> IO ()
@@ -685,6 +730,18 @@
   t <- atomicModifyIORef' tr predTransaction
   void $ pgSimpleQuery c $ BSLC.pack $ if t == 0 then "COMMIT" else "RELEASE SAVEPOINT pgt" ++ show t
 
+-- |Rollback all active 'pgBegin's.
+pgRollbackAll :: PGConnection -> IO ()
+pgRollbackAll c@PGConnection{ connTransaction = tr } = do
+  writeIORef tr 0
+  void $ pgSimpleQuery c $ BSLC.pack "ROLLBACK"
+
+-- |Commit all active 'pgBegin's.
+pgCommitAll :: PGConnection -> IO ()
+pgCommitAll c@PGConnection{ connTransaction = tr } = do
+  writeIORef tr 0
+  void $ pgSimpleQuery c $ BSLC.pack "COMMIT"
+
 -- |Wrap a computation in a 'pgBegin', 'pgCommit' block, or 'pgRollback' on exception.
 pgTransaction :: PGConnection -> IO a -> IO a
 pgTransaction c f = do
@@ -694,3 +751,82 @@
     pgCommit c
     return r)
     (pgRollback c)
+
+-- |Prepare, bind, execute, and close a single (unnamed) query, and return the number of rows affected, or Nothing if there are (ignored) result rows.
+pgRun :: PGConnection -> BSL.ByteString -> [OID] -> PGValues -> IO (Maybe Integer)
+pgRun c sql types bind = do
+  pgSync c
+  pgSend c Parse{ queryString = sql, statementName = BS.empty, parseTypes = types }
+  pgSend c Bind{ portalName = BS.empty, statementName = BS.empty, bindParameters = bind, binaryColumns = [] }
+  pgSend c Execute{ portalName = BS.empty, executeRows = 1 } -- 0 does not mean none
+  pgSend c Sync
+  pgFlush c
+  go where
+  go = pgReceive c >>= res
+  res ParseComplete = go
+  res BindComplete = go
+  res (DataRow _) = go
+  res PortalSuspended = return Nothing
+  res (CommandComplete d) = return (Just $ rowsAffected d)
+  res EmptyQueryResponse = return (Just 0)
+  res m = fail $ "pgRun: unexpected response: " ++ show m
+
+-- |Prepare a single query and return its handle.
+pgPrepare :: PGConnection -> BSL.ByteString -> [OID] -> IO PGPreparedStatement
+pgPrepare c sql types = do
+  n <- atomicModifyIORef' (connPreparedStatementCount c) (succ &&& PGPreparedStatement)
+  pgSync c
+  pgSend c Parse{ queryString = sql, statementName = preparedStatementName n, parseTypes = types }
+  pgSend c Sync
+  pgFlush c
+  ParseComplete <- pgReceive c
+  return n
+
+-- |Close a previously prepared query.
+pgClose :: PGConnection -> PGPreparedStatement -> IO ()
+pgClose c n = do
+  pgSync c
+  pgSend c ClosePortal{ portalName = preparedStatementName n }
+  pgSend c CloseStatement{ statementName = preparedStatementName n }
+  pgSend c Sync
+  pgFlush c
+  CloseComplete <- pgReceive c
+  CloseComplete <- pgReceive c
+  return ()
+
+-- |Bind a prepared statement, and return the row description.
+-- After 'pgBind', you must either call 'pgFetch' until it completes (returns @(_, 'Just' _)@) or 'pgFinish' before calling 'pgBind' again on the same prepared statement.
+pgBind :: PGConnection -> PGPreparedStatement -> PGValues -> IO PGRowDescription
+pgBind c n bind = do
+  pgSync c
+  pgSend c ClosePortal{ portalName = sn }
+  pgSend c Bind{ portalName = sn, statementName = sn, bindParameters = bind, binaryColumns = [] }
+  pgSend c DescribePortal{ portalName = sn }
+  pgSend c Sync
+  pgFlush c
+  CloseComplete <- pgReceive c
+  BindComplete <- pgReceive c
+  rowDescription <$> pgReceive c
+  where sn = preparedStatementName n
+
+-- |Fetch a single row from an executed prepared statement, returning the next N result rows (if any) and number of affected rows when complete.
+pgFetch :: PGConnection -> PGPreparedStatement -> Word32 -- ^Maximum number of rows to return, or 0 for all
+  -> IO ([PGValues], Maybe Integer)
+pgFetch c n count = do
+  pgSync c
+  pgSend c Execute{ portalName = preparedStatementName n, executeRows = count }
+  pgSend c Sync
+  pgFlush c
+  go where
+  go = pgReceive c >>= res
+  res (DataRow v) = first (v :) <$> go
+  res PortalSuspended = return ([], Nothing)
+  res (CommandComplete d) = do
+    pgSync c
+    pgSend c ClosePortal{ portalName = preparedStatementName n }
+    pgSend c Sync
+    pgFlush c
+    CloseComplete <- pgReceive c
+    return ([], Just $ rowsAffected d)
+  res EmptyQueryResponse = return ([], Just 0)
+  res m = fail $ "pgFetch: unexpected response: " ++ show m
diff --git a/Database/PostgreSQL/Typed/Query.hs b/Database/PostgreSQL/Typed/Query.hs
--- a/Database/PostgreSQL/Typed/Query.hs
+++ b/Database/PostgreSQL/Typed/Query.hs
@@ -35,11 +35,11 @@
 import qualified Language.Haskell.TH as TH
 import Language.Haskell.TH.Quote (QuasiQuoter(..))
 
-import Database.PostgreSQL.Typed.Internal
 import Database.PostgreSQL.Typed.Types
 import Database.PostgreSQL.Typed.Dynamic
 import Database.PostgreSQL.Typed.Protocol
 import Database.PostgreSQL.Typed.TH
+import Database.PostgreSQL.Typed.SQLToken
 
 class PGQuery q a | q -> a where
   -- |Execute a query and return the number of rows affected (or -1 if not known) and a list of results.
@@ -126,30 +126,24 @@
   PreparedQuery sql types bind bc = q e
 
 -- |Given a SQL statement with placeholders of the form @${expr}@, return a (hopefully) valid SQL statement with @$N@ placeholders and the list of expressions.
--- This does not understand strings or other SQL syntax, so any literal occurrence of the string @${@ must be escaped as @$${@.
--- Embedded expressions may not contain @{@ or @}@.
+-- This does its best to understand SQL syntax, so placeholders are only interpreted in places postgres would understand them (i.e., not in quoted strings).  Since this is not valid SQL otherwise, there is never reason to escape a literal @${@.
+-- You can use @$N@ placeholders in the query otherwise to refer to the N-th index placeholder expression.
 sqlPlaceholders :: String -> (String, [String])
-sqlPlaceholders = ssl 1 . sqlSplitExprs where
-  ssl :: Int -> SQLSplit String 'True -> (String, [String])
-  ssl n (SQLLiteral s l) = first (s ++) $ ssp n l
-  ssl _ SQLSplitEnd = ("", [])
-  ssp :: Int -> SQLSplit String 'False -> (String, [String])
-  ssp n (SQLPlaceholder e l) = (('$':show n) ++) *** (e :) $ ssl (succ n) l
-  ssp _ SQLSplitEnd = ("", [])
+sqlPlaceholders = sst (1 :: Int) . sqlTokens where
+  sst n (SQLExpr e : l) = (('$':show n) ++) *** (e :) $ sst (succ n) l
+  sst n (t : l) = first (show t ++) $ sst n l
+  sst _ [] = ("", [])
 
 -- |Given a SQL statement with placeholders of the form @$N@ and a list of TH 'ByteString' expressions, return a new 'ByteString' expression that substitutes the expressions for the placeholders.
--- This does not understand strings or other SQL syntax, so any literal occurrence of a string like @$N@ must be escaped as @$$N@.
 sqlSubstitute :: String -> [TH.Exp] -> TH.Exp
-sqlSubstitute sql exprl = TH.AppE (TH.VarE 'BS.concat) $ TH.ListE $ ssl $ sqlSplitParams sql where
+sqlSubstitute sql exprl = TH.AppE (TH.VarE 'BS.concat) $ TH.ListE $ map sst $ sqlTokens sql where
   bnds = (1, length exprl)
   exprs = listArray bnds exprl
   expr n
     | inRange bnds n = exprs ! n
-    | otherwise = error $ "SQL placeholder '$" ++ show n ++ "' out of range (not recognized by PostgreSQL); literal occurrences may need to be escaped with '$$'"
-  ssl (SQLLiteral s l) = TH.VarE 'fromString `TH.AppE` stringE s : ssp l
-  ssl SQLSplitEnd = []
-  ssp (SQLPlaceholder n l) = expr n : ssl l
-  ssp SQLSplitEnd = []
+    | otherwise = error $ "SQL placeholder '$" ++ show n ++ "' out of range (not recognized by PostgreSQL)"
+  sst (SQLParam n) = expr n
+  sst t = TH.VarE 'fromString `TH.AppE` TH.LitE (TH.StringL $ show t)
 
 splitCommas :: String -> [String]
 splitCommas = spl where
@@ -180,7 +174,7 @@
 makePGQuery QueryFlags{ flagQuery = False } sqle = pgSubstituteLiterals sqle
 makePGQuery QueryFlags{ flagNullable = nulls, flagPrepare = prep } sqle = do
   (pt, rt) <- TH.runIO $ tpgDescribe (fromString sqlp) (fromMaybe [] prep) (isNothing nulls)
-  when (length pt < length exprs) $ fail "Not all expression placeholders were recognized by PostgreSQL; literal occurrences of '${' may need to be escaped with '$${'"
+  when (length pt < length exprs) $ fail "Not all expression placeholders were recognized by PostgreSQL"
 
   e <- TH.newName "_tenv"
   (vars, vals) <- mapAndUnzipM (\t -> do
@@ -201,7 +195,7 @@
       (TH.ConE 'SimpleQuery
         `TH.AppE` sqlSubstitute sqlp vals)
       (\p -> TH.ConE 'PreparedQuery
-        `TH.AppE` (TH.VarE 'fromString `TH.AppE` stringE sqlp)
+        `TH.AppE` (TH.VarE 'fromString `TH.AppE` TH.LitE (TH.StringL sqlp))
         `TH.AppE` TH.ListE (map (TH.LitE . TH.IntegerL . toInteger . tpgValueTypeOID . snd) $ zip p pt)
         `TH.AppE` TH.ListE vals 
         `TH.AppE` TH.ListE 
@@ -250,8 +244,8 @@
 -- It will be replaced by a 'PGQuery' object that can be used to perform the SQL statement.
 -- If there are more @$N@ placeholders than expressions, it will instead be a function accepting the additional parameters and returning a 'PGQuery'.
 -- 
--- Note that all occurrences of @$N@ or @${@ will be treated as placeholders, regardless of their context in the SQL (e.g., even within SQL strings or other places placeholders are disallowed by PostgreSQL), which may cause invalid SQL or other errors.
--- If you need to pass a literal @$@ through in these contexts, you may double it to escape it as @$$N@ or @$${@.
+-- Ideally, this mimics postgres' SQL parsing, so that placeholders and expressions will only be expanded when they are in valid positions (i.e., not inside quoted strings).
+-- Since @${@ is not valid SQL otherwise, there should be no need to escape it.
 --
 -- The statement may start with one of more special flags affecting the interpretation:
 --
diff --git a/Database/PostgreSQL/Typed/Range.hs b/Database/PostgreSQL/Typed/Range.hs
--- a/Database/PostgreSQL/Typed/Range.hs
+++ b/Database/PostgreSQL/Typed/Range.hs
@@ -144,7 +144,7 @@
 isFull _ = False
 
 -- |Create a point range @[x,x]@
-point :: Eq a => a -> Range a
+point :: a -> Range a
 point a = Range (Lower (Bounded True a)) (Upper (Bounded True a))
 
 -- |Extract a point: @getPoint (point x) == Just x@
diff --git a/Database/PostgreSQL/Typed/SQLToken.hs b/Database/PostgreSQL/Typed/SQLToken.hs
new file mode 100644
--- /dev/null
+++ b/Database/PostgreSQL/Typed/SQLToken.hs
@@ -0,0 +1,131 @@
+-- |
+-- Module: Database.PostgreSQL.Typed.SQLToken
+-- Copyright: 2016 Dylan Simon
+-- 
+-- Parsing of SQL statements to safely identify placeholders.
+-- Supports both dollar-placeholders and question marks for HDBC.
+{-# LANGUAGE PatternGuards #-}
+module Database.PostgreSQL.Typed.SQLToken
+  ( SQLToken(..)
+  , sqlTokens
+  ) where
+
+import Control.Arrow (first)
+import Data.Char (isDigit, isAsciiUpper, isAsciiLower)
+import Data.List (stripPrefix)
+import Data.String (IsString(..))
+
+-- |A parsed SQL token.
+data SQLToken
+  = SQLToken String -- ^Raw (non-markup) SQL string
+  | SQLParam Int -- ^A \"$N\" parameter placeholder (this is the only non-string-preserving token: \"$012\" becomes \"$12\")
+  | SQLExpr String -- ^A \"${expr}\" expression placeholder
+  | SQLQMark Bool -- ^A possibly-escaped question-mark: False for \"?\" or True for \"\\?\"
+  deriving (Eq)
+
+-- |Produces the original SQL string
+instance Show SQLToken where
+  showsPrec _ (SQLToken s) = showString s
+  showsPrec _ (SQLParam p) = showChar '$' . shows p
+  showsPrec _ (SQLExpr e) = showString "${" . showString e . showChar '}'
+  showsPrec _ (SQLQMark False) = showChar '?'
+  showsPrec _ (SQLQMark True) = showString "\\?"
+  showList = flip $ foldr shows
+
+instance IsString SQLToken where
+  fromString = SQLToken
+
+type PH = String -> [SQLToken]
+
+infixr 4 ++:, +:
+
+(++:) :: String -> [SQLToken] -> [SQLToken]
+p ++: (SQLToken q : l) = SQLToken (p ++ q) : l
+p ++: l = SQLToken p : l
+
+(+:) :: Char -> [SQLToken] -> [SQLToken]
+p +: (SQLToken q : l) = SQLToken (p : q) : l
+p +: l = SQLToken [p] : l
+
+x :: PH
+x ('-':'-':s) = "--" ++: comment s
+x ('e':'\'':s) = "e'" ++: xe s
+x ('E':'\'':s) = "E'" ++: xe s
+x ('\'':s) = '\'' +: xq s
+x ('$':'{':s) = expr s
+x ('$':'$':s) = "$$" ++: xdolq "" s
+x ('$':c:s)
+  | dolqStart c
+  , (t,'$':r) <- span dolqCont s
+  = '$' : c : t ++: '$' +: xdolq (c:t) r
+  | isDigit c
+  , (i,r) <- span isDigit s
+  = SQLParam (read $ c:i) : x r
+x ('"':s) = '"' +: xd s
+x ('/':'*':s) = "/*" ++: xc 1 s
+x (c:s)
+  | identStart c
+  , (i,r) <- span identCont s
+  = c : i ++: x r
+x ('\\':'?':s) = SQLQMark True : x s
+x ('?':s) = SQLQMark False : x s
+x (c:s) = c +: x s
+x [] = []
+
+xthru :: (Char -> Bool) -> PH
+xthru f s = case break f s of
+  (p, c:r) -> p ++ [c] ++: x r
+  (p, []) -> [SQLToken p]
+
+comment :: PH
+comment = xthru (\n -> '\n' == n || '\r' == n)
+
+xe :: PH
+xe ('\\':c:s) = '\\' +: c +: xe s
+xe ('\'':s) = '\'' +: x s
+xe (c:s) = c +: xe s
+xe [] = []
+
+xq :: PH
+xq = xthru ('\'' ==)
+-- no need to handle xqdouble
+
+xd :: PH
+xd = xthru ('\"' ==)
+-- no need to handle xddouble
+
+identStart, identCont, dolqStart, dolqCont :: Char -> Bool
+identStart c = isAsciiUpper c || isAsciiLower c || c >= '\128' && c <= '\255' || c == '_'
+dolqStart = identStart
+dolqCont c = dolqStart c || isDigit c
+identCont c = dolqCont c || c == '$'
+
+xdolq :: String -> PH
+xdolq t = dolq where
+  dolq ('$':s)
+    | Just r <- stripPrefix t' s = '$':t' ++: x r
+  dolq (c:s) = c +: dolq s
+  dolq [] = []
+  t' = t ++ "$"
+
+xc :: Int -> PH
+xc 0 s = x s
+xc n ('/':'*':s) = "/*" ++: xc (succ n) s
+xc n ('*':'/':s) = "*/" ++: xc (pred n) s
+xc n (c:s) = c +: xc n s
+xc _ [] = []
+
+expr :: PH
+expr = pr . ex (0 :: Int) where
+  pr (e, Nothing) = [SQLToken ("${" ++ e)]
+  pr (e, Just r) = SQLExpr e : r
+  ex 0 ('}':s) = ("", Just $ x s)
+  ex n ('}':s) = first ('}':) $ ex (pred n) s
+  ex n ('{':s) = first ('{':) $ ex (succ n) s
+  ex n (c:s) = first (c:) $ ex n s
+  ex _ [] = ("", Nothing)
+
+-- |Parse a SQL string into a series of tokens.
+-- The 'showList' implementation for 'SQLToken' inverts this sequence back to a SQL string.
+sqlTokens :: String -> [SQLToken]
+sqlTokens = x
diff --git a/Database/PostgreSQL/Typed/TH.hs b/Database/PostgreSQL/Typed/TH.hs
--- a/Database/PostgreSQL/Typed/TH.hs
+++ b/Database/PostgreSQL/Typed/TH.hs
@@ -16,6 +16,9 @@
   , tpgTypeEncoder
   , tpgTypeDecoder
   , tpgTypeBinary
+  -- * HDBC support
+  , PGTypes
+  , pgLoadTypes
   ) where
 
 #if !MIN_VERSION_base(4,8,0)
@@ -74,13 +77,21 @@
 
 data TPGState = TPGState
   { tpgConnection :: PGConnection
-  , tpgTypes :: IntMap.IntMap TPGType -- keyed on fromIntegral OID
+  , tpgTypes :: PGTypes
   }
 
+-- |Map keyed on fromIntegral OID.
+type PGTypes = IntMap.IntMap TPGType
+
+-- |Load a map of types from the database.
+pgLoadTypes :: PGConnection -> IO PGTypes
+pgLoadTypes c =
+  IntMap.fromAscList . map (\[to, tn] -> (fromIntegral (pgDecodeRep to :: OID), pgDecodeRep tn)) .
+    snd <$> pgSimpleQuery c (BSLC.pack "SELECT typ.oid, format_type(CASE WHEN typtype = 'd' THEN typbasetype ELSE typ.oid END, -1) FROM pg_catalog.pg_type typ JOIN pg_catalog.pg_namespace nsp ON typnamespace = nsp.oid WHERE nspname <> 'pg_toast' AND nspname <> 'information_schema' ORDER BY typ.oid")
+
 tpgLoadTypes :: TPGState -> IO TPGState
 tpgLoadTypes tpg = do
-  t <- IntMap.fromAscList . map (\[to, tn] -> (fromIntegral (pgDecodeRep to :: OID), pgDecodeRep tn)) .
-    snd <$> pgSimpleQuery (tpgConnection tpg) (BSLC.pack "SELECT typ.oid, format_type(CASE WHEN typtype = 'd' THEN typbasetype ELSE typ.oid END, -1) FROM pg_catalog.pg_type typ JOIN pg_catalog.pg_namespace nsp ON typnamespace = nsp.oid WHERE nspname <> 'pg_toast' AND nspname <> 'information_schema' ORDER BY typ.oid")
+  t <- pgLoadTypes (tpgConnection tpg)
   return tpg{ tpgTypes = t }
 
 tpgInit :: PGConnection -> IO TPGState
@@ -123,7 +134,7 @@
 -- Error if not found.
 tpgType :: TPGState -> OID -> TPGType
 tpgType TPGState{ tpgTypes = types } t =
-  IntMap.findWithDefault (error $ "Unknown PostgreSQL type: " ++ show t) (fromIntegral t) types
+  IntMap.findWithDefault (error $ "Unknown PostgreSQL type: " ++ show t ++ "\nYour postgresql-typed application may need to be rebuilt.") (fromIntegral t) types
 
 -- |Lookup a type OID by type name.
 -- This is less common and thus less efficient than going the other way.
diff --git a/Database/PostgreSQL/Typed/Types.hs b/Database/PostgreSQL/Typed/Types.hs
--- a/Database/PostgreSQL/Typed/Types.hs
+++ b/Database/PostgreSQL/Typed/Types.hs
@@ -40,6 +40,7 @@
 #if !MIN_VERSION_base(4,8,0)
 import Control.Applicative ((<$>), (<$), (<*), (*>))
 #endif
+import Control.Arrow ((&&&))
 #ifdef USE_AESON
 import qualified Data.Aeson as JSON
 #endif
@@ -163,7 +164,6 @@
   pgDecodeValue _ _ PGNullValue = Nothing
   pgDecodeValue e t v = Just $ pgDecodeValue e t v
 
-
 -- |Final parameter encoding function used when a (nullable) parameter is passed to a prepared query.
 pgEncodeParameter :: PGParameter t a => PGTypeEnv -> PGTypeName t -> a -> PGValue
 pgEncodeParameter = pgEncodeValue
@@ -233,6 +233,17 @@
 #define BIN_DEC(F)
 #endif
 
+instance PGType "any"
+instance PGType t => PGColumn t PGValue where
+  pgDecode _ = PGTextValue
+  pgDecodeBinary _ _ = PGBinaryValue
+  pgDecodeValue _ _ = id
+instance PGParameter "any" PGValue where
+  pgEncode _ (PGTextValue v) = v
+  pgEncode _ PGNullValue = error "pgEncode any: NULL"
+  pgEncode _ (PGBinaryValue _) = error "pgEncode any: binary"
+  pgEncodeValue _ _ = id
+
 instance PGType "void"
 instance PGColumn "void" () where
   pgDecode _ _ = ()
@@ -298,12 +309,19 @@
 instance PGColumn "real" Float where
   pgDecode _ = read . BSC.unpack
   BIN_DEC(binDec BinD.float4)
+instance PGColumn "real" Double where
+  pgDecode _ = read . BSC.unpack
+  BIN_DEC((realToFrac .) . binDec BinD.float4)
 
 instance PGType "double precision" where BIN_COL
 instance PGParameter "double precision" Double where
   pgEncode _ = BSC.pack . show
   pgLiteral = pgEncode
   BIN_ENC(BinE.float8)
+instance PGParameter "double precision" Float where
+  pgEncode _ = BSC.pack . show
+  pgLiteral = pgEncode
+  BIN_ENC(BinE.float8 . realToFrac)
 instance PGColumn "double precision" Double where
   pgDecode _ = read . BSC.unpack
   BIN_DEC(binDec BinD.float8)
@@ -464,6 +482,16 @@
 binDecDatetime _ _ PGTypeEnv{ pgIntegerDatetimes = Nothing } = error "pgDecodeBinary: unknown integer_datetimes value"
 #endif
 
+-- PostgreSQL uses "[+-]HH[:MM]" timezone offsets, while "%z" uses "+HHMM" by default.
+-- readTime can successfully parse both formats, but PostgreSQL needs the colon.
+fixTZ :: String -> String
+fixTZ "" = ""
+fixTZ ['+',h1,h2] | isDigit h1 && isDigit h2 = ['+',h1,h2,':','0','0']
+fixTZ ['-',h1,h2] | isDigit h1 && isDigit h2 = ['-',h1,h2,':','0','0']
+fixTZ ['+',h1,h2,m1,m2] | isDigit h1 && isDigit h2 && isDigit m1 && isDigit m2 = ['+',h1,h2,':',m1,m2]
+fixTZ ['-',h1,h2,m1,m2] | isDigit h1 && isDigit h2 && isDigit m1 && isDigit m2 = ['-',h1,h2,':',m1,m2]
+fixTZ (c:s) = c:fixTZ s
+
 instance PGType "time without time zone" where
   pgBinaryColumn = binColDatetime
 instance PGParameter "time without time zone" Time.TimeOfDay where
@@ -478,6 +506,20 @@
   pgDecodeBinary = binDecDatetime BinD.time_int BinD.time_float
 #endif
 
+instance PGType "time with time zone" where
+  pgBinaryColumn = binColDatetime
+instance PGParameter "time with time zone" (Time.TimeOfDay, Time.TimeZone) where
+  pgEncode _ (t, z) = BSC.pack $ Time.formatTime defaultTimeLocale "%T%Q" t ++ fixTZ (Time.formatTime defaultTimeLocale "%z" z)
+  pgLiteral t = pgQuoteUnsafe . pgEncode t
+#ifdef USE_BINARY
+  pgEncodeValue = binEncDatetime BinE.timetz_int BinE.timetz_float
+#endif
+instance PGColumn "time with time zone" (Time.TimeOfDay, Time.TimeZone) where
+  pgDecode _ = (Time.localTimeOfDay . Time.zonedTimeToLocalTime &&& Time.zonedTimeZone) . readTime "%T%Q%z" . fixTZ . BSC.unpack
+#ifdef USE_BINARY
+  pgDecodeBinary = binDecDatetime BinD.timetz_int BinD.timetz_float
+#endif
+
 instance PGType "timestamp without time zone" where
   pgBinaryColumn = binColDatetime
 instance PGParameter "timestamp without time zone" Time.LocalTime where
@@ -491,16 +533,6 @@
 #ifdef USE_BINARY
   pgDecodeBinary = binDecDatetime BinD.timestamp_int BinD.timestamp_float
 #endif
-
--- PostgreSQL uses "[+-]HH[:MM]" timezone offsets, while "%z" uses "+HHMM" by default.
--- readTime can successfully parse both formats, but PostgreSQL needs the colon.
-fixTZ :: String -> String
-fixTZ "" = ""
-fixTZ ['+',h1,h2] | isDigit h1 && isDigit h2 = ['+',h1,h2,':','0','0']
-fixTZ ['-',h1,h2] | isDigit h1 && isDigit h2 = ['-',h1,h2,':','0','0']
-fixTZ ['+',h1,h2,m1,m2] | isDigit h1 && isDigit h2 && isDigit m1 && isDigit m2 = ['+',h1,h2,':',m1,m2]
-fixTZ ['-',h1,h2,m1,m2] | isDigit h1 && isDigit h2 && isDigit m1 && isDigit m2 = ['-',h1,h2,':',m1,m2]
-fixTZ (c:s) = c:fixTZ s
 
 instance PGType "timestamp with time zone" where
   pgBinaryColumn = binColDatetime
diff --git a/postgresql-typed.cabal b/postgresql-typed.cabal
--- a/postgresql-typed.cabal
+++ b/postgresql-typed.cabal
@@ -1,22 +1,25 @@
 Name:          postgresql-typed
-Version:       0.4.4
+Version:       0.4.5
 Cabal-Version: >= 1.8
 License:       BSD3
 License-File:  COPYING
-Copyright:     2010-2013 Chris Forno, 2014-2015 Dylan Simon
+Copyright:     2010-2013 Chris Forno, 2014-2016 Dylan Simon
 Author:        Dylan Simon
 Maintainer:    Dylan Simon <dylan-pgtyped@dylex.net>
-Stability:     beta
+Stability:     provisional
 Bug-Reports:   https://github.com/dylex/postgresql-typed/issues
 Homepage:      https://github.com/dylex/postgresql-typed
 Category:      Database
-Synopsis:      A PostgreSQL access library with compile-time SQL type inference
+Synopsis:      A PostgreSQL library with compile-time SQL type inference and optional HDBC backend
 Description:   Automatically type-check SQL statements at compile time.
                Uses Template Haskell and the raw PostgreSQL protocol to describe SQL statements at compile time and provide appropriate type marshalling for both parameters and results.
                Allows not only syntax verification of your SQL but also full type safety between your SQL and Haskell.
                Supports many built-in PostgreSQL types already, including arrays and ranges, and can be easily extended in user code to support any other types.
+               .
+               Also includes an optional HDBC backend that, since it uses the raw PostgreSQL protocol, may be more efficient than the normal libpq backend in some cases (though provides no more type safety than HDBC-postgresql when used without templates).
+               .
                Originally based on Chris Forno's templatepg library.
-Tested-With:   GHC == 7.8.4, GHC == 7.10.2
+Tested-With:   GHC == 7.10.3, GHC == 8.0.1
 Build-Type:    Simple
 
 source-repository head
@@ -47,9 +50,12 @@
   Description: Support decoding json via aeson.
   Default: True
 
+Flag HDBC
+  Description: Provide an HDBC driver backend using the raw PostgreSQL protocol.
+
 Library
   Build-Depends:
-    base >= 4.7 && < 5,
+    base >= 4.8 && < 5,
     array,
     binary,
     containers,
@@ -73,8 +79,10 @@
     Database.PostgreSQL.Typed.Inet
     Database.PostgreSQL.Typed.Dynamic
     Database.PostgreSQL.Typed.TemplatePG
+    Database.PostgreSQL.Typed.SQLToken
+    Database.PostgreSQL.Typed.ErrCodes
   Other-Modules:
-    Database.PostgreSQL.Typed.Internal
+    Paths_postgresql_typed
   GHC-Options: -Wall
   if flag(md5)
     Build-Depends: cryptonite >= 0.5, memory >= 0.5
@@ -95,13 +103,25 @@
   if flag(aeson)
     Build-Depends: aeson >= 0.7
     CPP-options: -DUSE_AESON
+  if flag(HDBC)
+    Build-Depends: HDBC >= 2.2
+    Exposed-Modules:
+      Database.PostgreSQL.Typed.HDBC
 
 test-suite test
-  build-depends: base, network, time, bytestring, postgresql-typed, QuickCheck
   type: exitcode-stdio-1.0
+  hs-source-dirs: test
   main-is: Main.hs
   Other-Modules: Connect
-  buildable: True
-  hs-source-dirs: test
   Extensions: TemplateHaskell, QuasiQuotes
+  build-depends: base, network, time, bytestring, postgresql-typed, QuickCheck
   GHC-Options: -Wall
+
+test-suite hdbc
+  type: exitcode-stdio-1.0
+  hs-source-dirs: test/hdbc, test
+  main-is: runtests.hs
+  if flag(HDBC)
+    build-depends: base, network, time, containers, convertible, postgresql-typed, HDBC, HUnit
+  else
+    buildable: False
diff --git a/test/Main.hs b/test/Main.hs
--- a/test/Main.hs
+++ b/test/Main.hs
@@ -1,9 +1,11 @@
 {-# LANGUAGE OverloadedStrings, FlexibleInstances, MultiParamTypeClasses, DataKinds, DeriveDataTypeable #-}
+{-# OPTIONS_GHC -fno-warn-orphans #-}
 -- {-# OPTIONS_GHC -ddump-splices #-}
 module Main (main) where
 
 import qualified Data.ByteString as BS
 import qualified Data.ByteString.Char8 as BSC
+import Data.Char (isDigit)
 import Data.Int (Int32)
 import qualified Data.Time as Time
 import System.Exit (exitSuccess, exitFailure)
@@ -16,6 +18,7 @@
 import qualified Database.PostgreSQL.Typed.Range as Range
 import Database.PostgreSQL.Typed.Enum
 import Database.PostgreSQL.Typed.Inet
+import Database.PostgreSQL.Typed.SQLToken
 
 import Connect
 
@@ -58,6 +61,14 @@
       then PGInet6 <$> Q.arbitrary <*> ((`mod` 129) <$> Q.arbitrary)
       else PGInet  <$> Q.arbitrary <*> ((`mod`  33) <$> Q.arbitrary)
 
+instance Q.Arbitrary SQLToken where
+  arbitrary = Q.oneof
+    [ SQLToken <$> Q.arbitrary
+    , SQLParam <$> Q.arbitrary
+    , SQLExpr <$> Q.arbitrary
+    , SQLQMark <$> Q.arbitrary
+    ]
+    
 newtype Str = Str { strString :: [Char] } deriving (Eq, Show)
 strByte :: Str -> BS.ByteString
 strByte = BSC.pack . strString
@@ -94,11 +105,36 @@
     , a Q.=== a'
     ]
 
+tokenProp :: String -> Q.Property
+tokenProp s =
+  not (has0 s) Q.==> s Q.=== show (sqlTokens s) where
+  has0 ('$':'0':c:_) | isDigit c = True
+  has0 (_:r) = has0 r
+  has0 [] = False
+
 main :: IO ()
 main = do
   c <- pgConnect db
 
-  r <- Q.quickCheckResult $ selectProp c
+  r <- Q.quickCheckResult
+    $ selectProp c
+    Q..&&. tokenProp
+    Q..&&. [pgSQL|#abc ${3.14::Float} def $f$ $$ ${1} $f$${2::Int32}|] Q.=== "abc 3.14::real def $f$ $$ ${1} $f$2::integer"
+    Q..&&. pgEnumValues Q.=== [(MyEnum_abc, "abc"), (MyEnum_DEF, "DEF"), (MyEnum_XX_ye, "XX_ye")]
+    Q..&&. Q.conjoin (map (\(s, t) -> sqlTokens s Q.=== t)
+      [ ("",
+        [])
+      , (  "SELECT a from b WHERE c = ?"
+        , ["SELECT a from b WHERE c = ", SQLQMark False])
+      , (  "INSERT INTO foo VALUES (?,?)"
+        , ["INSERT INTO foo VALUES (", SQLQMark False, ",", SQLQMark False, ")"])
+      , (  "INSERT INTO foo VALUES ('?','''?')"
+        , ["INSERT INTO foo VALUES ('?','''?')"])
+      , (  "-- really?\n-- yes'?\nINSERT INTO ? VALUES ('', ?, \"?asd\", e'?\\'?', '?''?', /* foo? */ /* foo /* bar */ ? */ ?)"
+        , ["-- really?\n-- yes'?\nINSERT INTO ", SQLQMark False, " VALUES ('', ", SQLQMark False, ", \"?asd\", e'?\\'?', '?''?', /* foo? */ /* foo /* bar */ ? */ ", SQLQMark False, ")"])
+        , (  "some ${things? {don't}} change$1 $1\\?"
+          , ["some ", SQLExpr "things? {don't}", " change$1 ", SQLParam 1, SQLQMark True])
+      ])
   assert $ isSuccess r
 
   ["box"] <- simple c 603
@@ -107,10 +143,6 @@
   ["box"] <- preparedApply c 603
   [Just "line"] <- prepared c 628 "line"
   ["line"] <- preparedApply c 628
-
-  assert $ [pgSQL|#abc${3.14 :: Float}def|] == "abc3.14::realdef"
-
-  assert $ pgEnumValues == [(MyEnum_abc, "abc"), (MyEnum_DEF, "DEF"), (MyEnum_XX_ye, "XX_ye")]
 
   pgDisconnect c
   exitSuccess
diff --git a/test/hdbc/runtests.hs b/test/hdbc/runtests.hs
new file mode 100644
--- /dev/null
+++ b/test/hdbc/runtests.hs
@@ -0,0 +1,16 @@
+{- arch-tag: Test runner
+-}
+
+module Main where 
+
+import Test.HUnit
+import System.Exit
+import Tests
+import TestUtils
+
+main = do
+  printDBInfo
+  r <- runTestTT tests
+  if errors r == 0 && failures r == 0
+    then exitSuccess
+    else exitFailure
