Skip to content

Add tencent_cls_fdw for Tencent Cloud Log Service#585

Open
wenerme wants to merge 5 commits intosupabase:mainfrom
wenerme:feat/tencent-cls
Open

Add tencent_cls_fdw for Tencent Cloud Log Service#585
wenerme wants to merge 5 commits intosupabase:mainfrom
wenerme:feat/tencent-cls

Conversation

@wenerme
Copy link

@wenerme wenerme commented Mar 5, 2026

Summary

  • Add a new native FDW (tencent_cls_fdw) for querying Tencent Cloud Log Service (CLS) as foreign tables
  • Uses TC3-HMAC-SHA256 authentication with the CLS SearchLog API
  • Supports CQL (CLS Query Language) query syntax
  • WHERE clause pushdown for _from/_to (timestamp range) and _query (search expression)
  • Auto-parses LogJson fields for column extraction
  • Based on logflare_fdw template with CLS-specific adaptations

wenerme added 3 commits March 5, 2026 14:42
HTTP-based FDW using TC3-HMAC-SHA256 authentication.
Supports SearchLog API with CQL query language.
WHERE pushdown for _from/_to (timestamps) and _query.
LogJson field auto-parsed for column extraction.
Based on logflare_fdw template.
@coderabbitai
Copy link

coderabbitai bot commented Mar 5, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Central YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: cfb9fe26-f2df-44a9-b839-12a637a5fd31

📥 Commits

Reviewing files that changed from the base of the PR and between 450119a and 4223b8d.

📒 Files selected for processing (1)
  • wrappers/src/fdw/tencent_cls_fdw/tencent_cls_fdw.rs

📝 Walkthrough

Summary by CodeRabbit

  • New Features
    • Tencent Cloud CLS integration for PostgreSQL with signed API requests, retrying HTTP client, and configurable defaults (max response size, result limits).
    • Query CLS logs as tables, mapping JSON fields to queryable columns and supporting dynamic fields.
  • Bug Fixes / Reliability
    • Improved error handling and sanitized error reports for API, parsing, and oversized responses.
  • Tests
    • Added test scaffolding for the new CLS integration

Walkthrough

Adds a new Tencent Cloud Log Service (CLS) foreign data wrapper behind the feature flag tencent_cls_fdw. Cargo features and dependencies were added (reqwest, reqwest-middleware, reqwest-retry, serde_json/serde, hmac, sha2, hex, chrono, tokio, url, thiserror). The fdw module is conditionally included. New code provides error types and conversions, TC3 HMAC-SHA256 request signing, a Reqwest HTTP client with retry, JSON-to-Postgres-cell mapping, API request/response handling with max-response-size checks, and a ForeignDataWrapper implementation (new, begin_scan, iter_scan, re_scan, end_scan, validator) plus helpers and constants.

Sequence Diagram

sequenceDiagram
    actor PostgreSQL as PostgreSQL
    participant FDW as TencentClsFdw
    participant Client as HTTP Client
    participant API as Tencent CLS API
    participant Converter as JSON->Rows

    PostgreSQL->>FDW: new(server)
    Note over FDW: init runtime, creds, region, endpoint, client

    PostgreSQL->>FDW: begin_scan(quals, columns, options)
    FDW->>FDW: build JSON payload\napply quals/options
    FDW->>FDW: tc3_sign(secret_id, secret_key, service, host, ts, payload)
    FDW->>Client: POST (signed headers + payload)
    Client->>API: HTTP request
    API-->>Client: HTTP response (JSON or error)
    Client-->>FDW: response text
    FDW->>FDW: enforce max response size\nparse JSON
    FDW->>Converter: resp_to_rows(JSON)
    Converter-->>FDW: Vec<Row>

    loop iterate rows
      PostgreSQL->>FDW: iter_scan()
      FDW-->>PostgreSQL: next Row
    end

    PostgreSQL->>FDW: end_scan()
    FDW->>FDW: clear scan state
Loading

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 and usage tips.

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: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@wrappers/src/fdw/tencent_cls_fdw/tencent_cls_fdw.rs`:
- Around line 332-345: The code silently substitutes empty strings when
get_vault_secret(...) returns None; change both secret_id and secret_key
branches to treat None as an error and fail fast: after calling
require_option("secret_id_id", &server.options)? and
require_option("secret_key_id", &server.options)?, call get_vault_secret(id_key)
and if it returns Some(value) use value, otherwise return an Err with a clear
message that includes the id_key (e.g., "vault secret not found for id X") so
connection setup fails immediately instead of proceeding with empty credentials;
update the same logic for secret_key using the "secret_key_id" key.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Central YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: 88e8f94c-2261-4f97-b222-24fa89c24a93

📥 Commits

Reviewing files that changed from the base of the PR and between f56b281 and d9ab39f.

📒 Files selected for processing (5)
  • wrappers/Cargo.toml
  • wrappers/src/fdw/mod.rs
  • wrappers/src/fdw/tencent_cls_fdw/mod.rs
  • wrappers/src/fdw/tencent_cls_fdw/tencent_cls_fdw.rs
  • wrappers/src/fdw/tencent_cls_fdw/tests.rs

…ings

Replace get_vault_secret().unwrap_or_default() with proper error handling
for both secret_id and secret_key, so missing vault secrets fail fast
with a clear error message instead of silently using empty credentials.
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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@wrappers/src/fdw/tencent_cls_fdw/tencent_cls_fdw.rs`:
- Around line 70-75: The HTTP client lacks a request timeout and the response is
fully buffered via .text().await before checking body size; update create_client
to set a sensible request timeout on reqwest::Client::builder() (e.g., via
.timeout(Duration::from_secs(...))) and change the response handling that
currently uses .text().await to stream the body (e.g., via response.bytes_stream
or response.take) and enforce a MAX_BODY_SIZE when accumulating bytes so you
abort and return an error if the size limit is exceeded before converting to
text; reference the create_client function and the code paths that call
.text().await (replace with streaming+limit) and continue using
ClientBuilder/RetryTransientMiddleware as before.
- Around line 169-179: The header construction currently uses
HeaderValue::from_str(...).unwrap() for authorization and self.region which can
panic on invalid input; instead validate and map these conversions to a proper
FDW error path: replace the unwraps around HeaderValue::from_str(&authorization)
and HeaderValue::from_str(&self.region) (and similarly for timestamp) with
fallible handling (e.g., match or map_err) that returns an Err containing a
descriptive FDW error (or Error/Result used by the surrounding function) when
HeaderValue::from_str fails, so header insertion never panics but yields a
controlled error; locate the HeaderValue::from_str calls in the header-building
block (the headers.insert lines) and implement the conversion-to-HeaderValue
with explicit error mapping tied to the function's existing error type.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Central YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: 162397a5-b7ef-4934-968a-6ced495ae076

📥 Commits

Reviewing files that changed from the base of the PR and between d9ab39f and 450119a.

📒 Files selected for processing (2)
  • wrappers/src/fdw/tencent_cls_fdw/mod.rs
  • wrappers/src/fdw/tencent_cls_fdw/tencent_cls_fdw.rs

Comment on lines +70 to +75
fn create_client() -> TencentClsFdwResult<ClientWithMiddleware> {
let client = reqwest::Client::builder().build()?;
let retry_policy = ExponentialBackoff::builder().build_with_max_retries(3);
Ok(ClientBuilder::new(client)
.with(RetryTransientMiddleware::new_with_policy(retry_policy))
.build())
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

cd wrappers/src/fdw/tencent_cls_fdw && \
echo "=== Lines 70-75 context ===" && \
sed -n '65,80p' tencent_cls_fdw.rs && \
echo -e "\n=== Client builder usage ===" && \
rg -n 'Client::builder|\.timeout|\.connect_timeout' tencent_cls_fdw.rs && \
echo -e "\n=== text().await usage ===" && \
rg -n -B2 -A4 '\.text\(\).*await' tencent_cls_fdw.rs && \
echo -e "\n=== max_response_size checks ===" && \
rg -n -B3 -A3 'max_response_size' tencent_cls_fdw.rs

Repository: supabase/wrappers

Length of output: 836


🏁 Script executed:

cd wrappers/src/fdw/tencent_cls_fdw && \
echo "=== Lines 181-212 context ===" && \
sed -n '175,220p' tencent_cls_fdw.rs

Repository: supabase/wrappers

Length of output: 1672


Add request timeout and enforce body-size limits before full buffering.

The HTTP client at line 71 has no timeout configuration, risking indefinite hangs on unresponsive backends. Additionally, the response at lines 197–198 is fully buffered with .text().await before the size check at line 203, allowing large responses to exhaust memory or block workers before validation occurs.

Proposed fix
+use std::time::Duration;

 fn create_client() -> TencentClsFdwResult<ClientWithMiddleware> {
-    let client = reqwest::Client::builder().build()?;
+    let client = reqwest::Client::builder()
+        .connect_timeout(Duration::from_secs(10))
+        .timeout(Duration::from_secs(30))
+        .build()?;
     let retry_policy = ExponentialBackoff::builder().build_with_max_retries(3);
     Ok(ClientBuilder::new(client)
         .with(RetryTransientMiddleware::new_with_policy(retry_policy))
         .build())
 }
             let status = resp.status();
+            if let Some(content_len) = resp.content_length() {
+                if content_len as usize > self.max_response_size {
+                    return Err(TencentClsFdwError::ResponseTooLarge(
+                        content_len as usize,
+                        self.max_response_size,
+                    ));
+                }
+            }
+
+            let mut body = Vec::new();
+            let mut resp = resp;
+            while let Some(chunk) = resp
+                .chunk()
+                .await
+                .map_err(|e| TencentClsFdwError::RequestError(e))?
+            {
+                if body.len() + chunk.len() > self.max_response_size {
+                    return Err(TencentClsFdwError::ResponseTooLarge(
+                        body.len() + chunk.len(),
+                        self.max_response_size,
+                    ));
+                }
+                body.extend_from_slice(&chunk);
+            }
+            let text = String::from_utf8(body)
+                .map_err(|e| TencentClsFdwError::ApiError(format!("response is not valid UTF-8: {e}")))?;
-            let text = resp
-                .text()
-                .await
-                .map_err(|e| TencentClsFdwError::RequestError(e))?;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wrappers/src/fdw/tencent_cls_fdw/tencent_cls_fdw.rs` around lines 70 - 75,
The HTTP client lacks a request timeout and the response is fully buffered via
.text().await before checking body size; update create_client to set a sensible
request timeout on reqwest::Client::builder() (e.g., via
.timeout(Duration::from_secs(...))) and change the response handling that
currently uses .text().await to stream the body (e.g., via response.bytes_stream
or response.take) and enforce a MAX_BODY_SIZE when accumulating bytes so you
abort and return an error if the size limit is exceeded before converting to
text; reference the create_client function and the code paths that call
.text().await (replace with streaming+limit) and continue using
ClientBuilder/RetryTransientMiddleware as before.

…check

- Add connect_timeout(10s) and request timeout(30s) to HTTP client
- Replace HeaderValue::from_str().unwrap() with fallible error handling
  to avoid panics on non-ASCII input
- Check Content-Length header before buffering full response body
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