Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name = "Postgres"
uuid = "8f23287e-300e-4f50-bc2b-9f1dfe95da84"
version = "2.0.0"
version = "2.1.0"

[deps]
ConcurrentUtilities = "f0e56b4a-5159-44fe-b623-3e5288b988bb"
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,12 @@ Connection options support:

- libpq-style keyword strings such as `host=127.0.0.1 port=5432 user=postgres dbname=postgres`.
- PostgreSQL URIs such as `postgresql://postgres:postgres@127.0.0.1:5432/postgres`.
- Environment defaults: `PGHOST`, `PGPORT`, `PGUSER`, `PGPASSWORD`, `PGDATABASE`, `PGAPPNAME`, `PGCONNECT_TIMEOUT`, and TLS-related `PGSSL*` variables.
- Environment defaults: `PGHOST`, `PGPORT`, `PGUSER`, `PGPASSWORD`, `PGDATABASE`, `PGAPPNAME`, `PGCONNECT_TIMEOUT`, `PGOPTIONS`, and TLS-related `PGSSL*` variables.
- `sslmode` values: `disable`, `prefer` (the default), `require`, `verify-full`. Only `verify-full` verifies the server's certificate; `require` encrypts without authenticating the server, and the default `prefer` falls back to an unencrypted connection if the server declines TLS. Use `verify-full` with `sslrootcert` when the connection needs to be authenticated.
- TLS files: `sslrootcert`, `sslcert`, `sslkey`, and `sslcapath` (`sslcapath` is a fallback CA bundle or directory, used only when `sslrootcert` is unset and ignored otherwise). `sslservername` overrides the TLS server name when connecting to a pre-resolved address; under `verify-full` it is also the name the certificate is verified against, so it must name the server you intend to authenticate.
- `connect_timeout` (seconds) and `statement_timeout` (milliseconds).
- `application_name` and `statement_cache_maxsize`.
- `options`: server command-line options applied when the session starts, as in libpq (`PGOPTIONS`). For example `options='-c search_path=myschema'` sets the default schema. The value is sent in the startup packet, so it also applies after an automatic reconnect.

See the [support policy](https://JuliaDatabases.github.io/Postgres.jl/dev/support/)
for tested Julia and PostgreSQL versions, TLS limits, and transaction-pooler
Expand Down
3 changes: 2 additions & 1 deletion docs/src/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,12 @@ Pkg.add("Postgres")
Postgres.jl accepts DSN strings or PostgreSQL URIs and supports:

- libpq-style keyword strings such as `host=127.0.0.1 port=5432 user=postgres dbname=postgres`.
- Environment defaults from `PGHOST`, `PGPORT`, `PGUSER`, `PGPASSWORD`, `PGDATABASE`, `PGAPPNAME`, `PGCONNECT_TIMEOUT`, and TLS-related `PGSSL*` variables.
- Environment defaults from `PGHOST`, `PGPORT`, `PGUSER`, `PGPASSWORD`, `PGDATABASE`, `PGAPPNAME`, `PGCONNECT_TIMEOUT`, `PGOPTIONS`, and TLS-related `PGSSL*` variables.
- `sslmode` values: `disable`, `prefer` (the default), `require`, `verify-full`. Only `verify-full` verifies the server's certificate; `require` encrypts without authenticating the server, and the default `prefer` falls back to an unencrypted connection if the server declines TLS. Use `verify-full` with `sslrootcert` when the connection needs to be authenticated.
- TLS files: `sslrootcert`, `sslcert`, `sslkey`, and `sslcapath` (`sslcapath` is a fallback CA bundle or directory, used only when `sslrootcert` is unset and ignored otherwise). `sslservername` overrides the TLS server name when connecting to a pre-resolved address; under `verify-full` it is also the name the certificate is verified against, so it must name the server you intend to authenticate.
- `connect_timeout` (seconds), `statement_timeout` (milliseconds).
- `application_name` and `statement_cache_maxsize`.
- `options`: server command-line options applied when the session starts, as in libpq (`PGOPTIONS`). For example `options='-c search_path=myschema'` sets the default schema. The value is sent in the startup packet, so it also applies after an automatic reconnect.

Options that request unsupported security or server-selection behavior are
rejected. They are not silently ignored.
Expand Down
11 changes: 11 additions & 0 deletions docs/src/manual.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,17 @@ conn = DBInterface.connect(
)
```

Session defaults such as the schema search path are set with libpq's `options`
keyword (or the `PGOPTIONS` environment variable). The value is sent to the
server in the startup packet, so it also applies after an automatic reconnect.

```julia
conn = DBInterface.connect(
Postgres.Connection,
"host=127.0.0.1 user=postgres password=postgres dbname=postgres options='-c search_path=myschema'",
)
```

## Querying

`DBInterface.execute` returns a Tables.jl-compatible result. For small result sets, `Tables.rowtable` is a convenient way to materialize rows.
Expand Down
5 changes: 5 additions & 0 deletions docs/src/support.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ transaction mode. Configure PostgreSQL or the pooler defaults with
formats for correct decoding. Use direct connections or session pooling when
the application needs session state.

PgBouncer 1.20 and later accept the `options` startup keyword but reject
parameters inside it that they do not track. `search_path` is tracked by
default only when the server reports it, which PostgreSQL 18 does; for older
servers add it to PgBouncer's `track_extra_parameters`.

`set_statement_timeout!` is rejected while a transaction is open. This keeps
the durable reconnect setting consistent with PostgreSQL's transactional `SET`
semantics.
Expand Down
25 changes: 16 additions & 9 deletions src/Postgres.jl
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ Supported keyword arguments. All are also available as DSN/URI options except
`style`, which is Julia-only:

- `dbname`, `port`, `application_name`
- `options`: server command-line options applied when the session starts, as
libpq's `options` (for example `"-c search_path=myschema"` to set the default
schema). Sent in the startup packet, so it also applies after an automatic
reconnect. Defaults to `PGOPTIONS` when built from a DSN.
- `connect_timeout` (seconds), `statement_timeout` (milliseconds)
- `sslmode` (`"disable"`, `"prefer"` (default), `"require"`, `"verify-full"`),
`sslrootcert`, `sslcert`, `sslkey`, `sslcapath`, and `sslservername`.
Expand Down Expand Up @@ -98,6 +102,7 @@ mutable struct Connection{T, S <: API.AbstractPostgresStyle} <: DBInterface.Conn
const dbname::String
const port::Int
const application_name::Union{String, Nothing}
const options::Union{String, Nothing}
const connect_timeout::Union{Int, Nothing}
const sslmode::Union{String, Nothing}
const sslrootcert::Union{String, Nothing}
Expand Down Expand Up @@ -134,14 +139,15 @@ mutable struct Connection{T, S <: API.AbstractPostgresStyle} <: DBInterface.Conn
# commit/rollback must not COMMIT/ROLLBACK the caller's work
owns_base_transaction::Bool

function Connection(; host::AbstractString="", user::AbstractString="", password::Union{AbstractString, Nothing}=nothing, dbname::AbstractString="", port::Integer=5432, debug::Bool=false, reconnect::Bool=false, application_name::Union{AbstractString, Nothing}=nothing, connect_timeout::Union{Integer, Nothing}=nothing, sslmode::Union{AbstractString, Nothing}=nothing, sslrootcert::Union{AbstractString, Nothing}=nothing, sslcert::Union{AbstractString, Nothing}=nothing, sslkey::Union{AbstractString, Nothing}=nothing, sslcapath::Union{AbstractString, Nothing}=nothing, sslservername::Union{AbstractString, Nothing}=nothing, statement_timeout::Union{Integer, Nothing}=nothing, statement_cache_maxsize::Integer=100, numeric_overflow::Symbol=:warn, style::API.AbstractPostgresStyle=PostgresStyle())
function Connection(; host::AbstractString="", user::AbstractString="", password::Union{AbstractString, Nothing}=nothing, dbname::AbstractString="", port::Integer=5432, debug::Bool=false, reconnect::Bool=false, application_name::Union{AbstractString, Nothing}=nothing, options::Union{AbstractString, Nothing}=nothing, connect_timeout::Union{Integer, Nothing}=nothing, sslmode::Union{AbstractString, Nothing}=nothing, sslrootcert::Union{AbstractString, Nothing}=nothing, sslcert::Union{AbstractString, Nothing}=nothing, sslkey::Union{AbstractString, Nothing}=nothing, sslcapath::Union{AbstractString, Nothing}=nothing, sslservername::Union{AbstractString, Nothing}=nothing, statement_timeout::Union{Integer, Nothing}=nothing, statement_cache_maxsize::Integer=100, numeric_overflow::Symbol=:warn, style::API.AbstractPostgresStyle=PostgresStyle())
numeric_overflow in (:warn, :error) || throw(ArgumentError("numeric_overflow must be :warn or :error"))
host = String(host)
user = String(user)
dbname = String(dbname)
port = Int(port)
password = password === nothing ? nothing : String(password)
app_name = application_name === nothing ? nothing : String(application_name)
options_val = options === nothing ? nothing : String(options)
timeout = connect_timeout === nothing ? nothing : Int(connect_timeout)
sslmode_val = sslmode === nothing ? nothing : String(sslmode)
sslrootcert_val = sslrootcert === nothing ? nothing : String(sslrootcert)
Expand All @@ -155,14 +161,15 @@ mutable struct Connection{T, S <: API.AbstractPostgresStyle} <: DBInterface.Conn
occursin('\0', dbname) && _reject_nul("dbname")
password !== nothing && occursin('\0', password) && _reject_nul("password")
app_name !== nothing && occursin('\0', app_name) && _reject_nul("application_name")
options_val !== nothing && occursin('\0', options_val) && _reject_nul("options")
sslservername_val !== nothing && occursin('\0', sslservername_val) && _reject_nul("sslservername")
xor(sslcert_val === nothing, sslkey_val === nothing) &&
throw(PostgresInterfaceError("sslcert and sslkey must be provided together"))
maxsize = max(0, Int(statement_cache_maxsize))
socket, pid, skey, server_params = API.connect(host, port, dbname, user, password, debug, app_name, timeout, sslmode_val, sslrootcert_val, sslcert_val, sslkey_val, sslcapath_val, sslservername_val, statement_timeout_val)
socket, pid, skey, server_params = API.connect(host, port, dbname, user, password, debug, app_name, options_val, timeout, sslmode_val, sslrootcert_val, sslcert_val, sslkey_val, sslcapath_val, sslservername_val, statement_timeout_val)
registry = Dict(API.DEFAULT_TYPE_REGISTRY)
registry[1700] = API.TypeInfo(API.NumericValue, (val, registry) -> API.parse_numeric(val, numeric_overflow))
return new{Statement{typeof(style)}, typeof(style)}(ReentrantLock(), socket, host, user, password, dbname, port, app_name, timeout, sslmode_val, sslrootcert_val, sslcert_val, sslkey_val, sslcapath_val, sslservername_val, statement_timeout_val, pid, skey, Dict{String, Statement{typeof(style)}}(), maxsize, 0, server_params, registry, false, reconnect, debug, style, false, 0, String[], 1, false, true)
return new{Statement{typeof(style)}, typeof(style)}(ReentrantLock(), socket, host, user, password, dbname, port, app_name, options_val, timeout, sslmode_val, sslrootcert_val, sslcert_val, sslkey_val, sslcapath_val, sslservername_val, statement_timeout_val, pid, skey, Dict{String, Statement{typeof(style)}}(), maxsize, 0, server_params, registry, false, reconnect, debug, style, false, 0, String[], 1, false, true)
end
end

Expand Down Expand Up @@ -772,7 +779,7 @@ function checkconn(conn::Connection)
# connection is closed, but not explicitly, reconnect
conn.in_transaction && throw(PostgresInterfaceError("postgres connection has been closed or disconnected; reconnect disabled during transaction"))
conn.reconnect || throw(PostgresInterfaceError("postgres connection has been closed or disconnected; reconnect disabled"))
conn.socket, conn.pid, conn.skey, server_params = API.connect(conn.host, conn.port, conn.dbname, conn.user, conn.password, conn.debug, conn.application_name, conn.connect_timeout, conn.sslmode, conn.sslrootcert, conn.sslcert, conn.sslkey, conn.sslcapath, conn.sslservername, conn.statement_timeout)
conn.socket, conn.pid, conn.skey, server_params = API.connect(conn.host, conn.port, conn.dbname, conn.user, conn.password, conn.debug, conn.application_name, conn.options, conn.connect_timeout, conn.sslmode, conn.sslrootcert, conn.sslcert, conn.sslkey, conn.sslcapath, conn.sslservername, conn.statement_timeout)
empty!(conn.statements)
conn.in_transaction = false
conn.transaction_depth = 0
Expand All @@ -788,8 +795,8 @@ function checkconn(conn::Connection)
return
end

function DBInterface.connect(::Type{Connection}, host::AbstractString, user::AbstractString, passwd::Union{AbstractString, Nothing}; dbname::AbstractString="", port::Integer=5432, debug::Bool=false, reconnect::Bool=false, application_name::Union{AbstractString, Nothing}=nothing, connect_timeout::Union{Integer, Nothing}=nothing, sslmode::Union{AbstractString, Nothing}=nothing, sslrootcert::Union{AbstractString, Nothing}=nothing, sslcert::Union{AbstractString, Nothing}=nothing, sslkey::Union{AbstractString, Nothing}=nothing, sslcapath::Union{AbstractString, Nothing}=nothing, sslservername::Union{AbstractString, Nothing}=nothing, statement_timeout::Union{Integer, Nothing}=nothing, statement_cache_maxsize::Integer=100, numeric_overflow::Symbol=:warn, style::API.AbstractPostgresStyle=PostgresStyle())
Connection(host=host, user=user, password=passwd, dbname=dbname, port=port, debug=debug, reconnect=reconnect, application_name=application_name, connect_timeout=connect_timeout, sslmode=sslmode, sslrootcert=sslrootcert, sslcert=sslcert, sslkey=sslkey, sslcapath=sslcapath, sslservername=sslservername, statement_timeout=statement_timeout, statement_cache_maxsize=statement_cache_maxsize, numeric_overflow=numeric_overflow, style=style)
function DBInterface.connect(::Type{Connection}, host::AbstractString, user::AbstractString, passwd::Union{AbstractString, Nothing}; dbname::AbstractString="", port::Integer=5432, debug::Bool=false, reconnect::Bool=false, application_name::Union{AbstractString, Nothing}=nothing, options::Union{AbstractString, Nothing}=nothing, connect_timeout::Union{Integer, Nothing}=nothing, sslmode::Union{AbstractString, Nothing}=nothing, sslrootcert::Union{AbstractString, Nothing}=nothing, sslcert::Union{AbstractString, Nothing}=nothing, sslkey::Union{AbstractString, Nothing}=nothing, sslcapath::Union{AbstractString, Nothing}=nothing, sslservername::Union{AbstractString, Nothing}=nothing, statement_timeout::Union{Integer, Nothing}=nothing, statement_cache_maxsize::Integer=100, numeric_overflow::Symbol=:warn, style::API.AbstractPostgresStyle=PostgresStyle())
Connection(host=host, user=user, password=passwd, dbname=dbname, port=port, debug=debug, reconnect=reconnect, application_name=application_name, options=options, connect_timeout=connect_timeout, sslmode=sslmode, sslrootcert=sslrootcert, sslcert=sslcert, sslkey=sslkey, sslcapath=sslcapath, sslservername=sslservername, statement_timeout=statement_timeout, statement_cache_maxsize=statement_cache_maxsize, numeric_overflow=numeric_overflow, style=style)
end

function DBInterface.connect(::Type{Connection}, dsn::String; debug::Union{Bool, Nothing}=nothing, reconnect::Union{Bool, Nothing}=nothing, statement_cache_maxsize::Union{Integer, Nothing}=nothing, numeric_overflow::Union{Symbol, Nothing}=nothing, style::API.AbstractPostgresStyle=PostgresStyle())
Expand All @@ -798,7 +805,7 @@ end

function DBInterface.connect(::Type{Connection}, params::ConnectionParams; debug::Union{Bool, Nothing}=nothing, reconnect::Union{Bool, Nothing}=nothing, statement_cache_maxsize::Union{Integer, Nothing}=nothing, numeric_overflow::Union{Symbol, Nothing}=nothing, style::API.AbstractPostgresStyle=PostgresStyle())
actual_maxsize = isnothing(statement_cache_maxsize) ? params.statement_cache_maxsize : statement_cache_maxsize
Connection(host=params.host, user=params.user, password=params.password, dbname=params.dbname, port=params.port, debug=something(debug, params.debug), reconnect=something(reconnect, params.reconnect), application_name=params.application_name, connect_timeout=params.connect_timeout, sslmode=params.sslmode, sslrootcert=params.sslrootcert, sslcert=params.sslcert, sslkey=params.sslkey, sslcapath=params.sslcapath, sslservername=params.sslservername, statement_timeout=params.statement_timeout, statement_cache_maxsize=actual_maxsize, numeric_overflow=something(numeric_overflow, params.numeric_overflow), style=style)
Connection(host=params.host, user=params.user, password=params.password, dbname=params.dbname, port=params.port, debug=something(debug, params.debug), reconnect=something(reconnect, params.reconnect), application_name=params.application_name, options=params.options, connect_timeout=params.connect_timeout, sslmode=params.sslmode, sslrootcert=params.sslrootcert, sslcert=params.sslcert, sslkey=params.sslkey, sslcapath=params.sslcapath, sslservername=params.sslservername, statement_timeout=params.statement_timeout, statement_cache_maxsize=actual_maxsize, numeric_overflow=something(numeric_overflow, params.numeric_overflow), style=style)
end

function DBInterface.connect(f::Function, ::Type{Connection}, args...; kwargs...)
Expand Down Expand Up @@ -852,8 +859,8 @@ end

Base.isopen(pool::ConnectionPool) = !pool.closed[]

function ConnectionPool(::Type{Connection}, host::AbstractString, user::AbstractString, passwd::Union{AbstractString, Nothing}; dbname::AbstractString="", port::Integer=5432, debug::Bool=false, reconnect::Bool=false, application_name::Union{AbstractString, Nothing}=nothing, connect_timeout::Union{Integer, Nothing}=nothing, sslmode::Union{AbstractString, Nothing}=nothing, sslrootcert::Union{AbstractString, Nothing}=nothing, sslcert::Union{AbstractString, Nothing}=nothing, sslkey::Union{AbstractString, Nothing}=nothing, sslcapath::Union{AbstractString, Nothing}=nothing, sslservername::Union{AbstractString, Nothing}=nothing, statement_timeout::Union{Integer, Nothing}=nothing, statement_cache_maxsize::Integer=100, limit::Integer=10, numeric_overflow::Symbol=:warn, style::API.AbstractPostgresStyle=PostgresStyle())
connector = () -> DBInterface.connect(Connection, host, user, passwd; dbname=dbname, port=port, debug=debug, reconnect=reconnect, application_name=application_name, connect_timeout=connect_timeout, sslmode=sslmode, sslrootcert=sslrootcert, sslcert=sslcert, sslkey=sslkey, sslcapath=sslcapath, sslservername=sslservername, statement_timeout=statement_timeout, statement_cache_maxsize=statement_cache_maxsize, numeric_overflow=numeric_overflow, style=style)
function ConnectionPool(::Type{Connection}, host::AbstractString, user::AbstractString, passwd::Union{AbstractString, Nothing}; dbname::AbstractString="", port::Integer=5432, debug::Bool=false, reconnect::Bool=false, application_name::Union{AbstractString, Nothing}=nothing, options::Union{AbstractString, Nothing}=nothing, connect_timeout::Union{Integer, Nothing}=nothing, sslmode::Union{AbstractString, Nothing}=nothing, sslrootcert::Union{AbstractString, Nothing}=nothing, sslcert::Union{AbstractString, Nothing}=nothing, sslkey::Union{AbstractString, Nothing}=nothing, sslcapath::Union{AbstractString, Nothing}=nothing, sslservername::Union{AbstractString, Nothing}=nothing, statement_timeout::Union{Integer, Nothing}=nothing, statement_cache_maxsize::Integer=100, limit::Integer=10, numeric_overflow::Symbol=:warn, style::API.AbstractPostgresStyle=PostgresStyle())
connector = () -> DBInterface.connect(Connection, host, user, passwd; dbname=dbname, port=port, debug=debug, reconnect=reconnect, application_name=application_name, options=options, connect_timeout=connect_timeout, sslmode=sslmode, sslrootcert=sslrootcert, sslcert=sslcert, sslkey=sslkey, sslcapath=sslcapath, sslservername=sslservername, statement_timeout=statement_timeout, statement_cache_maxsize=statement_cache_maxsize, numeric_overflow=numeric_overflow, style=style)
return ConnectionPool(connector; limit=limit)
end

Expand Down
Loading