-module(cquill@adapter@postgres). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/cquill/adapter/postgres.gleam"). -export([default_config/1, config_from_url/2, host/2, port/2, database/2, user/2, password/2, ssl/2, pool_size/2, default_timeout/2, idle_interval/2, start/1, named_connection/1, postgres_capabilities/0, postgres_adapter/0, with_transaction/2, execute_transaction/2, execute_transaction_with_user_error/2, execute_sql/3, execute_sql_mutation/3, create_savepoint/2, rollback_to_savepoint/2, release_savepoint/2, execute_savepoint/3, execute_savepoint_with_user_error/3, execute_sql_with_timeout/4, execute_sql_mutation_with_timeout/4, default_batch_config/0, batch_delete_all/4, batch_delete_all_returning/4, delete_all_rows/2, insert_all_with_config/5, insert_all/4, insert_all_returning/4, batch_update_all/5, batch_update_all_returning/5, decode_int/2, decode_string/2, decode_float/2, decode_bool/2, decode_optional/3]). -export_type([postgres_config/0, postgres_connection/0, transaction_error/0, batch_config/0]). -if(?OTP_RELEASE >= 27). -define(MODULEDOC(Str), -moduledoc(Str)). -define(DOC(Str), -doc(Str)). -else. -define(MODULEDOC(Str), -compile([])). -define(DOC(Str), -compile([])). -endif. -type postgres_config() :: {postgres_config, gleam@erlang@process:name(pog:message()), binary(), integer(), binary(), binary(), gleam@option:option(binary()), pog:ssl(), integer(), integer(), integer()}. -opaque postgres_connection() :: {postgres_connection, pog:connection()}. -type transaction_error() :: {query_error, cquill@error:adapter_error()} | {rolled_back, binary()}. -type batch_config() :: {batch_config, integer(), boolean()}. -file("src/cquill/adapter/postgres.gleam", 59). ?DOC(" Create a default configuration with a pool name.\n"). -spec default_config(gleam@erlang@process:name(pog:message())) -> postgres_config(). default_config(Pool_name) -> {postgres_config, Pool_name, <<"127.0.0.1"/utf8>>, 5432, <<"postgres"/utf8>>, <<"postgres"/utf8>>, none, ssl_disabled, 10, 5000, 1000}. -file("src/cquill/adapter/postgres.gleam", 76). ?DOC( " Create configuration from a database URL.\n" " URL format: postgresql://user:password@host:port/database\n" ). -spec config_from_url(gleam@erlang@process:name(pog:message()), binary()) -> {ok, postgres_config()} | {error, nil}. config_from_url(Pool_name, Url) -> case pog:url_config(Pool_name, Url) of {ok, Pog_config} -> {ok, {postgres_config, erlang:element(2, Pog_config), erlang:element(3, Pog_config), erlang:element(4, Pog_config), erlang:element(5, Pog_config), erlang:element(6, Pog_config), erlang:element(7, Pog_config), erlang:element(8, Pog_config), erlang:element(10, Pog_config), 5000, erlang:element(13, Pog_config)}}; {error, _} -> {error, nil} end. -file("src/cquill/adapter/postgres.gleam", 99). ?DOC(" Set the database host\n"). -spec host(postgres_config(), binary()) -> postgres_config(). host(Config, Host) -> {postgres_config, erlang:element(2, Config), Host, erlang:element(4, Config), erlang:element(5, Config), erlang:element(6, Config), erlang:element(7, Config), erlang:element(8, Config), erlang:element(9, Config), erlang:element(10, Config), erlang:element(11, Config)}. -file("src/cquill/adapter/postgres.gleam", 104). ?DOC(" Set the database port\n"). -spec port(postgres_config(), integer()) -> postgres_config(). port(Config, Port) -> {postgres_config, erlang:element(2, Config), erlang:element(3, Config), Port, erlang:element(5, Config), erlang:element(6, Config), erlang:element(7, Config), erlang:element(8, Config), erlang:element(9, Config), erlang:element(10, Config), erlang:element(11, Config)}. -file("src/cquill/adapter/postgres.gleam", 109). ?DOC(" Set the database name\n"). -spec database(postgres_config(), binary()) -> postgres_config(). database(Config, Database) -> {postgres_config, erlang:element(2, Config), erlang:element(3, Config), erlang:element(4, Config), Database, erlang:element(6, Config), erlang:element(7, Config), erlang:element(8, Config), erlang:element(9, Config), erlang:element(10, Config), erlang:element(11, Config)}. -file("src/cquill/adapter/postgres.gleam", 114). ?DOC(" Set the database user\n"). -spec user(postgres_config(), binary()) -> postgres_config(). user(Config, User) -> {postgres_config, erlang:element(2, Config), erlang:element(3, Config), erlang:element(4, Config), erlang:element(5, Config), User, erlang:element(7, Config), erlang:element(8, Config), erlang:element(9, Config), erlang:element(10, Config), erlang:element(11, Config)}. -file("src/cquill/adapter/postgres.gleam", 119). ?DOC(" Set the database password\n"). -spec password(postgres_config(), gleam@option:option(binary())) -> postgres_config(). password(Config, Password) -> {postgres_config, erlang:element(2, Config), erlang:element(3, Config), erlang:element(4, Config), erlang:element(5, Config), erlang:element(6, Config), Password, erlang:element(8, Config), erlang:element(9, Config), erlang:element(10, Config), erlang:element(11, Config)}. -file("src/cquill/adapter/postgres.gleam", 127). ?DOC(" Set SSL mode\n"). -spec ssl(postgres_config(), pog:ssl()) -> postgres_config(). ssl(Config, Ssl) -> {postgres_config, erlang:element(2, Config), erlang:element(3, Config), erlang:element(4, Config), erlang:element(5, Config), erlang:element(6, Config), erlang:element(7, Config), Ssl, erlang:element(9, Config), erlang:element(10, Config), erlang:element(11, Config)}. -file("src/cquill/adapter/postgres.gleam", 132). ?DOC(" Set the connection pool size\n"). -spec pool_size(postgres_config(), integer()) -> postgres_config(). pool_size(Config, Pool_size) -> {postgres_config, erlang:element(2, Config), erlang:element(3, Config), erlang:element(4, Config), erlang:element(5, Config), erlang:element(6, Config), erlang:element(7, Config), erlang:element(8, Config), Pool_size, erlang:element(10, Config), erlang:element(11, Config)}. -file("src/cquill/adapter/postgres.gleam", 139). ?DOC( " Set the default query timeout in milliseconds.\n" " Queries taking longer than this will be aborted with a Timeout error.\n" " Default is 5000ms (5 seconds).\n" ). -spec default_timeout(postgres_config(), integer()) -> postgres_config(). default_timeout(Config, Timeout_ms) -> {postgres_config, erlang:element(2, Config), erlang:element(3, Config), erlang:element(4, Config), erlang:element(5, Config), erlang:element(6, Config), erlang:element(7, Config), erlang:element(8, Config), erlang:element(9, Config), Timeout_ms, erlang:element(11, Config)}. -file("src/cquill/adapter/postgres.gleam", 150). ?DOC( " Set the idle connection ping interval in milliseconds.\n" " The database is pinged every idle_interval when the connection is idle.\n" " This helps detect and recover from stale connections.\n" " Default is 1000ms (1 second).\n" ). -spec idle_interval(postgres_config(), integer()) -> postgres_config(). idle_interval(Config, Interval_ms) -> {postgres_config, erlang:element(2, Config), erlang:element(3, Config), erlang:element(4, Config), erlang:element(5, Config), erlang:element(6, Config), erlang:element(7, Config), erlang:element(8, Config), erlang:element(9, Config), erlang:element(10, Config), Interval_ms}. -file("src/cquill/adapter/postgres.gleam", 165). ?DOC(" Convert PostgresConfig to pog.Config\n"). -spec to_pog_config(postgres_config()) -> pog:config(). to_pog_config(Config) -> _pipe = pog:default_config(erlang:element(2, Config)), _pipe@1 = pog:host(_pipe, erlang:element(3, Config)), _pipe@2 = pog:port(_pipe@1, erlang:element(4, Config)), _pipe@3 = pog:database(_pipe@2, erlang:element(5, Config)), _pipe@4 = pog:user(_pipe@3, erlang:element(6, Config)), _pipe@5 = pog:password(_pipe@4, erlang:element(7, Config)), _pipe@6 = pog:ssl(_pipe@5, erlang:element(8, Config)), _pipe@7 = pog:pool_size(_pipe@6, erlang:element(9, Config)), pog:idle_interval(_pipe@7, erlang:element(11, Config)). -file("src/cquill/adapter/postgres.gleam", 179). ?DOC( " Start a connection pool using the provided configuration.\n" " Returns a PostgresConnection that can be used for queries.\n" ). -spec start(postgres_config()) -> {ok, postgres_connection()} | {error, gleam@otp@actor:start_error()}. start(Config) -> Pog_config = to_pog_config(Config), case pog:start(Pog_config) of {ok, {started, _, Connection}} -> {ok, {postgres_connection, Connection}}; {error, Err} -> {error, Err} end. -file("src/cquill/adapter/postgres.gleam", 192). ?DOC( " Create a connection reference to a named pool.\n" " Use this when you've started a pool with supervision and need to\n" " reference it by name.\n" ). -spec named_connection(gleam@erlang@process:name(pog:message())) -> postgres_connection(). named_connection(Name) -> {postgres_connection, pog:named_connection(Name)}. -file("src/cquill/adapter/postgres.gleam", 274). ?DOC(" Infer constraint type from common naming conventions\n"). -spec infer_from_constraint_name(binary()) -> binary(). infer_from_constraint_name(Constraint_lower) -> case gleam_stdlib:contains_string(Constraint_lower, <<"_pkey"/utf8>>) of true -> <<"23505"/utf8>>; false -> case gleam_stdlib:contains_string(Constraint_lower, <<"_key"/utf8>>) of true -> <<"23505"/utf8>>; false -> case gleam_stdlib:contains_string( Constraint_lower, <<"_unique"/utf8>> ) of true -> <<"23505"/utf8>>; false -> case gleam_stdlib:contains_string( Constraint_lower, <<"_fkey"/utf8>> ) of true -> <<"23503"/utf8>>; false -> case gleam_stdlib:contains_string( Constraint_lower, <<"_check"/utf8>> ) of true -> <<"23514"/utf8>>; false -> case gleam_stdlib:contains_string( Constraint_lower, <<"_not_null"/utf8>> ) of true -> <<"23502"/utf8>>; false -> <<"23000"/utf8>> end end end end end end. -file("src/cquill/adapter/postgres.gleam", 249). ?DOC( " Infer PostgreSQL error code from constraint name and detail string\n" "\n" " Since pog's ConstraintViolated doesn't include the PostgreSQL error code,\n" " we infer the constraint type from:\n" " 1. The detail string patterns (most reliable)\n" " 2. Common constraint naming conventions\n" ). -spec infer_constraint_code(binary(), binary()) -> binary(). infer_constraint_code(Constraint, Detail) -> Detail_lower = string:lowercase(Detail), Constraint_lower = string:lowercase(Constraint), case gleam_stdlib:contains_string(Detail_lower, <<"already exists"/utf8>>) of true -> <<"23505"/utf8>>; false -> case gleam_stdlib:contains_string( Detail_lower, <<"is not present"/utf8>> ) of true -> <<"23503"/utf8>>; false -> case gleam_stdlib:contains_string( Detail_lower, <<"is still referenced"/utf8>> ) of true -> <<"23503"/utf8>>; false -> infer_from_constraint_name(Constraint_lower) end end end. -file("src/cquill/adapter/postgres.gleam", 319). ?DOC(" Join a list of strings with a separator\n"). -spec join(list(binary()), binary()) -> binary(). join(Strings, Separator) -> case Strings of [] -> <<""/utf8>>; [First] -> First; [First@1 | Rest] -> <<<>/binary, (join(Rest, Separator))/binary>> end. -file("src/cquill/adapter/postgres.gleam", 311). -spec format_path(list(binary())) -> binary(). format_path(Path) -> case Path of [] -> <<"root"/utf8>>; Segments -> join(Segments, <<"."/utf8>>) end. -file("src/cquill/adapter/postgres.gleam", 303). ?DOC(" Format decode errors for display\n"). -spec format_decode_errors(list(gleam@dynamic@decode:decode_error())) -> binary(). format_decode_errors(Errors) -> _pipe = Errors, _pipe@1 = gleam@list:map( _pipe, fun(Err) -> <<<<<<"expected "/utf8, (erlang:element(2, Err))/binary>>/binary, " at "/utf8>>/binary, (format_path(erlang:element(4, Err)))/binary>> end ), join(_pipe@1, <<", "/utf8>>). -file("src/cquill/adapter/postgres.gleam", 201). ?DOC(" Convert a pog.QueryError to cquill.AdapterError\n"). -spec map_query_error(pog:query_error()) -> cquill@error:adapter_error(). map_query_error(Err) -> case Err of {constraint_violated, Message, Constraint, Detail} -> cquill@error:from_postgres_error( infer_constraint_code(Constraint, Detail), Message, Detail ); {postgresql_error, Code, _, Message@1} -> cquill@error:from_postgres_error(Code, Message@1, <<""/utf8>>); {unexpected_argument_count, Expected, Got} -> {query_failed, <<<<<<"Unexpected argument count: expected "/utf8, (erlang:integer_to_binary(Expected))/binary>>/binary, ", got "/utf8>>/binary, (erlang:integer_to_binary(Got))/binary>>, none}; {unexpected_argument_type, Expected@1, Got@1} -> {query_failed, <<<<<<"Unexpected argument type: expected "/utf8, Expected@1/binary>>/binary, ", got "/utf8>>/binary, Got@1/binary>>, none}; {unexpected_result_type, Decode_errors} -> {decode_failed, 0, <<"result"/utf8>>, <<"expected type"/utf8>>, format_decode_errors(Decode_errors)}; query_timeout -> timeout; connection_unavailable -> {connection_failed, <<"Connection unavailable"/utf8>>} end. -file("src/cquill/adapter/postgres.gleam", 332). ?DOC(" Convert cquill QueryParam to pog.Value\n"). -spec param_to_value(cquill@adapter:query_param()) -> pog:value(). param_to_value(Param) -> case Param of {param_int, I} -> pog_ffi:coerce(I); {param_float, F} -> pog_ffi:coerce(F); {param_string, S} -> pog_ffi:coerce(S); {param_bool, B} -> pog_ffi:coerce(B); param_null -> pog_ffi:null(); {param_bytes, Bytes} -> pog_ffi:coerce(Bytes); {param_custom, _, Value} -> pog_ffi:coerce(Value) end. -file("src/cquill/adapter/postgres.gleam", 345). ?DOC(" Convert a list of QueryParams to pog.Values\n"). -spec params_to_values(list(cquill@adapter:query_param())) -> list(pog:value()). params_to_values(Params) -> gleam@list:map(Params, fun param_to_value/1). -file("src/cquill/adapter/postgres.gleam", 373). ?DOC(" Wrap a dynamic row into a list format for consistency with adapter interface\n"). -spec wrap_row(gleam@dynamic:dynamic_()) -> list(gleam@dynamic:dynamic_()). wrap_row(Row) -> cquill_ffi:tuple_to_list(Row). -file("src/cquill/adapter/postgres.gleam", 426). ?DOC(" Add parameters to a pog query\n"). -spec add_parameters(pog:'query'(PDO), list(pog:value())) -> pog:'query'(PDO). add_parameters(Query, Values) -> gleam@list:fold(Values, Query, fun(Q, V) -> pog:parameter(Q, V) end). -file("src/cquill/adapter/postgres.gleam", 354). ?DOC(" Execute a query and return all rows as List(Dynamic)\n"). -spec execute_query(postgres_connection(), cquill@adapter:compiled_query()) -> {ok, list(list(gleam@dynamic:dynamic_()))} | {error, cquill@error:adapter_error()}. execute_query(Conn, Compiled) -> {postgres_connection, Pool} = Conn, Values = params_to_values(erlang:element(3, Compiled)), Query = begin _pipe = pog:'query'(erlang:element(2, Compiled)), _pipe@1 = add_parameters(_pipe, Values), pog:returning( _pipe@1, {decoder, fun gleam@dynamic@decode:decode_dynamic/1} ) end, case pog:execute(Query, Pool) of {ok, {returned, _, Rows}} -> {ok, gleam@list:map(Rows, fun wrap_row/1)}; {error, Err} -> {error, map_query_error(Err)} end. -file("src/cquill/adapter/postgres.gleam", 386). ?DOC(" Execute a mutation (INSERT/UPDATE/DELETE) and return affected count\n"). -spec execute_mutation(postgres_connection(), cquill@adapter:compiled_query()) -> {ok, integer()} | {error, cquill@error:adapter_error()}. execute_mutation(Conn, Compiled) -> {postgres_connection, Pool} = Conn, Values = params_to_values(erlang:element(3, Compiled)), Query = begin _pipe = pog:'query'(erlang:element(2, Compiled)), _pipe@1 = add_parameters(_pipe, Values), pog:returning( _pipe@1, {decoder, fun gleam@dynamic@decode:decode_dynamic/1} ) end, case pog:execute(Query, Pool) of {ok, {returned, Count, _}} -> {ok, Count}; {error, Err} -> {error, map_query_error(Err)} end. -file("src/cquill/adapter/postgres.gleam", 405). ?DOC(" Execute an INSERT with RETURNING clause\n"). -spec execute_returning(postgres_connection(), cquill@adapter:compiled_query()) -> {ok, gleam@option:option(list(gleam@dynamic:dynamic_()))} | {error, cquill@error:adapter_error()}. execute_returning(Conn, Compiled) -> {postgres_connection, Pool} = Conn, Values = params_to_values(erlang:element(3, Compiled)), Query = begin _pipe = pog:'query'(erlang:element(2, Compiled)), _pipe@1 = add_parameters(_pipe, Values), pog:returning( _pipe@1, {decoder, fun gleam@dynamic@decode:decode_dynamic/1} ) end, case pog:execute(Query, Pool) of {ok, {returned, _, [Row]}} -> {ok, {some, wrap_row(Row)}}; {ok, {returned, _, []}} -> {ok, none}; {ok, {returned, _, _}} -> {ok, none}; {error, Err} -> {error, map_query_error(Err)} end. -file("src/cquill/adapter/postgres.gleam", 437). ?DOC( " Begin a transaction - pog handles this differently via pog.transaction\n" " For the adapter interface, we return the same connection since pog\n" " manages transaction state internally.\n" ). -spec begin_transaction(postgres_connection()) -> {ok, postgres_connection()} | {error, cquill@error:adapter_error()}. begin_transaction(Conn) -> {ok, Conn}. -file("src/cquill/adapter/postgres.gleam", 447). ?DOC(" Commit a transaction\n"). -spec commit_transaction(postgres_connection()) -> {ok, nil} | {error, cquill@error:adapter_error()}. commit_transaction(_) -> {ok, nil}. -file("src/cquill/adapter/postgres.gleam", 453). ?DOC(" Rollback a transaction\n"). -spec rollback_transaction(postgres_connection()) -> {ok, nil} | {error, cquill@error:adapter_error()}. rollback_transaction(_) -> {ok, nil}. -file("src/cquill/adapter/postgres.gleam", 463). ?DOC(" PostgreSQL adapter capabilities - full SQL support\n"). -spec postgres_capabilities() -> cquill@adapter:adapter_capabilities(). postgres_capabilities() -> {adapter_capabilities, true, true, true, true, {some, 65535}, true, true}. -file("src/cquill/adapter/postgres.gleam", 477). ?DOC( " Create a PostgreSQL adapter.\n" " This implements the full adapter interface for PostgreSQL.\n" ). -spec postgres_adapter() -> cquill@adapter:adapter(postgres_connection(), list(gleam@dynamic:dynamic_())). postgres_adapter() -> cquill@adapter:new( <<"postgres"/utf8>>, postgres_capabilities(), fun execute_query/2, fun execute_mutation/2, fun execute_returning/2, fun begin_transaction/1, fun commit_transaction/1, fun rollback_transaction/1 ). -file("src/cquill/adapter/postgres.gleam", 497). ?DOC( " Run a function within a PostgreSQL transaction.\n" " This uses pog's native transaction support which provides proper\n" " transaction isolation and automatic commit/rollback.\n" ). -spec with_transaction( postgres_connection(), fun((postgres_connection()) -> {ok, PEB} | {error, binary()}) ) -> {ok, PEB} | {error, transaction_error()}. with_transaction(Conn, Operation) -> {postgres_connection, Pool} = Conn, case pog:transaction( Pool, fun(Tx_pool) -> Tx_conn = {postgres_connection, Tx_pool}, Operation(Tx_conn) end ) of {ok, Result} -> {ok, Result}; {error, {transaction_query_error, Query_err}} -> {error, {query_error, map_query_error(Query_err)}}; {error, {transaction_rolled_back, Reason}} -> {error, {rolled_back, Reason}} end. -file("src/cquill/adapter/postgres.gleam", 527). ?DOC( " Execute a transaction with automatic error handling.\n" " This version uses the standard cquill TransactionError type.\n" " - On success: commits and returns the result\n" " - On adapter error (constraint violation, etc.): rolls back and returns AdapterTransactionError\n" " - On pog query error during transaction: returns AdapterTransactionError\n" ). -spec execute_transaction( postgres_connection(), fun((postgres_connection()) -> {ok, PEG} | {error, cquill@error:adapter_error()}) ) -> {ok, PEG} | {error, cquill@error:transaction_error(nil)}. execute_transaction(Conn, Operation) -> {postgres_connection, Pool} = Conn, case pog:transaction( Pool, fun(Tx_pool) -> Tx_conn = {postgres_connection, Tx_pool}, Operation(Tx_conn) end ) of {ok, Result} -> {ok, Result}; {error, {transaction_query_error, Query_err}} -> Adapter_err = map_query_error(Query_err), case Query_err of {postgresql_error, <<"40001"/utf8>>, _, _} -> {error, serialization_failure}; query_timeout -> {error, transaction_timeout}; _ -> {error, {adapter_transaction_error, Adapter_err}} end; {error, {transaction_rolled_back, Adapter_err@1}} -> {error, {adapter_transaction_error, Adapter_err@1}} end. -file("src/cquill/adapter/postgres.gleam", 561). ?DOC( " Execute a transaction with a user error type.\n" " This version allows the operation to return a custom error type.\n" " - On success: commits and returns the result\n" " - On user error: rolls back and returns UserError\n" " - On pog query error during transaction: returns AdapterTransactionError\n" ). -spec execute_transaction_with_user_error( postgres_connection(), fun((postgres_connection()) -> {ok, PEM} | {error, PEN}) ) -> {ok, PEM} | {error, cquill@error:transaction_error(PEN)}. execute_transaction_with_user_error(Conn, Operation) -> {postgres_connection, Pool} = Conn, case pog:transaction( Pool, fun(Tx_pool) -> Tx_conn = {postgres_connection, Tx_pool}, Operation(Tx_conn) end ) of {ok, Result} -> {ok, Result}; {error, {transaction_query_error, Query_err}} -> Adapter_err = map_query_error(Query_err), case Query_err of {postgresql_error, <<"40001"/utf8>>, _, _} -> {error, serialization_failure}; query_timeout -> {error, transaction_timeout}; _ -> {error, {adapter_transaction_error, Adapter_err}} end; {error, {transaction_rolled_back, User_error}} -> {error, {user_error, User_error}} end. -file("src/cquill/adapter/postgres.gleam", 727). ?DOC( " Escape a SQL identifier (table name, savepoint name, etc.)\n" " to prevent SQL injection.\n" ). -spec escape_identifier(binary()) -> binary(). escape_identifier(Name) -> <<<<"\""/utf8, (gleam@string:replace(Name, <<"\""/utf8>>, <<"\"\""/utf8>>))/binary>>/binary, "\""/utf8>>. -file("src/cquill/adapter/postgres.gleam", 739). ?DOC( " Execute a raw SQL query with parameters.\n" " Returns rows as List(Dynamic).\n" ). -spec execute_sql( postgres_connection(), binary(), list(cquill@adapter:query_param()) ) -> {ok, list(list(gleam@dynamic:dynamic_()))} | {error, cquill@error:adapter_error()}. execute_sql(Conn, Sql, Params) -> execute_query(Conn, {compiled_query, Sql, Params, 0}). -file("src/cquill/adapter/postgres.gleam", 752). ?DOC( " Execute a raw SQL mutation with parameters.\n" " Returns the number of affected rows.\n" ). -spec execute_sql_mutation( postgres_connection(), binary(), list(cquill@adapter:query_param()) ) -> {ok, integer()} | {error, cquill@error:adapter_error()}. execute_sql_mutation(Conn, Sql, Params) -> execute_mutation(Conn, {compiled_query, Sql, Params, 0}). -file("src/cquill/adapter/postgres.gleam", 597). ?DOC( " Create a savepoint with the given name.\n" " Savepoints allow partial rollback within a transaction.\n" " The connection must be within an active transaction.\n" ). -spec create_savepoint(postgres_connection(), binary()) -> {ok, nil} | {error, cquill@error:savepoint_error(nil)}. create_savepoint(Conn, Name) -> Sql = <<"SAVEPOINT "/utf8, (escape_identifier(Name))/binary>>, case execute_sql_mutation(Conn, Sql, []) of {ok, _} -> {ok, nil}; {error, Adapter_err} -> {error, {savepoint_creation_failed, cquill@error:format_error(Adapter_err)}} end. -file("src/cquill/adapter/postgres.gleam", 612). ?DOC( " Rollback to a named savepoint.\n" " All changes made after the savepoint was created are discarded.\n" " The savepoint remains active and can be rolled back to again.\n" ). -spec rollback_to_savepoint(postgres_connection(), binary()) -> {ok, nil} | {error, cquill@error:savepoint_error(nil)}. rollback_to_savepoint(Conn, Name) -> Sql = <<"ROLLBACK TO SAVEPOINT "/utf8, (escape_identifier(Name))/binary>>, case execute_sql_mutation(Conn, Sql, []) of {ok, _} -> {ok, nil}; {error, Adapter_err} -> {error, {savepoint_adapter_error, Adapter_err}} end. -file("src/cquill/adapter/postgres.gleam", 626). ?DOC( " Release a savepoint without rolling back.\n" " This destroys the savepoint but keeps all changes made after it.\n" " This is useful to free up resources when a savepoint is no longer needed.\n" ). -spec release_savepoint(postgres_connection(), binary()) -> {ok, nil} | {error, cquill@error:savepoint_error(nil)}. release_savepoint(Conn, Name) -> Sql = <<"RELEASE SAVEPOINT "/utf8, (escape_identifier(Name))/binary>>, case execute_sql_mutation(Conn, Sql, []) of {ok, _} -> {ok, nil}; {error, Adapter_err} -> {error, {savepoint_release_failed, cquill@error:format_error(Adapter_err)}} end. -file("src/cquill/adapter/postgres.gleam", 642). ?DOC( " Execute a function within a savepoint.\n" " If the function succeeds, the savepoint is released.\n" " If the function fails, changes are rolled back to the savepoint.\n" " This must be called within an active transaction.\n" ). -spec execute_savepoint( postgres_connection(), binary(), fun((postgres_connection()) -> {ok, PFC} | {error, cquill@error:adapter_error()}) ) -> {ok, PFC} | {error, cquill@error:savepoint_error(nil)}. execute_savepoint(Conn, Name, Operation) -> case create_savepoint(Conn, Name) of {error, Err} -> {error, Err}; {ok, _} -> case Operation(Conn) of {ok, Result} -> case release_savepoint(Conn, Name) of {ok, _} -> {ok, Result}; {error, Err@1} -> {error, Err@1} end; {error, Adapter_err} -> _ = rollback_to_savepoint(Conn, Name), {error, {savepoint_adapter_error, Adapter_err}} end end. -file("src/cquill/adapter/postgres.gleam", 675). ?DOC( " Execute a function within a savepoint, allowing user errors.\n" " If the function succeeds, the savepoint is released.\n" " If the function fails, changes are rolled back to the savepoint.\n" ). -spec execute_savepoint_with_user_error( postgres_connection(), binary(), fun((postgres_connection()) -> {ok, PFI} | {error, PFJ}) ) -> {ok, PFI} | {error, cquill@error:savepoint_error(PFJ)}. execute_savepoint_with_user_error(Conn, Name, Operation) -> case create_savepoint(Conn, Name) of {error, {savepoint_creation_failed, Reason}} -> {error, {savepoint_creation_failed, Reason}}; {error, {savepoint_adapter_error, Adapter_err}} -> {error, {savepoint_adapter_error, Adapter_err}}; {error, {savepoint_not_found, N}} -> {error, {savepoint_not_found, N}}; {error, {savepoint_release_failed, R}} -> {error, {savepoint_release_failed, R}}; {error, savepoint_no_transaction} -> {error, savepoint_no_transaction}; {error, {savepoint_user_error, _}} -> {error, {savepoint_creation_failed, <<"Unexpected user error"/utf8>>}}; {ok, _} -> case Operation(Conn) of {ok, Result} -> case release_savepoint(Conn, Name) of {ok, _} -> {ok, Result}; {error, {savepoint_release_failed, Reason@1}} -> {error, {savepoint_release_failed, Reason@1}}; {error, {savepoint_adapter_error, Adapter_err@1}} -> {error, {savepoint_adapter_error, Adapter_err@1}}; {error, {savepoint_not_found, N@1}} -> {error, {savepoint_not_found, N@1}}; {error, {savepoint_creation_failed, R@1}} -> {error, {savepoint_creation_failed, R@1}}; {error, savepoint_no_transaction} -> {error, savepoint_no_transaction}; {error, {savepoint_user_error, _}} -> {error, {savepoint_release_failed, <<"Unexpected user error"/utf8>>}} end; {error, User_err} -> _ = rollback_to_savepoint(Conn, Name), {error, {savepoint_user_error, User_err}} end end. -file("src/cquill/adapter/postgres.gleam", 766). ?DOC( " Execute a raw SQL query with a custom timeout.\n" " The timeout is in milliseconds. Use this for queries that may take\n" " longer than the default 5 second timeout.\n" ). -spec execute_sql_with_timeout( postgres_connection(), binary(), list(cquill@adapter:query_param()), integer() ) -> {ok, list(list(gleam@dynamic:dynamic_()))} | {error, cquill@error:adapter_error()}. execute_sql_with_timeout(Conn, Sql, Params, Timeout_ms) -> {postgres_connection, Pool} = Conn, Values = params_to_values(Params), Query = begin _pipe = pog:'query'(Sql), _pipe@1 = add_parameters(_pipe, Values), _pipe@2 = pog:timeout(_pipe@1, Timeout_ms), pog:returning( _pipe@2, {decoder, fun gleam@dynamic@decode:decode_dynamic/1} ) end, case pog:execute(Query, Pool) of {ok, {returned, _, Rows}} -> {ok, gleam@list:map(Rows, fun wrap_row/1)}; {error, Err} -> {error, map_query_error(Err)} end. -file("src/cquill/adapter/postgres.gleam", 789). ?DOC( " Execute a raw SQL mutation with a custom timeout.\n" " The timeout is in milliseconds.\n" ). -spec execute_sql_mutation_with_timeout( postgres_connection(), binary(), list(cquill@adapter:query_param()), integer() ) -> {ok, integer()} | {error, cquill@error:adapter_error()}. execute_sql_mutation_with_timeout(Conn, Sql, Params, Timeout_ms) -> {postgres_connection, Pool} = Conn, Values = params_to_values(Params), Query = begin _pipe = pog:'query'(Sql), _pipe@1 = add_parameters(_pipe, Values), _pipe@2 = pog:timeout(_pipe@1, Timeout_ms), pog:returning( _pipe@2, {decoder, fun gleam@dynamic@decode:decode_dynamic/1} ) end, case pog:execute(Query, Pool) of {ok, {returned, Count, _}} -> {ok, Count}; {error, Err} -> {error, map_query_error(Err)} end. -file("src/cquill/adapter/postgres.gleam", 825). ?DOC(" Default batch configuration for Postgres\n"). -spec default_batch_config() -> batch_config(). default_batch_config() -> {batch_config, 1000, true}. -file("src/cquill/adapter/postgres.gleam", 948). ?DOC(" Build parameter placeholders ($1, $2, etc.)\n"). -spec build_placeholders(integer(), integer(), list(binary())) -> list(binary()). build_placeholders(Count, Current_idx, Acc) -> case Count of 0 -> lists:reverse(Acc); N -> Placeholder = <<"$"/utf8, (erlang:integer_to_binary(Current_idx))/binary>>, build_placeholders(N - 1, Current_idx + 1, [Placeholder | Acc]) end. -file("src/cquill/adapter/postgres.gleam", 1029). ?DOC( " Adjust parameter indices in a WHERE clause\n" " e.g., \"$1\" becomes \"$3\" if start_idx is 3\n" ). -spec adjust_param_indices(binary(), integer()) -> binary(). adjust_param_indices(Where_clause, _) -> Where_clause. -file("src/cquill/adapter/postgres.gleam", 1061). ?DOC( " Delete all rows matching a condition.\n" " Returns the number of deleted rows.\n" ). -spec batch_delete_all( postgres_connection(), binary(), binary(), list(cquill@adapter:query_param()) ) -> {ok, integer()} | {error, cquill@error:adapter_error()}. batch_delete_all(Conn, Table, Where_clause, Where_params) -> Sql = <<<<<<"DELETE FROM "/utf8, (escape_identifier(Table))/binary>>/binary, " WHERE "/utf8>>/binary, Where_clause/binary>>, execute_sql_mutation(Conn, Sql, Where_params). -file("src/cquill/adapter/postgres.gleam", 1073). ?DOC(" Delete all rows matching a condition and return deleted rows.\n"). -spec batch_delete_all_returning( postgres_connection(), binary(), binary(), list(cquill@adapter:query_param()) ) -> {ok, list(list(gleam@dynamic:dynamic_()))} | {error, cquill@error:adapter_error()}. batch_delete_all_returning(Conn, Table, Where_clause, Where_params) -> Sql = <<<<<<<<"DELETE FROM "/utf8, (escape_identifier(Table))/binary>>/binary, " WHERE "/utf8>>/binary, Where_clause/binary>>/binary, " RETURNING *"/utf8>>, execute_sql(Conn, Sql, Where_params). -file("src/cquill/adapter/postgres.gleam", 1090). ?DOC( " Delete all rows in a table (truncate-like operation).\n" " Returns the number of deleted rows.\n" ). -spec delete_all_rows(postgres_connection(), binary()) -> {ok, integer()} | {error, cquill@error:adapter_error()}. delete_all_rows(Conn, Table) -> Sql = <<"DELETE FROM "/utf8, (escape_identifier(Table))/binary>>, execute_sql_mutation(Conn, Sql, []). -file("src/cquill/adapter/postgres.gleam", 1103). -spec do_chunk_list(list(PIO), integer(), list(PIO), list(list(PIO))) -> list(list(PIO)). do_chunk_list(Items, Max_size, Current_chunk, Chunks) -> case Items of [] -> case Current_chunk of [] -> lists:reverse(Chunks); _ -> lists:reverse([lists:reverse(Current_chunk) | Chunks]) end; [Item | Rest] -> New_chunk = [Item | Current_chunk], case erlang:length(New_chunk) >= Max_size of true -> do_chunk_list( Rest, Max_size, [], [lists:reverse(New_chunk) | Chunks] ); false -> do_chunk_list(Rest, Max_size, New_chunk, Chunks) end end. -file("src/cquill/adapter/postgres.gleam", 1099). ?DOC(" Chunk a list into sublists of max_size\n"). -spec chunk_list(list(PIK), integer()) -> list(list(PIK)). chunk_list(Items, Max_size) -> do_chunk_list(Items, Max_size, [], []). -file("src/cquill/adapter/postgres.gleam", 1127). ?DOC(" Join a list of strings with a separator\n"). -spec join_strings(list(binary()), binary()) -> binary(). join_strings(Strings, Separator) -> case Strings of [] -> <<""/utf8>>; [First] -> First; [First@1 | Rest] -> <<<>/binary, (join_strings(Rest, Separator))/binary>> end. -file("src/cquill/adapter/postgres.gleam", 937). ?DOC(" Build a single VALUES clause like ($1, $2, $3)\n"). -spec build_value_clause( list(cquill@adapter:query_param()), integer(), integer() ) -> {binary(), integer()}. build_value_clause(_, Column_count, Start_idx) -> Placeholders = build_placeholders(Column_count, Start_idx, []), Clause = <<<<"("/utf8, (join_strings(Placeholders, <<", "/utf8>>))/binary>>/binary, ")"/utf8>>, {Clause, Start_idx + Column_count}. -file("src/cquill/adapter/postgres.gleam", 909). ?DOC(" Build a multi-value INSERT statement\n"). -spec build_multi_value_insert( binary(), list(binary()), list(list(cquill@adapter:query_param())), integer() ) -> {binary(), list(cquill@adapter:query_param())}. build_multi_value_insert(Table, Columns, Rows, Column_count) -> Column_list = join_strings(Columns, <<", "/utf8>>), Insert_prefix = <<<<<<<<"INSERT INTO "/utf8, (escape_identifier(Table))/binary>>/binary, " ("/utf8>>/binary, Column_list/binary>>/binary, ") VALUES "/utf8>>, {Values_clauses, All_params, _} = gleam@list:fold( Rows, {[], [], 1}, fun(Acc, Row) -> {Clauses, Params, Param_idx} = Acc, {Value_clause, New_idx} = build_value_clause( Row, Column_count, Param_idx ), {[Value_clause | Clauses], lists:append(Params, Row), New_idx} end ), Values_sql = join_strings(lists:reverse(Values_clauses), <<", "/utf8>>), {<>, All_params}. -file("src/cquill/adapter/postgres.gleam", 896). ?DOC(" Insert a single chunk using multi-value INSERT\n"). -spec insert_single_chunk( postgres_connection(), binary(), list(binary()), list(list(cquill@adapter:query_param())) ) -> {ok, integer()} | {error, cquill@error:adapter_error()}. insert_single_chunk(Conn, Table, Columns, Rows) -> Column_count = erlang:length(Columns), {Sql, All_params} = build_multi_value_insert( Table, Columns, Rows, Column_count ), execute_sql_mutation(Conn, Sql, All_params). -file("src/cquill/adapter/postgres.gleam", 878). ?DOC(" Insert multiple chunks sequentially\n"). -spec insert_chunks( postgres_connection(), binary(), list(binary()), list(list(list(cquill@adapter:query_param()))), integer() ) -> {ok, integer()} | {error, cquill@error:adapter_error()}. insert_chunks(Conn, Table, Columns, Chunks, Total) -> case Chunks of [] -> {ok, Total}; [Chunk | Rest] -> case insert_single_chunk(Conn, Table, Columns, Chunk) of {error, E} -> {error, E}; {ok, Count} -> insert_chunks(Conn, Table, Columns, Rest, Total + Count) end end. -file("src/cquill/adapter/postgres.gleam", 844). ?DOC(" Insert multiple rows with configuration options.\n"). -spec insert_all_with_config( postgres_connection(), binary(), list(binary()), list(list(cquill@adapter:query_param())), batch_config() ) -> {ok, integer()} | {error, cquill@error:adapter_error()}. insert_all_with_config(Conn, Table, Columns, Rows, Config) -> case Rows of [] -> {ok, 0}; _ -> Chunks = chunk_list(Rows, erlang:element(2, Config)), case erlang:element(3, Config) of true -> case execute_transaction( Conn, fun(Tx_conn) -> insert_chunks(Tx_conn, Table, Columns, Chunks, 0) end ) of {ok, Count} -> {ok, Count}; {error, {adapter_transaction_error, E}} -> {error, E}; {error, _} -> {error, {query_failed, <<"Batch insert failed"/utf8>>, none}} end; false -> insert_chunks(Conn, Table, Columns, Chunks, 0) end end. -file("src/cquill/adapter/postgres.gleam", 834). ?DOC( " Insert multiple rows using a multi-value INSERT statement.\n" " This is more efficient than individual inserts.\n" "\n" " Example SQL generated:\n" " INSERT INTO users (email, name) VALUES ($1, $2), ($3, $4), ($5, $6)\n" ). -spec insert_all( postgres_connection(), binary(), list(binary()), list(list(cquill@adapter:query_param())) ) -> {ok, integer()} | {error, cquill@error:adapter_error()}. insert_all(Conn, Table, Columns, Rows) -> insert_all_with_config(Conn, Table, Columns, Rows, default_batch_config()). -file("src/cquill/adapter/postgres.gleam", 964). ?DOC( " Insert multiple rows and return the inserted rows.\n" " Uses RETURNING * to get all inserted data.\n" ). -spec insert_all_returning( postgres_connection(), binary(), list(binary()), list(list(cquill@adapter:query_param())) ) -> {ok, list(list(gleam@dynamic:dynamic_()))} | {error, cquill@error:adapter_error()}. insert_all_returning(Conn, Table, Columns, Rows) -> case Rows of [] -> {ok, []}; _ -> Column_count = erlang:length(Columns), {Base_sql, All_params} = build_multi_value_insert( Table, Columns, Rows, Column_count ), Sql = <>, execute_sql(Conn, Sql, All_params) end. -file("src/cquill/adapter/postgres.gleam", 1011). ?DOC(" Build SET clause for UPDATE\n"). -spec build_set_clause( list({binary(), cquill@adapter:query_param()}), integer() ) -> {binary(), list(cquill@adapter:query_param()), integer()}. build_set_clause(Clauses, Start_idx) -> {Parts, Params, Idx} = gleam@list:fold( Clauses, {[], [], Start_idx}, fun(Acc, Clause) -> {Sql_parts, Param_acc, Current_idx} = Acc, {Column, Param} = Clause, Sql_part = <<<>/binary, (erlang:integer_to_binary(Current_idx))/binary>>, {[Sql_part | Sql_parts], [Param | Param_acc], Current_idx + 1} end ), Sql = join_strings(lists:reverse(Parts), <<", "/utf8>>), {Sql, lists:reverse(Params), Idx}. -file("src/cquill/adapter/postgres.gleam", 988). ?DOC( " Update all rows matching a condition.\n" " Returns the number of updated rows.\n" "\n" " Example:\n" " update_all(conn, \"users\", [#(\"active\", adapter.param_bool(False))],\n" " \"created_at < $1\", [adapter.param_string(\"2024-01-01\")])\n" ). -spec batch_update_all( postgres_connection(), binary(), list({binary(), cquill@adapter:query_param()}), binary(), list(cquill@adapter:query_param()) ) -> {ok, integer()} | {error, cquill@error:adapter_error()}. batch_update_all(Conn, Table, Set_clauses, Where_clause, Where_params) -> {Set_sql, Set_params, Next_idx} = build_set_clause(Set_clauses, 1), Adjusted_where = adjust_param_indices(Where_clause, Next_idx), Sql = <<<<<<<<<<"UPDATE "/utf8, (escape_identifier(Table))/binary>>/binary, " SET "/utf8>>/binary, Set_sql/binary>>/binary, " WHERE "/utf8>>/binary, Adjusted_where/binary>>, All_params = lists:append(Set_params, Where_params), execute_sql_mutation(Conn, Sql, All_params). -file("src/cquill/adapter/postgres.gleam", 1036). ?DOC(" Update all rows matching a condition and return updated rows.\n"). -spec batch_update_all_returning( postgres_connection(), binary(), list({binary(), cquill@adapter:query_param()}), binary(), list(cquill@adapter:query_param()) ) -> {ok, list(list(gleam@dynamic:dynamic_()))} | {error, cquill@error:adapter_error()}. batch_update_all_returning(Conn, Table, Set_clauses, Where_clause, Where_params) -> {Set_sql, Set_params, Next_idx} = build_set_clause(Set_clauses, 1), Adjusted_where = adjust_param_indices(Where_clause, Next_idx), Sql = <<<<<<<<<<<<"UPDATE "/utf8, (escape_identifier(Table))/binary>>/binary, " SET "/utf8>>/binary, Set_sql/binary>>/binary, " WHERE "/utf8>>/binary, Adjusted_where/binary>>/binary, " RETURNING *"/utf8>>, All_params = lists:append(Set_params, Where_params), execute_sql(Conn, Sql, All_params). -file("src/cquill/adapter/postgres.gleam", 1219). -spec do_list_at(list(PJR), integer()) -> gleam@option:option(PJR). do_list_at(List, Index) -> case List of [] -> none; [First | Rest] -> case Index of 0 -> {some, First}; _ -> do_list_at(Rest, Index - 1) end end. -file("src/cquill/adapter/postgres.gleam", 1212). ?DOC(" Get an element from a list by index\n"). -spec list_at(list(PJO), integer()) -> gleam@option:option(PJO). list_at(List, Index) -> case Index < 0 of true -> none; false -> do_list_at(List, Index) end. -file("src/cquill/adapter/postgres.gleam", 1148). ?DOC(" Decode a single integer value from a result row\n"). -spec decode_int(list(gleam@dynamic:dynamic_()), integer()) -> {ok, integer()} | {error, nil}. decode_int(Row, Index) -> case list_at(Row, Index) of {some, Value} -> case gleam@dynamic@decode:run( Value, {decoder, fun gleam@dynamic@decode:decode_int/1} ) of {ok, I} -> {ok, I}; {error, _} -> {error, nil} end; none -> {error, nil} end. -file("src/cquill/adapter/postgres.gleam", 1160). ?DOC(" Decode a single string value from a result row\n"). -spec decode_string(list(gleam@dynamic:dynamic_()), integer()) -> {ok, binary()} | {error, nil}. decode_string(Row, Index) -> case list_at(Row, Index) of {some, Value} -> case gleam@dynamic@decode:run( Value, {decoder, fun gleam@dynamic@decode:decode_string/1} ) of {ok, S} -> {ok, S}; {error, _} -> {error, nil} end; none -> {error, nil} end. -file("src/cquill/adapter/postgres.gleam", 1172). ?DOC(" Decode a single float value from a result row\n"). -spec decode_float(list(gleam@dynamic:dynamic_()), integer()) -> {ok, float()} | {error, nil}. decode_float(Row, Index) -> case list_at(Row, Index) of {some, Value} -> case gleam@dynamic@decode:run( Value, {decoder, fun gleam@dynamic@decode:decode_float/1} ) of {ok, F} -> {ok, F}; {error, _} -> {error, nil} end; none -> {error, nil} end. -file("src/cquill/adapter/postgres.gleam", 1184). ?DOC(" Decode a single bool value from a result row\n"). -spec decode_bool(list(gleam@dynamic:dynamic_()), integer()) -> {ok, boolean()} | {error, nil}. decode_bool(Row, Index) -> case list_at(Row, Index) of {some, Value} -> case gleam@dynamic@decode:run( Value, {decoder, fun gleam@dynamic@decode:decode_bool/1} ) of {ok, B} -> {ok, B}; {error, _} -> {error, nil} end; none -> {error, nil} end. -file("src/cquill/adapter/postgres.gleam", 1196). ?DOC(" Decode an optional value from a result row\n"). -spec decode_optional( list(gleam@dynamic:dynamic_()), integer(), gleam@dynamic@decode:decoder(PJJ) ) -> {ok, gleam@option:option(PJJ)} | {error, nil}. decode_optional(Row, Index, Decoder) -> case list_at(Row, Index) of {some, Value} -> case gleam@dynamic@decode:run( Value, gleam@dynamic@decode:optional(Decoder) ) of {ok, Opt} -> {ok, Opt}; {error, _} -> {error, nil} end; none -> {error, nil} end.