Skip to content

Conversation

@joaoleite
Copy link

@joaoleite joaoleite commented Sep 4, 2025

Summary

Add support for configuring and sending the X-Trino-Source header in Trino queries through the TRINO_SOURCE environment variable.

Changes

  • Added TrinoSource field to TrinoConfig struct
  • Added TRINO_SOURCE environment variable support (optional, no default value)
  • Modified ExecuteQuery to send X-Trino-Source header when configured
  • Header is only sent when TRINO_SOURCE is explicitly set

Use Case

Some Trino installations require the X-Trino-Source header for audit trails and query tracking in enterprise environments. This change enables users to configure this header without code modifications.

Testing

  • Code compiles successfully
  • Unit tests added for TrinoConfig environment variable handling
  • Unit tests added for X-Trino-Source header logic
  • All existing tests continue to pass
  • Maintains backward compatibility (header only sent when configured)
  • No breaking changes to existing functionality

Test Coverage

  • Configuration parsing: TRINO_SOURCE environment variable
  • Default value handling: empty string when not set
  • Header sending logic: conditional based on configuration
  • Edge cases: empty values, missing environment variables

Example Usage

export TRINO_SOURCE="my-application"
./mcp-trino

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- New Features
  - Optional support for sending an X-Trino-Source header with Trino requests via the TRINO_SOURCE environment variable; if unset or empty, no header is sent.

- Tests
  - Added tests verifying TRINO_SOURCE handling and configuration defaults.
  - Added client tests confirming behavior with and without the source header.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

@coderabbitai
Copy link

coderabbitai bot commented Sep 4, 2025

Walkthrough

Adds a TrinoSource field to TrinoConfig (loaded from TRINO_SOURCE), updates the Trino client to optionally send an X-Trino-Source header when TrinoSource is non-empty, and adds unit tests for config defaults and header behavior.

Changes

Cohort / File(s) Summary
Config: Trino source and tests
internal/config/config.go, internal/config/config_test.go
Added exported TrinoSource string to TrinoConfig. NewTrinoConfig reads TRINO_SOURCE (no default) into this field. Tests validate TRINO_SOURCE set/empty/unset and default values for host, port, user, catalog, schema, and TrinoSource.
Trino client: conditional header and tests
internal/trino/client.go, internal/trino/client_test.go
Client query invocation now conditionally includes X-Trino-Source via a named parameter when config.TrinoSource is non-empty; otherwise calls the existing path. Tests verify header presence/absence based on TrinoSource value.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant App as Caller
  participant Client as TrinoClient
  participant Cfg as TrinoConfig
  participant DB as Trino Driver

  App->>Client: Execute(ctx, query)
  Client->>Cfg: check TrinoSource
  alt TrinoSource non-empty
    Note over Client,DB: attach X-Trino-Source header
    Client->>DB: QueryContext(ctx, query, Named("X-Trino-Source", TrinoSource))
  else TrinoSource empty
    Client->>DB: QueryContext(ctx, query)
  end
  DB-->>Client: rows / error
  alt error
    Client-->>App: wrapped error
  else success
    Client->>Client: process rows (defer close)
    Client-->>App: results
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

A rabbit hops through env and source,
I sniff TRINO_SOURCE with gentle force.
If empty, I skip the extra tag,
If set, I tuck it in the bag.
Tests nibble crumbs — all green, of course. 🐇✨


📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 0e6d112 and 6051798.

📒 Files selected for processing (4)
  • internal/config/config.go (3 hunks)
  • internal/config/config_test.go (1 hunks)
  • internal/trino/client.go (1 hunks)
  • internal/trino/client_test.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • internal/trino/client_test.go
  • internal/trino/client.go
  • internal/config/config_test.go
  • internal/config/config.go
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

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

Other keywords and placeholders

  • Add @coderabbitai ignore or @coderabbit ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (3)
internal/config/config.go (1)

107-109: Trim and sanitize TRINO_SOURCE to avoid header formatting pitfalls.

Leading/trailing spaces or accidental newlines can produce invalid headers.

Apply:

-// Get Trino Source from env/config (no default)
-trinoSource := getEnv("TRINO_SOURCE", "")
+// Get Trino Source from env/config (no default) and sanitize
+trinoSource := strings.TrimSpace(getEnv("TRINO_SOURCE", ""))
+// Disallow CR/LF to prevent malformed headers
+trinoSource = strings.Map(func(r rune) rune {
+	if r == '\r' || r == '\n' {
+		return -1
+	}
+	return r
+}, trinoSource)
internal/config/config_test.go (1)

8-52: Use t.Setenv to avoid env leakage between subtests; add a whitespace-sanitization case.

t.Setenv simplifies cleanup and makes tests safer when run in parallel; also add a case with surrounding spaces/newlines if you adopt trimming.

-// Clean up environment
-os.Unsetenv("TRINO_SOURCE")
-// Set environment variable if not UNSET
-if tt.envValue != "UNSET" {
-  os.Setenv("TRINO_SOURCE", tt.envValue)
-  defer os.Unsetenv("TRINO_SOURCE")
-}
+// Set env (auto-cleaned) unless UNSET sentinel
+if tt.envValue != "UNSET" {
+  t.Setenv("TRINO_SOURCE", tt.envValue)
+} else {
+  os.Unsetenv("TRINO_SOURCE")
+}

Optionally add:

{name: "TRINO_SOURCE with spaces/newline", envValue: "  app\n", expectedSource: "app"},
internal/trino/client.go (1)

129-139: Consider accepting a context to propagate caller deadlines.

Expose ExecuteQueryContext(ctx, query) and have ExecuteQuery call it with context.Background(). This respects upstream timeouts/cancellations while still applying your per-client timeout.

func (c *Client) ExecuteQueryContext(parent context.Context, query string) ([]map[string]interface{}, error) {
    ctx, cancel := context.WithTimeout(parent, c.timeout)
    defer cancel()
    // ...
}

func (c *Client) ExecuteQuery(query string) ([]map[string]interface{}, error) {
    return c.ExecuteQueryContext(context.Background(), query)
}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 5700b0d and 0e6d112.

📒 Files selected for processing (4)
  • internal/config/config.go (3 hunks)
  • internal/config/config_test.go (1 hunks)
  • internal/trino/client.go (1 hunks)
  • internal/trino/client_test.go (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
internal/config/config.go

📄 CodeRabbit inference engine (CLAUDE.md)

internal/config/config.go: Use environment-based configuration with validation for all connection/security settings
Force SSL when TRINO_SCHEME=https regardless of TRINO_SSL
Validate TRINO_QUERY_TIMEOUT > 0, otherwise fallback to 30s and log a warning

Files:

  • internal/config/config.go
internal/trino/client.go

📄 CodeRabbit inference engine (CLAUDE.md)

internal/trino/client.go: Enforce read-only SQL via isReadOnlyQuery(): allow SELECT/SHOW/DESCRIBE/EXPLAIN/WITH; block INSERT/UPDATE/DELETE/CREATE/DROP/ALTER unless TRINO_ALLOW_WRITE_QUERIES=true (with warning)
Implement database connection pooling (10 max open, 5 max idle, 5m max lifetime)
Use context-based timeouts for query execution and handle deadline propagation

Files:

  • internal/trino/client.go
🧠 Learnings (1)
📚 Learning: 2025-09-01T14:40:22.612Z
Learnt from: CR
PR: tuannvm/mcp-trino#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-01T14:40:22.612Z
Learning: Applies to internal/trino/client.go : Use context-based timeouts for query execution and handle deadline propagation

Applied to files:

  • internal/trino/client.go
🧬 Code graph analysis (1)
internal/config/config_test.go (1)
internal/config/config.go (1)
  • NewTrinoConfig (43-132)
🔇 Additional comments (3)
internal/config/config.go (2)

38-40: Field addition looks good and is backward-compatible.

Public TrinoSource on the config struct is the right place; no default keeps behavior unchanged when unset.


122-122: Plumbing of TrinoSource into the returned config is correct.

internal/config/config_test.go (1)

54-84: Defaults test reads well and covers the new field.

Comment on lines +183 to +213
func TestTrinoSourceHeader(t *testing.T) {
tests := []struct {
name string
trinoSource string
expectHeader bool
}{
{
name: "TrinoSource configured",
trinoSource: "test-application",
expectHeader: true,
},
{
name: "TrinoSource empty",
trinoSource: "",
expectHeader: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// This test verifies that our logic for conditionally sending
// the X-Trino-Source header is correct
if tt.trinoSource != "" && !tt.expectHeader {
t.Error("Logic error: non-empty TrinoSource should expect header")
}
if tt.trinoSource == "" && tt.expectHeader {
t.Error("Logic error: empty TrinoSource should not expect header")
}
})
}
}
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Test doesn’t assert the actual header behavior; it only checks table logic.

This won’t catch regressions. Prefer asserting the DSN includes source= or refactor DSN construction into a helper for direct testing.

Example approach:

// in client.go (non-test): factor out
func buildDSN(cfg *config.TrinoConfig) string { /* compose DSN incl. source if set */ }

// in test:
cfg := &config.TrinoConfig{ /* ... TrinoSource: "test-application" ... */ }
dsn := trino.BuildDSN(cfg)
if !strings.Contains(dsn, "source=test-application") { t.Fatalf("missing source in DSN: %s", dsn) }

Alternatively, spin up a test HTTP server and point the driver at it to assert X-Trino-Source is present, but the DSN test is simpler and sufficient here.


🌐 Web query:

In the Go driver github.com/trinodb/trino-go-client, which DSN parameter maps to the X-Trino-Source header?

💡 Result:

The DSN parameter is source — it sets the X-Trino-Source header. [1][2]

Sources:
[1] github.com/trinodb/trino-go-client README (DSN parameters: source).
[2] Trino client protocol docs (X-Trino-Source header description).


Extract DSN construction into a helper and test the source DSN parameter
Current test only validates boolean logic and won’t catch regressions in header inclusion. Add a buildDSN(cfg *config.TrinoConfig) function (e.g. in client.go) and write tests asserting the DSN string contains source=test-application when TrinoSource is set and omits it when empty.

Comment on lines +140 to +148
// Execute the query with X-Trino-Source header if configured
var rows *sql.Rows
var err error
if c.config.TrinoSource != "" {
rows, err = c.db.QueryContext(ctx, query, sql.Named("X-Trino-Source", c.config.TrinoSource))
} else {
rows, err = c.db.QueryContext(ctx, query)
}

Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Incorrect way to set X-Trino-Source; this passes a query argument, not an HTTP header.

sql.Named("X-Trino-Source", ...) is treated as a bound parameter for placeholders, and will not set a header. It may also cause “unexpected arguments” errors for queries without placeholders.

Apply this diff to revert the per-query arg injection:

-// Execute the query with X-Trino-Source header if configured
-var rows *sql.Rows
-var err error
-if c.config.TrinoSource != "" {
-  rows, err = c.db.QueryContext(ctx, query, sql.Named("X-Trino-Source", c.config.TrinoSource))
-} else {
-  rows, err = c.db.QueryContext(ctx, query)
-}
+rows, err := c.db.QueryContext(ctx, query)

Then set the Source once at connection time via DSN so the driver emits X-Trino-Source automatically:

// after constructing dsn in NewClient:
if cfg.TrinoSource != "" {
    dsn = dsn + "&source=" + url.QueryEscape(cfg.TrinoSource)
}

This aligns with the Trino Go driver’s config model where source maps to X-Trino-Source.


🌐 Web query:

Does the Trino Go driver (github.com/trinodb/trino-go-client) map the DSN query param "source" to the X-Trino-Source request header?

💡 Result:

Yes. The trino-go-client DSN "source" parameter is the client's source and is sent to the server as the X-Trino-Source request header. [1][2][3]

Sources:

  • trino-go-client README (documents the DSN "source" parameter). [1]
  • Trino client protocol docs (defines X-Trino-Source header). [2]
  • trino-go-client GitHub issue referencing how DSN params (e.g., session_properties) are mapped into X-Trino-* headers in trino.go. [3]

Use DSN source param instead of per-query bound parameter to set X-Trino-Source header
sql.Named("X-Trino-Source", …) is treated as a SQL argument, not an HTTP header, and can trigger “unexpected arguments” errors. The Trino Go driver maps the DSN’s source query parameter to the X-Trino-Source header, so remove the per-query injection and configure source on the DSN in NewClient:

-// Execute the query with X-Trino-Source header if configured
-var rows *sql.Rows
-var err error
-if c.config.TrinoSource != "" {
-  rows, err = c.db.QueryContext(ctx, query, sql.Named("X-Trino-Source", c.config.TrinoSource))
-} else {
-  rows, err = c.db.QueryContext(ctx, query)
-}
+rows, err := c.db.QueryContext(ctx, query)

Then, after constructing the DSN in NewClient:

if cfg.TrinoSource != "" {
    dsn += "&source=" + url.QueryEscape(cfg.TrinoSource)
}
🤖 Prompt for AI Agents
In internal/trino/client.go around lines 140 to 148, the code passes
sql.Named("X-Trino-Source", c.config.TrinoSource) as a query argument which the
driver treats as a SQL parameter (causing unexpected-argument errors); remove
the per-query injection and instead set the Trino X-Trino-Source header via the
DSN `source` query parameter when creating the client. Update NewClient where
you build the DSN to append `&source=` + url.QueryEscape(cfg.TrinoSource) when
cfg.TrinoSource is non-empty, and then delete the sql.Named(...) usage in the
query execution path so calls use c.db.QueryContext(ctx, query) only.

- Add TRINO_SOURCE environment variable configuration
- Support sending X-Trino-Source header in all Trino queries
- Helps with audit trails and query tracking in enterprise environments
- Header is sent only when TRINO_SOURCE is configured (optional)

Signed-off-by: Joao Leite <[email protected]>
- Add tests for TrinoConfig with TRINO_SOURCE environment variable
- Add tests for X-Trino-Source header logic
- Verify default values and environment variable handling
- All tests pass, maintaining backward compatibility

Signed-off-by: Joao Leite <[email protected]>
@joaoleite joaoleite force-pushed the feature/add-trino-source-header branch from 0e6d112 to 6051798 Compare September 4, 2025 17:19
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.

1 participant