Skip to content

Claude/update fork upstream xq c1 m - #4

Merged
NikolayS merged 125 commits into
mainfrom
claude/update-fork-upstream-XqC1M
Jan 7, 2026
Merged

Claude/update fork upstream xq c1 m#4
NikolayS merged 125 commits into
mainfrom
claude/update-fork-upstream-XqC1M

Conversation

@NikolayS

@NikolayS NikolayS commented Jan 7, 2026

Copy link
Copy Markdown
Owner

No description provided.

jaggederest and others added 30 commits September 30, 2025 07:45
* Rewrite bug fixes - old cache key, more efficient update

* Update pgdog/src/frontend/prepared_statements/mod.rs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update pgdog/src/frontend/client_request.rs

Co-authored-by: Lev Kokotov <levkk@users.noreply.github.com>

* Skip removing and readding cache item, just modify it in place

* skip cloning name if passed by reference

* skip cloning name if passed by reference

* format

* cache key fixes

* Update cache removal test to handle close_unused(0) clearing

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Lev Kokotov <levkk@users.noreply.github.com>
)

* Add query parameter support for Database URL configuration

Enables configuring Database fields via URL query parameters in
databases_from_urls(), allowing flexible configuration through
environment variables without separate config files.

Supported parameters: database_name, role, shard, user, password,
pool_size, min_pool_size, pooler_mode, statement_timeout, idle_timeout,
server_lifetime, read_only

Example:
PGDOG_DATABASE_URL_1=postgres://user:pass@host:port/db?database_name=realdb&role=replica&shard=3

* Load mirroring configuration from PGDOG_MIRRORING_N ENV vars
* Fix pgdogdev#48

* remove sha

* Sleep before checking process

* Use tls in CI
* swap to u16 on inbound and outbound paths

* Add a test to check bounds of backend pg parameter limits

* also update num_codes

* parameterize u16 test to cover pgdog and raw postgresql

* Unit test for parameter descriptions FromBytes/ToBytes

* format
* Initial pass on TLS hot swap

* wip tls testing

* Ensure TLS acceptor cached

* test demonstrating no additional tls acceptor rebuilds

* Set up explicit TCP connector caching
* Correctly handle error state

* Timeout server error

* Add rollback timeout

* Test and warning
* Initial AVG + expression parser
AVG function implementation:
Detects paired count+avg functions on the same expression
Expression parser handles logic of "same expression" by assigning id through interning
Adds "ghost" column using rewriting if no count is paired with avg
removes "ghost" column from returned value of aggregates

* rollback transactions correctly

* stddev / variance

* format

* cover changed files with tests

* format
* tweak codecov.yml

* adding additional tests, for the admin module

* format

* prom stats tests

* basic tests for some replication fns

* scram and multitenant smoke tests

* Update admin tests to add new pool value (healthy)
* Add plugin unit/integration tests, optional for PR merge

* install mold before plugin tests

* try bundling

* try to pass plugin unit tests

* cant sudo bundle

* try bundle

* remove other bundler

* use vendored bundler
* Support savepoints

* Fix spelling
)

* Fix: cancel backend sessions when shutdown timeout expires

* fix: wait on passthrough auth pgdog shutdown

* feature: add shutdown termination timeout

* test: add cancel-on-shutdown integration

* fix: only cancel connections when shutdown termination timeout is set
* initial draft internal explain annotations

* clean up formatting logic in query parser

* explain plan annotation integration test

* explain annotation testing

* enable explain annotations only when expanded_explain flag set

* Enable expanded_explain in integration tests

* Tweak explain recorder

* better integration debugging

* less strict assertion :(
* Dont rewrite anon prepared statements by default

* Fix test
* Use Bytes for Bind fields

* Fix warning

* comment
levkk and others added 29 commits December 10, 2025 11:00
Save `SET LOCAL` into Parameters so we can fetch them if we're looking
for `search_path` for schema-based routing.

Also, fix parameter encoding/decoding.
```
client "pgdog" startup parameters: application_name="psql", client_encoding="UTF8", database="pgdog", pgdog.role="primary", user="pgdog" [127.0.0.1:33622]
```
Log a reason for which a server connection was created. This helps debug
configuration.
Log info about pool state when pool decides to create new connection.
Additionally, avoid triggering pool creation check when pool is full:
saves us a mutex lock and makes the pool, at capacity, a bit faster!

A couple clippy fixes as well.
- fix: collision in server <-> client mapping in pool leaked connections
by under-reporting the number of checked out connections. This is caused
by "async" `Server::drop` which _eventually_ returns a connection back
into the pool, but fast clients are already requesting a new one,
overriding their server <-> client mapping.
- fix: under counting banned hosts in unban all logic.
- refactor: create a `ClientComms` for each client, instead of mutating
a struct in-place
- fix: use atomic integer to generate client IDs, reducing collision
probability
pgdogdev#677 

Prepared statements engine was acting as if `prepared_statements =
"full"` was default, and rewriting `PREPARE` and `EXECUTE` commands
(incorrectly, inside transactions) even though `prepared_statements =
"extended"` is actually the default.
Parameters that contain double quotes were encoded incorrectly.
## Purpose

This addresses some side effects of pgdogdev#565 in that when a connection falls
out of sync (due to currently unknown series of events), the connection
is detected as out of sync, but is checked back into the pool without
any corrective action.

## Approach

This updates server.rs to set its stats state to State::Error when we
encounter a ProtocolOutOfSync error while reading from the server and
decided whether to forward the message to the client. The result is that
the connection will be disconnected when attempted to be checked back
into the pool and the tainted connection will not be re-used.
I ran into a bug, where the `application_name` in the postgres
connection parameters was not being correctly set on the server after
the first connection.

I tested with a local postgres container. Here is my `pgdog.toml`:
```
[general]
host = "127.0.0.1"
port = 6432
pooler_mode = "session"

# Primary Database
[[databases]]
host = "127.0.0.1"
name = "primary"
port = 5433
database_name = "intempus"
user = "intempus"
password = "intempus"
pool_size = 1
min_pool_size = 1
```
I could reproduce the problem by starting pgdog and then connecting with
psql, running a query to print the `application_name` in the postgres
DB:
```
$ psql "host=127.0.0.1 port=6432 dbname=primary application_name=test1" -c "SELECT application_name,query from pg_stat_activity;"
 application_name |                        query                         
------------------+------------------------------------------------------
 test1               | SELECT application_name,query from pg_stat_activity;

$ psql "host=127.0.0.1 port=6432 dbname=primary application_name=test1" -c "SELECT application_name,query from pg_stat_activity;"
 application_name |                        query                         
------------------+------------------------------------------------------
 PgDog            | SELECT application_name,query from pg_stat_activity;

$ psql "host=127.0.0.1 port=6432 dbname=primary application_name=test1" -c "SELECT application_name,query from pg_stat_activity;"
 application_name |                        query                         
------------------+------------------------------------------------------
 PgDog            | SELECT application_name,query from pg_stat_activity;
```

The `application_name` will always be `PgDog` after the 1st connection.
If I change the `application_name` right after to something other than
`test1`, it will reset and work for the 1st connection only again:

```
$ psql "host=127.0.0.1 port=6432 dbname=primary application_name=test2" -c "SELECT application_name,query from pg_stat_activity;"
 application_name |                        query                         
------------------+------------------------------------------------------
 test2               | SELECT application_name,query from pg_stat_activity;
 
$ psql "host=127.0.0.1 port=6432 dbname=primary application_name=test2" -c "SELECT application_name,query from pg_stat_activity;"
 application_name |                        query                         
------------------+------------------------------------------------------
 PgDog            | SELECT application_name,query from pg_stat_activity;

$ psql "host=127.0.0.1 port=6432 dbname=primary application_name=test2" -c "SELECT application_name,query from pg_stat_activity;"
 application_name |                        query                         
------------------+------------------------------------------------------
 PgDog            | SELECT application_name,query from pg_stat_activity;
```


I tracked down the [problem to this comparison statement in
servers.rs](https://github.com/pgdogdev/pgdog/blob/main/pgdog/src/backend/server.rs#L479).
The `identical` function compares if the hashes in `params` and
`client_params` on the server object are the same.
On my 1st connection attempt the comparison correctly identifies that
the parameters are not the same and goes into the if block. All
subsequent connections never go into the if block, until the
`application_name` is changed.

Here are the values for `params` and `self.client_params`:
```
# 1st call
client_params: Parameters { params: {}, transaction_params: {}, transaction_local_params: {}, hash: 0 }
params: Parameters { params: {"application_name": String("test2"), "client_encoding": String("UTF8"), "database": String("primary"), "user": String("intempus")}, transaction_params: {}, transaction_local_params: {}, hash: 6714540884140617138 }
# 2nd call
client_params: Parameters { params: {}, transaction_params: {}, transaction_local_params: {}, hash: 6714540884140617138 }
params: Parameters { params: {"application_name": String("test2"), "client_encoding": String("UTF8"), "database": String("primary"), "user": String("intempus")}, transaction_params: {}, transaction_local_params: {}, hash: 6714540884140617138 }
```

When the connection is closed and a `DISCARD ALL` query is sent,
`self.client_params.clear()` [is
called](https://github.com/pgdogdev/pgdog/blob/main/pgdog/src/backend/server.rs#L445),
but that doesn't recompute the hash, so parameters will be cleared but
the hash will stay the same value and result in wrong `identical` checks
from then on.

Ensuring the hash is recomputed when the parameters are cleared fixes
the issue. Let me know if it sounds good or if it should be done using a
different approach :)
- refactor: move rewrite engine into its own module under the parser and
make it run independently
- feat: support binary encoding for multi-tuple insert statements
splitting
- feat: multi-tuple inserts use whatever protocol the original insert
was sent with instead of rewriting them into simple queries
- feat: generate unique IDs with `pgdog.unique_id()` function call
anywhere in a query
- refactor: query AST is attached to the request, ensuring it's
available to all parts of the parser/rewrite engine
- fix: ambiguous shard key detection refactored into a priority heap,
allowing the parser to evaluate multiple conditions and select the
highest priority one; added granular debugging to check where a shard
route came from
- fix: sending `BEGIN` twice when starting transactions in sharded and
load balanced clusters
- feat: support `SET [LOCAL] pgdog.shard` and `SET [LOCAL]
pgdog.sharding_key` in and out of transactions, following Postgres
semantics
- feat: add `rewrite` column to `SHOW PREARED` to show actual statements
executed on the server
- feat: keep rewritten statements in the prepared cache to avoid churn
- refactor: much more granular errors for different not-connected states
- refactor: change some methods that return boolean to use `is_` prefix
- chore: add quite a bit of test coverage, especially around
schema-based sharding
- refactor: re-build parameter sharding hints for each request, avoiding
stale state in the query engine
- chore: attribute message input/output in trace logs to each server
- chore: created `QueryParserTest` and `TestClient` structs to simplify
testing those complex components
- fix: parsing `BIGINT` sharding keys in simple queries that exceeded
that `INTEGER` range (2.2B)
- feat: handle `SET` commands completely inside the proxy, not sending
them to a server even if using extended protocol
- fix: query comments-based sharding hints being ignored when schema
sharding was used
- fix: dedup shard numbers in `Shard::Multi` binding
- chore: add `RESET` test for pgdogdev#682
- chore: tag release
…sharding (pgdogdev#684)

Example:

```
BEGIN;
SELECT 1;
SET LOCAL work_mem TO '128MB';
COMMIT;
```
Fixes

```
WARNING:  SET TRANSACTION can only be used in transaction blocks
```

and possibly the not in transaction warnings reported in pgdogdev#685
…ogdev#687)

Handle saving/restoring parameters for connections that execute `SET`
after a regular query inside a transaction, e.g.:

```sql
BEGIN;
SET LOCAL statement_timeout TO 1234;
SELECT 1;
SET work_mem TO '128MB';
COMMIT;
```

This would preserve the value of `work_mem` for that client for all
subsequent transactions, as per Postgres spec.
Obviously this doesn't require people to use mise, but this provides a
simple installation for people that do, and we can hardcode the versions
of dev tools that pgdog relies on in case that's needed later.

Obviously a totally optional one, but I set it up locally so figured I'd
upstream.
- refactor: move sharding key update into its own module
- BREAKING: sharding key updates require a transaction started by the
user instead of starting one automatically
- feat: support `RETURNING` clause in `UPDATE` that changes the sharding
key
- fix: `ErrorResponse` message incorrectly parsed `severity` field
- feat: double-check we actually need to do a multi-step key update when
the key isn't actually changed by reading the row from the database

This is common in Rails:

```sql
UPDATE orders
SET user_id = $1, amount = $2, created_at = $3,
/* [..every other model field..] */
WHERE id = $1
```

The sharding key doesn't actually change (`user_id`), the ORM just puts
it into the query anyway.
- fix: handle expressions in `UPDATE` statements that change the
sharding key
- chore: clippy

```sql
UPDATE users SET id = $1, created_at = now() WHERE id = $2
```
Make sure we escape any special characters in the parameter values. Will
make sure SET queries always work as expected.
We forked [pg_query](https://github.com/pgdogdev/pg_query.rs) and
replaced protobuf (de)serialization with native C/Rust bindings.

This results in the following performance improvements:

1. Query parsing is 5x faster.
2. Query construction using the Posgres AST builder is 10x faster. We
can generate queries on the fly with virtually no performance overhead.
3. `pg_bench` is reporting a 25% improvement in benchmarks using the
simple protocol, which we don't cache. This is great for clients that
don't support or use prepared statements (e.g. Rails!).

This is configurable with the new `query_parser_engine` setting:

```toml
[general]
query_parser_engine = "pg_query_raw" # pg_query_protobuf to use the old protobuf-based path
```

While we continue to test this, we'll be using the old protobuf-based
paths by default. This PR therefore won't affect any current deployments
and adoption is completely optional.

Things we need to optimize:

1. The raw C parser is very recursive. We're currently bumping up the
Tokio worker stack size to 32MiB to make sure there is enough to parse
complex statements, but that increases our memory requirements quite a
bit. This is _per thread_, so if you have 4 threads, PgDog will allocate
32 x 4 = 132 MiB of stack memory on boot. For most modern systems that's
not a problem, but still, the memory usage increases quite a bit.
2. The parser may not be complete. While we test it extensively, the
number of Postgres AST nodes is high.

### Misc

Upgraded rand to 0.9.2.
The shard calculating logic was using table-based sharding and erroring
out when no sharded tables were configured. Also, added a test to
comment parser just for good measure.
@NikolayS
NikolayS merged commit 382e1be into main Jan 7, 2026
0 of 6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.