Skip to content

refactor(qwp): consolidate the QWP connect-string configuration keys - #66

Merged
puzpuzpuz merged 17 commits into
mainfrom
mt_sf-max-segment-bytes
Jul 29, 2026
Merged

refactor(qwp): consolidate the QWP connect-string configuration keys#66
puzpuzpuz merged 17 commits into
mainfrom
mt_sf-max-segment-bytes

Conversation

@mtopolnik

@mtopolnik mtopolnik commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Consolidates the QWP (ws:: / wss::) connect-string configuration vocabulary. A single connect string has to drive both the ingress LineSender and the egress QwpQueryClient, so this PR settles which keys each side accepts, drops keys that no longer carry meaning, tightens per-transport validation, and renames one key for clarity. Almost every change is confined to the QWP transport; the sole cross-transport change is the user / pass credential aliasing, which resolves in the shared connect-string tokenizer and so also affects the legacy ILP http(s):: / tcp(s):: schemas (detailed under One connect string for ingress and egress below).

Removed QWP options

  • auth / WithQwpQueryAuth — the query client no longer accepts a pre-formed Authorization header value. Credentials are given as the structured username / password or token keys, from which the client synthesizes the Basic or Bearer header downstream. Mirrors the Java client change of the same name.
  • in_flight_window / WithInFlightWindow — a dead no-op. The value never reached the sender: the cursor architecture governs backpressure through the segment ring and the append deadline, and Flush never waits for the ACK. The key now falls through to the generic parser and is rejected as an unsupported option.
  • gorilla / WithGorilla — the encoder now always sets FLAG_GORILLA and unconditionally applies Gorilla delta-of-delta timestamp encoding on ingress, keeping the per-column fallback to uncompressed when the delta-of-deltas overflow int32. The egress decoder is unchanged — it still reads the server's FLAG_GORILLA bit and handles both encoded and raw timestamps.

One connect string for ingress and egress

  • The ingress parser now accepts the egress-only keys (path, client_id, server_info_timeout_ms, replay_exec) and the Java-parity ingress keys the doc Key index omits (transaction, connection_listener_inbox_capacity) as validated no-ops or equivalents.
  • The egress parser silently ignores the ingress-only keys.

Either side parses the shared string without rejecting the other side's keys.

user / pass are shared aliases, not QWP-scoped. They canonicalize to username / password in the shared connect-string tokenizer (canonicalConfigKey), ahead of any per-schema handling, so they resolve on every transport, not just QWP. On QWP and http(s):: they map to the Basic-auth user/password; on tcp(s)::, user maps to the key id exactly as username always has (pass stays rejected there, as password always was). This is a deliberate widening: those spellings were previously rejected as unsupported options on the legacy ILP transports. No existing valid config changes meaning — the mapping matches how username / password already behaved on each transport.

Non-QWP keys rejected on the QWP path

  • Legacy ILP HTTP keys — protocol_version (including =auto, which previously slipped through because it leaves the field unset), request_timeout, request_min_throughput, retry_timeout — are rejected on QWP while http:: / tcp:: still accept them. The protocol version is negotiated at the WebSocket upgrade.
  • ECDSA token_x / token_y are rejected on QWP, restoring ingress/egress symmetry (they remain ignored on the legacy ILP transports, which don't need the public key).
  • The UDP-only keys max_datagram_size / multicast_ttl are rejected on QWP; this client has no UDP transport.

This matches the egress QwpQueryClient and the Java, Rust, and .NET clients.

Rename: sf_max_bytessf_max_segment_bytes

The key caps a single store-and-forward segment, and the new name says so; it also pairs cleanly with sf_max_total_bytes. The public option follows: WithSfMaxBytes is now WithSfMaxSegmentBytes. Internal identifiers, error messages, tests, the README, and the SF example rename in step, so the repo greps zero for the old spelling in any case variant.

Cross-repo parity

Several changes track the Java client (Sender.java): the sf_max_bytes rename (matching questdb/java-questdb-client#70), the auth removal, and the shared ingress/egress key vocabulary. A connect string that works on the Java QWP client is meant to work here.

API impact

This is almost entirely confined to the QWP transport, which is still being stabilized ahead of its first stable release — the full transport (store-and-forward cursor engine, query client, typed errors, failover) landed in #62, after v4.2.0. The lone exception is the shared-tokenizer user / pass aliasing described above, which now also parses on http(s):: / tcp(s):: (spellings previously rejected as unsupported options); it only widens what parses and leaves every existing valid config unchanged. sf_max_bytes / WithSfMaxBytes and WithQwpQueryAuth are part of that unreleased surface. WithGorilla and WithInFlightWindow shipped with the QWP ingress in v4.2.0, so their removal is a breaking change (flagged under Breaking Changes below).

Verification

go build ./... and go vet ./... are green locally; the Docker integration suite is CI's to confirm.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Breaking Changes
    • Renamed sf_max_bytessf_max_segment_bytes (and WithSfMaxBytesWithSfMaxSegmentBytes).
    • Removed WithInFlightWindow and WithGorilla; QWP no longer accepts in_flight_window/gorilla connect-string knobs.
    • Removed WithQwpQueryAuth; query connect strings no longer accept the legacy auth key.
  • New Features
    • Added connect_timeout for QWP connect strings.
    • Added WithQwpQueryConnectTimeout for QWP query clients.
  • Bug Fixes
    • Stricter, transport-aware QWP connect-string validation and clearer option rejection.
  • Documentation
    • Updated QWP SF docs, parity notes, and examples to use sf_max_segment_bytes.

@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

QWP configuration now uses segment-specific store-and-forward sizing, removes in-flight-window and Gorilla configuration options, tightens key-scope parsing, adds connect-timeout handling, fixes encoder invariants, and removes standalone query authorization support. Tests and documentation reflect these contracts.

Changes

QWP configuration contracts

Layer / File(s) Summary
Configuration parsing and scope rules
conf_parse.go, conf_audit_test.go, conf_test.go
QWP parsing accepts aliases and connect timeouts, rejects unsupported keys, validates transport-specific options, and tests scope behavior.
Store-and-forward option and validation
sender.go, qwp_sender_cursor.go, qwp_sf_conf_test.go, README.md, examples/qwp/sf/main.go, qwp_sf_fallocate_unix_other.go, qwp_error_resilience_test.go, qwp_ingress_oracle_fuzz_test.go, CLAUDE.md
Segment-size options, sanitization constraints, runtime propagation, examples, resilience tests, and documentation use the new naming.
QWP connect-timeout transport flow
sender.go, qwp_query_conf.go, qwp_query_client.go, qwp_query_failover.go, qwp_transport.go
Connect-timeout configuration is validated and applied to endpoint TCP dialing.

QWP sender runtime

Layer / File(s) Summary
Sender flow-control removal
qwp_sender.go, sender.go, qwp_constants.go, qwp_sender_test.go, qwp_integration_test.go, CLAUDE.md
Sender construction and asynchronous tests no longer accept or retain an in-flight-window parameter or Gorilla toggle.

QWP wire and query contracts

Layer / File(s) Summary
Encoder wire-format invariants
qwp_encoder.go, qwp_encoder_test.go
Gorilla encoding is always enabled, and multi-table, nullable-column, and byte-level wire-format coverage is added.
Query authentication contract
qwp_query_conf.go, qwp_query_client.go, qwp_query_client_test.go
Standalone authorization-header support is removed; Basic and Bearer authentication remain mutually exclusive.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

Suggested reviewers: bluestreak01

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 70.73% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: consolidating and refactoring QWP connect-string configuration keys.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch mt_sf-max-segment-bytes

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

mtopolnik and others added 7 commits July 22, 2026 17:12
Drop the `auth` connect-string key and the WithQwpQueryAuth option from
the QWP query client. Both supplied a pre-formed HTTP Authorization
header value verbatim; credentials must now be given as the structured
`username`/`password` or `token` keys, from which the client
synthesizes the header downstream (Basic or Bearer).

The mutual-exclusivity check covers username/password versus token;
the `authorization` config field is gone. `auth=` is now rejected as an
unsupported option, and the ingress LineSender never accepted the key,
so it now fails on both directions.

This mirrors the Java client change of the same name.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Drop the `in_flight_window` config-string key and the
WithInFlightWindow option from the QWP sender. The value never reached
the sender -- the cursor architecture governs backpressure via the
segment ring and append deadline -- so it was a dead no-op.

The key now falls through to the generic parser path and is rejected as
`unsupported option "in_flight_window"`. Remove the now-dead config
field, default constant, validation, and the constructor plumbing that
carried the value, and update the docs that described it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Drop the WithGorilla option and the gorilla=on|off connection-string
key. The QWP encoder now always sets FLAG_GORILLA and unconditionally
applies Gorilla delta-of-delta timestamp encoding on ingress, keeping
the per-column fallback to uncompressed when DoDs overflow int32.

The egress query decoder is unchanged; it still reads the server's
FLAG_GORILLA bit and handles both encoded and raw timestamps.

Remove the gorilla config-parsing, encoder-disabled, and audit tests,
and the CLAUDE.md note that documented the parameter.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
path, client_id, server_info_timeout_ms, and replay_exec are egress-only
QWP keys that the query client (parseQwpQueryConf) actively parses. A
single ws:: / wss:: connect string must drive both the Sender and the
QwpQueryClient, so the ingress LineSender has to silently accept the
egress-only keys rather than reject them.

These four were absent from egressOnlyKeys, so the ingress default branch
rejected them with "unsupported option". Add them to the map and extend
TestConfIngestSilentlyAcceptsEgressKeys.

request_timeout, retry_timeout, request_min_throughput, and
protocol_version are deliberately left out: sanitizeQwpConf rejects them
on the QWP ingress as HTTP-only keys (TestConfQwpRejectsRetryTimeout), so
the query client rejecting them too is consistent, not a portability gap.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
On QWP (ws:: / wss::) the four legacy ILP HTTP keys -- request_timeout,
retry_timeout, request_min_throughput, and protocol_version -- are not
part of the connect-string vocabulary (connect-string.md Key index).

sanitizeQwpConf already rejected the first three and protocol_version
when set to a concrete version. protocol_version=auto slipped through,
because "auto" leaves the field unset and the sanitizer only inspects
the field. The parser now rejects protocol_version outright on QWP, so
even "auto" is caught, matching the egress QwpQueryClient and the Java,
Rust, and .NET clients.

New tests lock all four keys as rejected on both the ingress and egress
QWP paths, and confirm they remain valid on the http:: schema.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A single ws:: / wss:: connect string is shared by the QWP ingress
sender and the egress query client. Each parser now accepts the full
QWP key vocabulary and silently ignores the keys meant for the other
side, so one connect string configures both.

The ingress parser accepts the egress-only keys; the egress parser
ignores the ingress-only keys, including the user / pass auth aliases,
transaction, and connection_listener_inbox_capacity.

Both parsers reject keys outside the QWP vocabulary:

- Legacy ILP keys (protocol_version including =auto, request_timeout,
  request_min_throughput, retry_timeout) are rejected on QWP while
  http/tcp still accept them.
- ECDSA token_x / token_y are rejected on the QWP path, restoring
  ingress/egress symmetry.
- The UDP-only keys max_datagram_size and multicast_ttl are rejected
  on the QWP path; this client has no UDP transport.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The connect-string key caps a single SF segment, and the new name says
so; it also pairs cleanly with sf_max_total_bytes. The public option
func follows: WithSfMaxBytes is now WithSfMaxSegmentBytes. Internal
identifiers, error messages, tests, and docs rename in step so the old
spelling is gone entirely.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@mtopolnik
mtopolnik force-pushed the mt_sf-max-segment-bytes branch from 07c60b2 to 7b87d91 Compare July 22, 2026 15:14

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@CLAUDE.md`:
- Around line 120-125: Update the QWP connect-string documentation section in
CLAUDE.md to list transaction, connection_listener_inbox_capacity, and user/pass
as Java-parity keys. Describe each key’s QWP scope and its no-op or alias
behavior, and retain the requirement that these compatibility keys must not be
removed without a corresponding Java change.

In `@conf_parse.go`:
- Around line 606-622: In the max_datagram_size/multicast_ttl handling within
the configuration parser, validate v with strconv.Atoi before checking
senderConf.senderType against qwpSenderType, so malformed values always produce
the invalid-integer error before any scope rejection. Extend
TestConfQwpRejectsUdpKeys with malformed ws:: cases covering these UDP keys.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ea773527-f075-4156-8d9b-f89a7b982bc7

📥 Commits

Reviewing files that changed from the base of the PR and between 07c60b2 and 7b87d91.

📒 Files selected for processing (21)
  • CLAUDE.md
  • README.md
  • conf_audit_test.go
  • conf_parse.go
  • conf_test.go
  • examples/qwp/sf/main.go
  • qwp_constants.go
  • qwp_encoder.go
  • qwp_encoder_test.go
  • qwp_error_resilience_test.go
  • qwp_ingress_oracle_fuzz_test.go
  • qwp_integration_test.go
  • qwp_query_client.go
  • qwp_query_client_test.go
  • qwp_query_conf.go
  • qwp_sender.go
  • qwp_sender_cursor.go
  • qwp_sender_test.go
  • qwp_sf_conf_test.go
  • qwp_sf_fallocate_unix_other.go
  • sender.go
💤 Files with no reviewable changes (2)
  • qwp_constants.go
  • qwp_encoder_test.go
🚧 Files skipped from review as they are similar to previous changes (6)
  • examples/qwp/sf/main.go
  • qwp_sf_fallocate_unix_other.go
  • README.md
  • qwp_error_resilience_test.go
  • qwp_ingress_oracle_fuzz_test.go
  • qwp_sf_conf_test.go

Comment thread CLAUDE.md Outdated
Comment thread conf_parse.go
@mtopolnik mtopolnik changed the title refactor(qwp): rename sf_max_bytes to sf_max_segment_bytes refactor(qwp): consolidate the QWP connect-string configuration keys Jul 22, 2026
@mtopolnik

Copy link
Copy Markdown
Contributor Author
  • [P1] transaction=on is silently ignored — conf_parse.go:579
    Callers request transactional ingestion but receive ordinary writes. Reject on until implemented; off can remain a no-op.

  • [P2] user/pass are incorrectly ingress-only — conf_parse.go:99, qwp_query_conf.go:427
    Egress discards these credentials, while Java canonicalizes them to username/password. Alias/canonical combinations are also nondeterministic because Go iterates the configuration map randomly.

  • [P2] The shared vocabulary omits connect_timeoutconf_parse.go:49
    Both Go QWP parsers reject this common Java QWP key, contrary to the stated cross-client configuration goal.

  • [P2] Listener capacity lacks range validation — conf_parse.go:595
    connection_listener_inbox_capacity accepts zero and negative values, whereas the corresponding Java option requires >= 1.

  • [P3] The patch is not gofmt-clean — conf_parse.go:108, sender.go:385.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
conf_parse.go (1)

622-638: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

UDP-key validation order still rejects malformed values with the wrong error on QWP.

This is the exact ordering flagged in a prior review: max_datagram_size=abc on a ws:: string returns the QWP-scope error instead of the "not a valid int" error, because the scope check runs before strconv.Atoi. The code here is unchanged from the previously-flagged version — the fix (validate shape first, then reject scope) was not applied.

🐛 Proposed fix
 case "max_datagram_size", "multicast_ttl":
+    if _, err := strconv.Atoi(v); err != nil {
+        return nil, NewInvalidConfigStrError(
+            "invalid %s value, %q is not a valid int", k, v)
+    }
     if senderConf.senderType == qwpSenderType {
         return nil, NewInvalidConfigStrError(
             "%s is not supported for QWP; it applies to the UDP transport only", k)
     }
-    if _, err := strconv.Atoi(v); err != nil {
-        return nil, NewInvalidConfigStrError(
-            "invalid %s value, %q is not a valid int", k, v)
-    }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@conf_parse.go` around lines 622 - 638, In the max_datagram_size/multicast_ttl
handling, perform the strconv.Atoi shape validation before checking
senderConf.senderType against qwpSenderType, so malformed values return the “not
a valid int” error even for QWP; retain the existing QWP unsupported-key error
for valid values.
🧹 Nitpick comments (1)
conf_parse.go (1)

168-183: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Drop the "user" / "pass" arms from the canonical-key loop.

parseConfigStr canonicalizes userusername and passpassword before populating data.KeyValuePairs, so these aliases can never appear as k in confFromStr. The alias behavior is still handled by canonicalConfigKey; keeping these arms is only misleading dead code.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@conf_parse.go` around lines 168 - 183, Remove the "user" and "pass" cases
from the key switch in confFromStr, leaving only the canonical "username" and
"password" handlers. Preserve alias support through canonicalConfigKey, which
normalizes these keys before confFromStr processes them.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@conf_parse.go`:
- Around line 622-638: In the max_datagram_size/multicast_ttl handling, perform
the strconv.Atoi shape validation before checking senderConf.senderType against
qwpSenderType, so malformed values return the “not a valid int” error even for
QWP; retain the existing QWP unsupported-key error for valid values.

---

Nitpick comments:
In `@conf_parse.go`:
- Around line 168-183: Remove the "user" and "pass" cases from the key switch in
confFromStr, leaving only the canonical "username" and "password" handlers.
Preserve alias support through canonicalConfigKey, which normalizes these keys
before confFromStr processes them.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 0da49f13-a3ae-4209-89fc-d69c311b95d6

📥 Commits

Reviewing files that changed from the base of the PR and between 7b87d91 and 3f2626d.

📒 Files selected for processing (7)
  • CLAUDE.md
  • conf_audit_test.go
  • conf_parse.go
  • qwp_query_conf.go
  • qwp_query_failover.go
  • qwp_transport.go
  • sender.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • sender.go

@mtopolnik

Copy link
Copy Markdown
Contributor Author
  • [P2] Reject UDP-only options on every supported sender — /Users/mtopol/dev/questdb/go-questdb-client/conf_parse.go:631-638
    For http(s):: and tcp(s):: configurations, this branch merely parses these values and then ignores them, so inputs such as max_datagram_size=0 or multicast_ttl=999 are accepted despite having no effect. Since this client has no UDP sender and these keys previously produced an unsupported-option error, silently accepting them hides deployment misconfiguration; reject them on all currently supported transports.

  • [P2] Expose connect timeouts through the option APIs — /Users/mtopol/dev/questdb/go-questdb-client/qwp_query_conf.go:109-111
    When callers use NewLineSender(...WithQwp...) or NewQwpQueryClient, no exported option assigns this new field, so it remains zero and the TCP connect timeout cannot be enabled; only the config-string constructors can use the feature. Add sender and query-client functional options parallel to WithAuthTimeout and WithQwpQueryAuthTimeout so both supported construction paths can configure the timeout.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@qwp_query_client.go`:
- Around line 447-456: Update WithQwpQueryConnectTimeout to reject negative
durations before calling d.Milliseconds(), ensuring sub-millisecond negative
values cannot become zero and bypass validation. Preserve the existing
conversion and zero-duration behavior for non-negative durations.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 1c33890e-5709-4c78-b2cd-b9419ef5c805

📥 Commits

Reviewing files that changed from the base of the PR and between 3f2626d and edb8c5b.

📒 Files selected for processing (6)
  • conf_test.go
  • qwp_error_resilience_test.go
  • qwp_ingress_oracle_fuzz_test.go
  • qwp_query_client.go
  • qwp_query_client_test.go
  • sender.go
🚧 Files skipped from review as they are similar to previous changes (5)
  • qwp_error_resilience_test.go
  • qwp_query_client_test.go
  • qwp_ingress_oracle_fuzz_test.go
  • conf_test.go
  • sender.go

Comment thread qwp_query_client.go
WithConnectTimeout and WithQwpQueryConnectTimeout convert a
time.Duration to integer milliseconds by truncation. A negative
duration whose magnitude is below one millisecond truncated to 0,
which reads as the valid zero (OS-default) case, so the negative
value silently bypassed the "negative durations are rejected" check
in sanitizeQwpConf / validate().

Both options now map any negative duration to -1 ms, which the
existing construction-time check rejects, while non-negative
durations keep the prior conversion and zero (OS-default) behavior.

Add white-box regression tests on both the ingest and query sides
covering sub-millisecond negatives, a whole-millisecond negative,
and the zero case.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@mtopolnik

mtopolnik commented Jul 28, 2026

Copy link
Copy Markdown
Contributor Author

Review: PR #66

Reviewing at level 3 (full mission-critical pass), scoped to in-diff issues. This is a QWP-config refactor: rename sf_max_bytessf_max_segment_bytes, removal of in_flight_window/gorilla/auth (options + keys), a new connect_timeout/WithConnectTimeout/WithQwpQueryConnectTimeout, key-scope tightening, and a user/pass canonicalization in the parser.

Verification performed: go build ./... and go vet ./... green; grep confirms zero dangling references to every removed/renamed identifier (sfMaxBytes, sf_max_bytes, WithSfMaxBytes, inFlightWindow, WithInFlightWindow, qwpDefaultInFlightWindow, gorillaDisabled, WithGorilla, WithQwpQueryAuth, query-client authorization) including comments and all platform-specific fallocate files; all newQwpLineSender callers use the new 6-arg signature; targeted unit tests pass (config, encoder, SF-conf, query-conf, async-sender).

Critical

None. No correctness, panic, concurrency, leak, or wire-format regression found. The encoder change removes a branch (never adds a hot-path allocation); the default wire output is unchanged since Gorilla was already the default-on path.

Moderate

1. The user/pass alias widens http::/tcp:: behavior — now documented in the PR body, but still untested there. (in-diff: conf_parse.go:911 canonicalConfigKey, conf_parse.go:879, conf_parse.go:168/178)

canonicalConfigKey is applied inside parseConfigStr, which runs for every schema, not just QWP. Before this PR, http::addr=x;user=alice; produced unsupported option "user"; now userusernamehttpUser and passpasswordhttpPass, so it is silently accepted. On tcp::, usertcpKeyId is likewise newly accepted (pass still rejects via the HTTP/QWP-only guard).

The functional risk is essentially nil (accepting the standard user/pass aliases is benign, arguably an improvement). The description-accuracy half of this finding is resolved — the PR body no longer claims the change is confined to QWP and now documents the shared-tokenizer aliasing and its effect on the legacy ILP transports. What remains is a test gap: there is no coverage for user/pass on http/tcp (TestConfUserPassAliases only exercises ws::). Add an http/tcp alias test to pin the now-documented behavior (or scope the canonicalization to QWP if the widening is unwanted).

Minor

1. Dead "user"/"pass" case arms in confFromStr. (conf_parse.go:168, conf_parse.go:178) Because parseConfigStr canonicalizes userusername and passpassword before confFromStr iterates KeyValuePairs, k is never "user"/"pass". The extra labels are unreachable — harmless but misleading. (CodeRabbit flagged the same.)

2. connect_timeout=0 is rejected via connect string but accepted via the option. parseConnectTimeoutMillis (conf_parse.go:766) rejects parsed <= 0, so connect_timeout=0 errors; but WithConnectTimeout(0) / WithQwpQueryConnectTimeout(0) treat 0 as the valid OS-default. The two construction paths disagree on whether 0 is legal. Defensible (matches auth_timeout_ms), but worth a one-line doc note or an intentional decision.

3. Two spellings of the same ms conversion in sibling functions. Ingress WithConnectTimeout uses int(d / time.Millisecond) (sender.go:709); egress WithQwpQueryConnectTimeout uses int(d.Milliseconds()) (qwp_query_client.go:461). Identical result — pick one for consistency.

4. Connect-timeout test gaps. No test asserts the opts→dialer wiring at qwp_transport.go:357 (that a positive connect_timeout actually installs the bounded net.Dialer); and no connect-string test pins http::…;connect_timeout= rejection (only the option path is covered by TestQwpOnlyOptionsRejectedOnHttpAndTcp). The field-threading itself is well covered.

Summary

  • Verdict: Approve — clean, thoroughly-tested refactor. The one Moderate item's description-accuracy half is now fixed in the PR body; only a small http/tcp user/pass test gap remains, alongside four Minor nits — none block merge.
  • Regressions/tradeoffs: none functional. The removals (WithInFlightWindow, WithGorilla, WithQwpQueryAuth) are deliberate breaking changes on the unreleased QWP surface, correctly flagged in the PR body. Default wire output is byte-identical.
  • In-diff vs out-of-diff: 5 in-diff, 0 out-of-diff. This is the correct outcome, not an underrun — the cross-context surfaces (renamed sfMaxBytes field, the de-variadic'd newQwpLineSender, connect_timeout threading through the cursor factory + reconnect + egress walk, effectiveAuthorization/mutual-exclusion) were each checked and found correctly updated by the diff.

One process note: the two open P-comments from the earlier commits (connect_timeout missing from options; UDP-key handling) are both resolved in the current tree — WithConnectTimeout/WithQwpQueryConnectTimeout exist, and UDP keys are shape-validated no-ops on http/tcp and rejected on QWP.

parseConfigStr canonicalizes the `user` / `pass` aliases to
`username` / `password` before confFromStr iterates the key/value
map, so the `user` and `pass` case labels never matched; confFromStr
only ever sees the canonical keys.

Drop the unreachable labels and reword the comments to state that
each canonical case handles both spellings.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@puzpuzpuz
puzpuzpuz merged commit d1fd2bd into main Jul 29, 2026
6 checks passed
@puzpuzpuz
puzpuzpuz deleted the mt_sf-max-segment-bytes branch July 29, 2026 09:54
kafka1991 added a commit that referenced this pull request Jul 29, 2026
Brings in the mainline QWP work (#66 connect-string key consolidation,
#67 connection recycle) on top of the qwip_go feature branch (delta symbol
dictionary, NACK policy v2, the QuestDB facade, durable-ack).

Conflict resolution notes:
- NACK policy: kept v2 (PolicyRetriable/Terminal + poison-frame detector);
  main's PolicyHalt/DropAndContinue is superseded and undefined post-merge.
- Config keys: adopted main's #66 policy per request — sf_max_segment_bytes
  rename, canonicalConfigKey aliases, duplicate-key rejection, transaction /
  UDP-key handling, and the strict QWP key vocabulary (gorilla,
  in_flight_window, token_x/token_y, protocol_version, egress auth are
  rejected/removed). Kept ours' orthogonal features (facade pool keys,
  durable-ack, connection-listener storage, delta symbol dictionary).
- connect_timeout: combined both branches' edge-case fixes (reject negatives,
  floor sub-millisecond positives to 1ms).
- README: dropped the duplicated legacy QWP section in favour of the
  reorganized facade-first structure.

go build ./..., go vet ./..., and the unit suite all pass.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants