Skip to content

Conversation

@matt-aitken
Copy link
Member

Summary

  • Improve query experience and safety across ClickHouse and TSQL.

Changes

  • Display JSON columns when in non-pretty mode (no longer show [Object Object]).
  • Sanitize ClickHouse errors originating from TSQL.
  • Remove tenant details from errors.
  • Add AI-assisted error-fixing for queries.
  • Improve code quality and readability.
  • Provide autocomplete support for enum values.
  • Enforce limits on ClickHouse queries (10s query limit).
  • Add org-level and global concurrency limits.
  • Warn and train AI to avoid SELECT *; when used, only return core columns and show info.
  • If AI suggests no time range, default to past 7 days.
  • Format the default query for readability.
  • Add an admin-only EXPLAIN button.
  • Prevent impersonation queries from being saved to history.

@changeset-bot
Copy link

changeset-bot bot commented Jan 12, 2026

⚠️ No Changeset found

Latest commit: ec4da28

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 12, 2026

Walkthrough

The PR adds Redis-backed query concurrency limiting and related env-configured limits; admin-gated EXPLAIN support that returns generated SQL and execution plans; ClickHouse settings defaults for query protection; a RedisConcurrencyLimiter implementation and singleton exporter; changes to TSQL execution to support explain mode and surface hiddenColumns, explainOutput, and generatedSql; coreColumn support in schemas and propagation to AI prompts and query printer (hiddenColumns); error sanitization utilities; quote-aware and enum-aware SQL completion; JSON cell rendering refactor; various test updates and public API exports.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

🚥 Pre-merge checks | ❌ 3
❌ Failed checks (2 warnings, 1 inconclusive)
Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description provides a detailed bulleted summary of changes but lacks required template sections including issue closure, testing steps, changelog formatting, and a proper checklist. Fill in all required template sections: add 'Closes #', complete the checklist, describe testing steps, provide a formatted changelog, and include any relevant screenshots.
Docstring Coverage ⚠️ Warning Docstring coverage is 67.86% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'TRQL/Query improvements' is vague and generic, using non-specific terms without clearly conveying the primary changes in this multi-faceted PR. Consider a more specific title that highlights the main change, such as 'Add EXPLAIN support, concurrency limits, and query safety improvements' or similar.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

📜 Recent review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ba92c07 and ec4da28.

📒 Files selected for processing (1)
  • internal-packages/tsql/src/query/validator.test.ts
🧰 Additional context used
📓 Path-based instructions (9)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{ts,tsx}: Use types over interfaces for TypeScript
Avoid using enums; prefer string unions or const objects instead

Every Trigger.dev task must be exported; use task() function with unique id and run async function

Files:

  • internal-packages/tsql/src/query/validator.test.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use function declarations instead of default exports

Files:

  • internal-packages/tsql/src/query/validator.test.ts
**/*.{test,spec}.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use vitest for all tests in the Trigger.dev repository

Files:

  • internal-packages/tsql/src/query/validator.test.ts
**/*.{js,ts,jsx,tsx,json,md,css,scss}

📄 CodeRabbit inference engine (AGENTS.md)

Format code using Prettier

Files:

  • internal-packages/tsql/src/query/validator.test.ts
**/*.test.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.test.{ts,tsx,js,jsx}: Test files should live beside the files under test and use descriptive describe and it blocks
Avoid mocks or stubs in tests; use helpers from @internal/testcontainers when Redis or Postgres are needed
Use vitest for unit tests

Files:

  • internal-packages/tsql/src/query/validator.test.ts
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/otel-metrics.mdc)

**/*.ts: When creating or editing OTEL metrics (counters, histograms, gauges), ensure metric attributes have low cardinality by using only enums, booleans, bounded error codes, or bounded shard IDs
Do not use high-cardinality attributes in OTEL metrics such as UUIDs/IDs (envId, userId, runId, projectId, organizationId), unbounded integers (itemCount, batchSize, retryCount), timestamps (createdAt, startTime), or free-form strings (errorMessage, taskName, queueName)
When exporting OTEL metrics via OTLP to Prometheus, be aware that the exporter automatically adds unit suffixes to metric names (e.g., 'my_duration_ms' becomes 'my_duration_ms_milliseconds', 'my_counter' becomes 'my_counter_total'). Account for these transformations when writing Grafana dashboards or Prometheus queries

Files:

  • internal-packages/tsql/src/query/validator.test.ts
**/*.test.{ts,tsx,js}

📄 CodeRabbit inference engine (CLAUDE.md)

Use vitest exclusively for testing and never mock anything; use testcontainers instead

Files:

  • internal-packages/tsql/src/query/validator.test.ts
**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.test.{ts,tsx}: Place test files next to source files with .test.ts naming convention (e.g., MyService.ts → MyService.test.ts)
Use testcontainers helpers (redisTest, postgresTest, containerTest from @internal/testcontainers) for Redis/PostgreSQL testing instead of mocks

Files:

  • internal-packages/tsql/src/query/validator.test.ts
**/*.{ts,tsx,js}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx,js}: Import from @trigger.dev/core using subpaths only; never import from root of @trigger.dev/core
Always import task definitions from @trigger.dev/sdk, never from @trigger.dev/sdk/v3 or deprecated client.defineJob pattern

Files:

  • internal-packages/tsql/src/query/validator.test.ts
🧠 Learnings (2)
📓 Common learnings
Learnt from: matt-aitken
Repo: triggerdotdev/trigger.dev PR: 2870
File: apps/webapp/app/services/redisConcurrencyLimiter.server.ts:56-66
Timestamp: 2026-01-12T17:17:59.489Z
Learning: In `apps/webapp/app/services/redisConcurrencyLimiter.server.ts`, the query concurrency limiter will not be deployed with Redis Cluster mode, so multi-key operations (keyKey and globalKey in different hash slots) are acceptable and will function correctly in standalone Redis mode.
📚 Learning: 2026-01-12T11:01:34.777Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-12T11:01:34.777Z
Learning: Applies to internal-packages/database/prisma/migrations/**/*.sql : Edit database schema in internal-packages/database/prisma/schema.prisma; remove extraneous lines from migrations related to BackgroundWorker, TaskRun tags, waitpoints, and SecretStore indexes

Applied to files:

  • internal-packages/tsql/src/query/validator.test.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (23)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (7, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (5, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (8, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (4, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (2, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (6, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (4, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (1, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (6, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (5, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (7, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (2, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (1, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (8, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (3, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (3, 8)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - pnpm)
  • GitHub Check: units / packages / 🧪 Unit Tests: Packages (1, 1)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - npm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - npm)
  • GitHub Check: typecheck / typecheck
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - pnpm)
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (2)
internal-packages/tsql/src/query/validator.test.ts (2)

1-4: LGTM! Test setup follows guidelines.

Using vitest correctly and the test file is properly placed beside the source file.


109-124: Good alignment with PR objectives.

The enum validation tests now use explicit column selection (SELECT id, status) instead of SELECT *, which is consistent with the PR's goal to discourage wildcard selections and train users/AI to avoid SELECT *. Test assertions correctly verify both valid and invalid enum value scenarios.


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.

@claude
Copy link

claude bot commented Jan 12, 2026

PR Review: TRQL/Query improvements

This is a well-structured PR that adds important safety and UX improvements to the query feature. Below is my detailed review.


Overall Assessment

Strengths:

  • Good security practices with query concurrency limits and ClickHouse execution safeguards
  • Comprehensive error sanitization that removes internal implementation details
  • Solid test coverage for the new schema utilities and autocomplete functionality
  • AI-assisted error fixing is a nice UX touch
  • Admin-only EXPLAIN functionality is well-gated

Code Quality & Best Practices

Excellent Patterns

  1. RedisConcurrencyLimiter (apps/webapp/app/services/redisConcurrencyLimiter.server.ts): The atomic Lua script approach for concurrency limiting is well-designed - it prevents race conditions and handles both key-level and global limits in a single operation.

  2. Error Sanitization (internal-packages/tsql/src/query/schema.ts:747-895): Thorough sanitization of ClickHouse errors to remove tenant IDs and internal column names. The approach of stripping tenant filters and required filters is good defensive practice.

  3. Test Coverage: The tests in schema.test.ts are comprehensive, covering value mapping, virtual columns, field mappings, and error sanitization with realistic scenarios.

Minor Suggestions

  1. Schema.ts line 890-894: The replaceAllOccurrences function uses a regex pattern that could be inefficient for very long error messages with many replacements. Consider caching compiled patterns if this becomes a hot path.

  2. Route file is quite large (route.tsx at 2334 lines): Consider extracting some of the helper components (like TRQLGuideContent, FunctionCategory, TableSchemaContent) into separate files for better maintainability.


Potential Bugs or Issues

1. Concurrency Limiter Release on Error (Low Risk)

In queryService.server.ts:188-193, the finally block correctly releases the concurrency slot. However, if queryConcurrencyLimiter.release() itself throws (e.g., Redis connection issue), this error would be silently swallowed. Consider adding error logging:

finally {
  try {
    await queryConcurrencyLimiter.release({ key: organizationId, requestId });
  } catch (releaseError) {
    logger.error('Failed to release concurrency slot', { organizationId, requestId, error: releaseError });
  }
}

2. Missing Cleanup for AutoSubmit Ref (Low Risk)

In AIQueryInput.tsx, the lastAutoSubmitRef tracks the last submission but doesn't reset when the component unmounts. This is generally fine due to the ref nature, but worth noting if the component gets reused across different contexts.

3. Autocomplete Timing with setTimeout

In TSQLEditor.tsx:113-121, there's a 50ms setTimeout for triggering autocomplete on quote keypresses. This works but could potentially trigger multiple times in rapid succession. Consider using a debounce or checking if autocomplete is already active.


Performance Considerations

Good Decisions

  1. ClickHouse Query Limits: The configurable limits (QUERY_CLICKHOUSE_MAX_EXECUTION_TIME, QUERY_CLICKHOUSE_MAX_MEMORY_USAGE, etc.) in env.server.ts are sensible defaults that prevent runaway queries.

  2. Concurrency Limiting: The two-level concurrency limiting (per-org and global) is a good approach for protecting the system from abuse while still allowing legitimate usage.

  3. SELECT * Handling: Transforming SELECT * to core columns only and showing a warning is a pragmatic approach for columnar databases where SELECT * is expensive.

Potential Concerns

  1. Multiple EXPLAIN Queries (tsql.ts:180-207): When explain: true, the code runs the main EXPLAIN plus two additional EXPLAIN queries (ESTIMATE and PIPELINE). This could be slow. Consider making the additional explains optional or running them in parallel:
const [estimateResult, pipelineResult] = await Promise.all([
  runExplainQuery('ESTIMATE'),
  runExplainQuery('PIPELINE'),
]);
  1. History Query on Every Page Load: The QueryPresenter fetches history on every loader call. If history grows large, consider adding pagination or caching.

Security Concerns

Strong Points

  1. Admin-only EXPLAIN: Good restriction at route.tsx:249-251 ensuring only admins/impersonating users can access EXPLAIN functionality.

  2. Impersonation Query Skipping (route.tsx:267-268): Not saving queries to history during impersonation is a good privacy practice.

  3. Readonly Query Setting: Setting readonly: '1' in ClickHouse settings (queryService.server.ts:54) is an important safeguard.

  4. Error Message Sanitization: Removing organization IDs, project IDs, and environment IDs from error messages prevents information leakage.

Recommendation

Consider rate limiting the AI query endpoint in addition to the concurrency limiting for the query execution itself. The AI endpoint could be abused separately.


Test Coverage

Well Tested:

  • Value mapping utilities (getUserFriendlyValue, getInternalValue, etc.)
  • Virtual column detection
  • Field mapping helpers
  • Error message sanitization with comprehensive edge cases
  • TSQL completion including enum value completions

Could Use More Coverage:

  • The RedisConcurrencyLimiter class - consider adding integration tests using testcontainers
  • The AI query service streaming behavior
  • Edge cases for the autocomplete context detection

Summary

This is a solid PR that adds meaningful safety improvements to the query feature. The concurrency limiting, error sanitization, and ClickHouse query safeguards are all well-implemented. The code follows the project's patterns and has good test coverage for the new utilities.

Recommendations before merge:

  1. Consider adding error logging to the concurrency release in the finally block
  2. Consider running the additional EXPLAIN queries in parallel for better performance
  3. The route file could benefit from being split into smaller components, but this is not blocking

Overall: Approve with minor suggestions

Copy link
Contributor

@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

🤖 Fix all issues with AI agents
In @apps/webapp/app/services/redisConcurrencyLimiter.server.ts:
- Around line 56-66: The custom Redis command fails in Cluster mode because
acquireConcurrency (and the release() pipeline) are called with two keys that
map to different hash slots (keyKey vs globalKey); change to a
single-cluster-safe key strategy by either (a) collapsing both scopes into one
key passed to acquireConcurrency (e.g., store global counters under a namespaced
field inside the per-key key) or (b) ensure both keys hash to the same slot by
using Redis hash tags (make keyKey and globalKey include the same {tag} token).
Update the call sites in acquireConcurrency and the release() pipeline so only
the single slot-safe key is used (or both keys share the same {tag}), and adjust
any Lua/script logic to reference the unified key name or tagged keys
accordingly.
🧹 Nitpick comments (12)
apps/webapp/app/components/code/TSQLResultsTable.tsx (1)

372-392: Use unknown instead of any and handle undefined edge case.

Two issues:

  1. Per TypeScript best practices, prefer unknown over any for better type safety.
  2. JSON.stringify(undefined) returns undefined (not a string), which would cause jsonString.length to throw a TypeError.
Suggested fix
-function JSONCellValue({ value }: { value: any }) {
-  const jsonString = JSON.stringify(value);
+function JSONCellValue({ value }: { value: unknown }) {
+  if (value === undefined) {
+    return <span className="font-mono text-xs text-text-dimmed">undefined</span>;
+  }
+  const jsonString = JSON.stringify(value);
apps/webapp/app/components/code/tsql/tsqlCompletion.ts (1)

200-207: Use type instead of interface per coding guidelines.

The coding guidelines specify using types over interfaces for TypeScript definitions.

Suggested change
-interface ContextResult {
+type ContextResult = {
   type: CompletionContextType;
   tablePrefix?: string;
   /** Column being compared (for value context) */
   columnName?: string;
   /** Table alias for the column (for value context) */
   columnTableAlias?: string;
-}
+};
apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.query/route.tsx (1)

831-852: Consider: aiFixRequest always takes precedence over example prompts.

Once "Try fix error" is clicked, aiFixRequest is set and will always be used (aiFixRequest ?? examplePromptRequest). Clicking example prompts afterward won't have any effect because aiFixRequest is never cleared.

Consider clearing aiFixRequest after the AI query completes or when the user interacts with example prompts:

♻️ Suggested approach

In Page component, pass a callback to clear aiFixRequest:

 <QueryHelpSidebar
   ...
   aiFixRequest={aiFixRequest}
+  onAiFixComplete={() => setAiFixRequest(null)}
 />

Then in AITabContent, call it after the AI generates a query (via a callback from AIQueryInput).

internal-packages/tsql/src/query/schema.ts (1)

799-806: Low ReDoS risk - schema values are trusted.

Static analysis flagged these dynamically constructed regexes. However, the risk is low because:

  1. columnsToStrip comes from schema definitions (developer-controlled, not user input)
  2. The regex patterns (\s*=\s*'[^']*') are linear and don't contain nested quantifiers
  3. Error messages are typically short strings

The escapeRegex helper properly escapes special characters. If schema values ever become user-controllable, consider adding length limits or using string operations instead.

apps/webapp/app/v3/querySchemas.ts (1)

2-2: Consider moving autoFormatSQL to a shared utility.

Importing from a UI component (TSQLEditor.tsx) into a schema definition file creates a coupling that could cause issues:

  • Schemas might be imported in server-only contexts
  • Creates a circular dependency risk if TSQLEditor ever imports from querySchemas

Consider extracting autoFormatSQL to a shared utility file (e.g., ~/utils/sqlFormatter.ts) that both files can import from.

Suggested refactor

Create apps/webapp/app/utils/sqlFormatter.ts:

import { format as formatSQL } from "sql-formatter";

export function autoFormatSQL(sql: string) {
  return formatSQL(sql, {
    language: "sql",
    keywordCase: "upper",
    indentStyle: "standard",
    linesBetweenQueries: 2,
  });
}

Then update both files to import from the shared location.

internal-packages/tsql/src/query/validator.ts (1)

201-226: LGTM! Informative SELECT * handling.

The implementation:

  1. Detects SELECT * patterns in the query
  2. Collects core columns from all tables in the FROM clause
  3. Emits an info-level issue (not blocking) with helpful guidance
  4. Includes suggestedColumns for UI consumption

Using "info" severity is the right choice - it informs users about the behavior without blocking their query.

One minor note: if the same table appears with different aliases, coreColumns might contain duplicates. Consider deduplicating:

Optional: Deduplicate core columns
 // Collect core columns from all tables in context
-const coreColumns: string[] = [];
+const coreColumns = new Set<string>();
 for (const tableSchema of context.tables.values()) {
   const tableCoreColumns = getCoreColumns(tableSchema);
-  coreColumns.push(...tableCoreColumns);
+  tableCoreColumns.forEach(col => coreColumns.add(col));
 }
+const uniqueCoreColumns = Array.from(coreColumns);
apps/webapp/app/v3/services/aiQueryService.server.ts (1)

71-104: Consider extracting duplicate tool definitions.

The validateTSQLQuery and getTableSchema tools are duplicated between streamQuery and call methods. Consider extracting them to a private method to reduce duplication.

♻️ Optional refactor to reduce duplication
private buildTools() {
  return {
    validateTSQLQuery: tool({
      description:
        "Validate a TSQL query for syntax errors and schema compliance. Always use this tool to verify your query before returning it to the user.",
      parameters: z.object({
        query: z.string().describe("The TSQL query to validate"),
      }),
      execute: async ({ query }) => {
        return this.validateQuery(query);
      },
    }),
    getTableSchema: tool({
      description:
        "Get detailed schema information about available tables and columns. Use this to understand what data is available and how to query it.",
      parameters: z.object({
        tableName: z
          .string()
          .optional()
          .describe("Optional: specific table name to get details for"),
      }),
      execute: async ({ tableName }) => {
        return this.getSchemaInfo(tableName);
      },
    }),
  };
}

Then use tools: this.buildTools() in both methods.

Also applies to: 126-159

apps/webapp/app/services/queryService.server.ts (1)

140-153: Consider moving database queries outside the concurrency slot.

The prisma.project.findMany and prisma.runtimeEnvironment.findMany calls are executed while holding the concurrency slot. If these queries are slow, they unnecessarily consume the slot time. Consider fetching field mappings before acquiring the slot.

♻️ Move DB queries before concurrency acquisition
 export async function executeQuery<TOut extends z.ZodSchema>(
   options: ExecuteQueryOptions<TOut>
 ): Promise<TSQLQueryResult<z.output<TOut>>> {
   const {
     scope,
     organizationId,
     projectId,
     environmentId,
     history,
     customOrgConcurrencyLimit,
     ...baseOptions
   } = options;

+  // Build field mappings BEFORE acquiring concurrency slot
+  const projects = await prisma.project.findMany({
+    where: { organizationId },
+    select: { id: true, externalRef: true },
+  });
+
+  const environments = await prisma.runtimeEnvironment.findMany({
+    where: { project: { organizationId } },
+    select: { id: true, slug: true },
+  });
+
+  const fieldMappings: FieldMappings = {
+    project: Object.fromEntries(projects.map((p) => [p.id, p.externalRef])),
+    environment: Object.fromEntries(environments.map((e) => [e.id, e.slug])),
+  };

   // Generate unique request ID for concurrency tracking
   const requestId = crypto.randomUUID();
   // ... acquire slot and execute
internal-packages/tsql/src/query/printer.ts (1)

682-700: Consider deduplicating hiddenColumns when multiple tables are involved.

When expanding SELECT * across multiple tables, the same column name could appear in multiple schemas. The current implementation pushes to hiddenColumns without deduplication.

♻️ Consider using a Set for hiddenColumns

If column name uniqueness matters for the user notification, consider using a Set internally:

-  private hiddenColumns: string[] = [];
+  private hiddenColumnsSet: Set<string> = new Set();

   print(node: SelectQuery | SelectSetQuery): PrintResult {
     this.outputColumns = [];
-    this.hiddenColumns = [];
+    this.hiddenColumnsSet = new Set();
     const sql = this.visit(node);
     const result: PrintResult = {
       sql,
       params: this.context.getParams(),
       columns: this.outputColumns,
     };
-    if (this.hiddenColumns.length > 0) {
-      result.hiddenColumns = this.hiddenColumns;
+    if (this.hiddenColumnsSet.size > 0) {
+      result.hiddenColumns = Array.from(this.hiddenColumnsSet);
     }
     return result;
   }

And in getSelectableColumnsFromSchema:

-      this.hiddenColumns.push(columnName);
+      this.hiddenColumnsSet.add(columnName);
internal-packages/clickhouse/src/client/tsql.ts (2)

180-207: Consider parallelizing additional EXPLAIN queries.

The ESTIMATE and PIPELINE explain queries are executed sequentially. Since they're independent, running them in parallel could improve response time for the EXPLAIN feature.

♻️ Parallelize additional explain queries
-      for (const explainType of explainTypes) {
-        try {
-          const additionalQueryFn = reader.queryWithStats({
-            name: `${options.name}-explain-${explainType.name.toLowerCase()}`,
-            query: explainType.query,
-            params: z.record(z.any()),
-            schema: z.object({ explain: z.string() }),
-            settings: options.clickhouseSettings,
-          });
-
-          const [additionalError, additionalResult] = await additionalQueryFn(params);
-
-          if (!additionalError && additionalResult) {
-            const additionalRows = additionalResult.rows as Array<{ explain: string }>;
-            const output = additionalRows.map((r) => r.explain).join("\n");
-            additionalOutputs.push(`── ${explainType.name} ──\n${output}`);
-          }
-        } catch {
-          // Ignore errors from additional explain queries
-        }
-      }
+      const explainPromises = explainTypes.map(async (explainType) => {
+        try {
+          const additionalQueryFn = reader.queryWithStats({
+            name: `${options.name}-explain-${explainType.name.toLowerCase()}`,
+            query: explainType.query,
+            params: z.record(z.any()),
+            schema: z.object({ explain: z.string() }),
+            settings: options.clickhouseSettings,
+          });
+
+          const [additionalError, additionalResult] = await additionalQueryFn(params);
+
+          if (!additionalError && additionalResult) {
+            const additionalRows = additionalResult.rows as Array<{ explain: string }>;
+            const output = additionalRows.map((r) => r.explain).join("\n");
+            return `── ${explainType.name} ──\n${output}`;
+          }
+        } catch {
+          // Ignore errors from additional explain queries
+        }
+        return null;
+      });
+
+      const results = await Promise.all(explainPromises);
+      additionalOutputs.push(...results.filter((r): r is string => r !== null));

204-206: Consider logging suppressed errors for debugging.

The empty catch block silently swallows errors from additional explain queries. While this is intentional (best-effort), logging at debug level could help diagnose issues.

♻️ Add debug logging for suppressed errors
         } catch {
-          // Ignore errors from additional explain queries
+          // Best-effort: additional explain queries may fail for certain query types
+          logger.debug(`[TSQL] ${explainType.name} explain query failed`, {
+            name: options.name,
+          });
         }
apps/webapp/app/services/redisConcurrencyLimiter.server.ts (1)

84-91: Consider adding error handling for pipeline execution.

The release method uses a pipeline but doesn't check for execution errors. While ZREM is idempotent, logging failures could help debug issues.

♻️ Add error handling for release pipeline
   async release(options: { key: string; requestId: string }): Promise<void> {
     const { key, requestId } = options;
     const keyKey = this.#getKeyKey(key);
     const globalKey = this.#getGlobalKey();

     // Remove from both sets in a single round trip
-    await this.redis.pipeline().zrem(keyKey, requestId).zrem(globalKey, requestId).exec();
+    const results = await this.redis.pipeline().zrem(keyKey, requestId).zrem(globalKey, requestId).exec();
+    
+    // Check for errors (results is [[error, reply], [error, reply]])
+    if (results) {
+      for (const [error] of results) {
+        if (error) {
+          // Log but don't throw - release should be best-effort
+          console.warn(`[ConcurrencyLimiter] Release error: ${error.message}`);
+        }
+      }
+    }
   }
📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 768206c and ba92c07.

📒 Files selected for processing (20)
  • apps/webapp/app/components/code/AIQueryInput.tsx
  • apps/webapp/app/components/code/TSQLEditor.tsx
  • apps/webapp/app/components/code/TSQLResultsTable.tsx
  • apps/webapp/app/components/code/tsql/tsqlCompletion.test.ts
  • apps/webapp/app/components/code/tsql/tsqlCompletion.ts
  • apps/webapp/app/env.server.ts
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.query/route.tsx
  • apps/webapp/app/services/queryConcurrencyLimiter.server.ts
  • apps/webapp/app/services/queryService.server.ts
  • apps/webapp/app/services/redisConcurrencyLimiter.server.ts
  • apps/webapp/app/v3/querySchemas.ts
  • apps/webapp/app/v3/services/aiQueryService.server.ts
  • apps/webapp/test/components/code/tsql/tsqlCompletion.test.ts
  • internal-packages/clickhouse/src/client/tsql.ts
  • internal-packages/clickhouse/src/index.ts
  • internal-packages/tsql/src/index.ts
  • internal-packages/tsql/src/query/printer.ts
  • internal-packages/tsql/src/query/schema.test.ts
  • internal-packages/tsql/src/query/schema.ts
  • internal-packages/tsql/src/query/validator.ts
💤 Files with no reviewable changes (1)
  • apps/webapp/app/components/code/tsql/tsqlCompletion.test.ts
🧰 Additional context used
📓 Path-based instructions (15)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{ts,tsx}: Use types over interfaces for TypeScript
Avoid using enums; prefer string unions or const objects instead

Every Trigger.dev task must be exported; use task() function with unique id and run async function

Files:

  • apps/webapp/app/components/code/TSQLEditor.tsx
  • apps/webapp/app/services/queryConcurrencyLimiter.server.ts
  • internal-packages/tsql/src/query/schema.ts
  • internal-packages/tsql/src/query/printer.ts
  • apps/webapp/app/components/code/AIQueryInput.tsx
  • internal-packages/tsql/src/query/schema.test.ts
  • apps/webapp/app/components/code/TSQLResultsTable.tsx
  • apps/webapp/app/services/redisConcurrencyLimiter.server.ts
  • apps/webapp/app/services/queryService.server.ts
  • internal-packages/tsql/src/index.ts
  • apps/webapp/app/env.server.ts
  • apps/webapp/app/components/code/tsql/tsqlCompletion.ts
  • internal-packages/tsql/src/query/validator.ts
  • internal-packages/clickhouse/src/index.ts
  • internal-packages/clickhouse/src/client/tsql.ts
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.query/route.tsx
  • apps/webapp/app/v3/querySchemas.ts
  • apps/webapp/app/v3/services/aiQueryService.server.ts
  • apps/webapp/test/components/code/tsql/tsqlCompletion.test.ts
{packages/core,apps/webapp}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use zod for validation in packages/core and apps/webapp

Files:

  • apps/webapp/app/components/code/TSQLEditor.tsx
  • apps/webapp/app/services/queryConcurrencyLimiter.server.ts
  • apps/webapp/app/components/code/AIQueryInput.tsx
  • apps/webapp/app/components/code/TSQLResultsTable.tsx
  • apps/webapp/app/services/redisConcurrencyLimiter.server.ts
  • apps/webapp/app/services/queryService.server.ts
  • apps/webapp/app/env.server.ts
  • apps/webapp/app/components/code/tsql/tsqlCompletion.ts
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.query/route.tsx
  • apps/webapp/app/v3/querySchemas.ts
  • apps/webapp/app/v3/services/aiQueryService.server.ts
  • apps/webapp/test/components/code/tsql/tsqlCompletion.test.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use function declarations instead of default exports

Files:

  • apps/webapp/app/components/code/TSQLEditor.tsx
  • apps/webapp/app/services/queryConcurrencyLimiter.server.ts
  • internal-packages/tsql/src/query/schema.ts
  • internal-packages/tsql/src/query/printer.ts
  • apps/webapp/app/components/code/AIQueryInput.tsx
  • internal-packages/tsql/src/query/schema.test.ts
  • apps/webapp/app/components/code/TSQLResultsTable.tsx
  • apps/webapp/app/services/redisConcurrencyLimiter.server.ts
  • apps/webapp/app/services/queryService.server.ts
  • internal-packages/tsql/src/index.ts
  • apps/webapp/app/env.server.ts
  • apps/webapp/app/components/code/tsql/tsqlCompletion.ts
  • internal-packages/tsql/src/query/validator.ts
  • internal-packages/clickhouse/src/index.ts
  • internal-packages/clickhouse/src/client/tsql.ts
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.query/route.tsx
  • apps/webapp/app/v3/querySchemas.ts
  • apps/webapp/app/v3/services/aiQueryService.server.ts
  • apps/webapp/test/components/code/tsql/tsqlCompletion.test.ts
apps/webapp/app/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/webapp.mdc)

Access all environment variables through the env export of env.server.ts instead of directly accessing process.env in the Trigger.dev webapp

In webapp development, access environment variables via env export from apps/webapp/app/env.server.ts; never use process.env directly

Files:

  • apps/webapp/app/components/code/TSQLEditor.tsx
  • apps/webapp/app/services/queryConcurrencyLimiter.server.ts
  • apps/webapp/app/components/code/AIQueryInput.tsx
  • apps/webapp/app/components/code/TSQLResultsTable.tsx
  • apps/webapp/app/services/redisConcurrencyLimiter.server.ts
  • apps/webapp/app/services/queryService.server.ts
  • apps/webapp/app/env.server.ts
  • apps/webapp/app/components/code/tsql/tsqlCompletion.ts
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.query/route.tsx
  • apps/webapp/app/v3/querySchemas.ts
  • apps/webapp/app/v3/services/aiQueryService.server.ts
apps/webapp/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/webapp.mdc)

apps/webapp/**/*.{ts,tsx}: When importing from @trigger.dev/core in the webapp, use subpath exports from the package.json instead of importing from the root path
Follow the Remix 2.1.0 and Express server conventions when updating the main trigger.dev webapp

Files:

  • apps/webapp/app/components/code/TSQLEditor.tsx
  • apps/webapp/app/services/queryConcurrencyLimiter.server.ts
  • apps/webapp/app/components/code/AIQueryInput.tsx
  • apps/webapp/app/components/code/TSQLResultsTable.tsx
  • apps/webapp/app/services/redisConcurrencyLimiter.server.ts
  • apps/webapp/app/services/queryService.server.ts
  • apps/webapp/app/env.server.ts
  • apps/webapp/app/components/code/tsql/tsqlCompletion.ts
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.query/route.tsx
  • apps/webapp/app/v3/querySchemas.ts
  • apps/webapp/app/v3/services/aiQueryService.server.ts
  • apps/webapp/test/components/code/tsql/tsqlCompletion.test.ts
**/*.{js,ts,jsx,tsx,json,md,css,scss}

📄 CodeRabbit inference engine (AGENTS.md)

Format code using Prettier

Files:

  • apps/webapp/app/components/code/TSQLEditor.tsx
  • apps/webapp/app/services/queryConcurrencyLimiter.server.ts
  • internal-packages/tsql/src/query/schema.ts
  • internal-packages/tsql/src/query/printer.ts
  • apps/webapp/app/components/code/AIQueryInput.tsx
  • internal-packages/tsql/src/query/schema.test.ts
  • apps/webapp/app/components/code/TSQLResultsTable.tsx
  • apps/webapp/app/services/redisConcurrencyLimiter.server.ts
  • apps/webapp/app/services/queryService.server.ts
  • internal-packages/tsql/src/index.ts
  • apps/webapp/app/env.server.ts
  • apps/webapp/app/components/code/tsql/tsqlCompletion.ts
  • internal-packages/tsql/src/query/validator.ts
  • internal-packages/clickhouse/src/index.ts
  • internal-packages/clickhouse/src/client/tsql.ts
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.query/route.tsx
  • apps/webapp/app/v3/querySchemas.ts
  • apps/webapp/app/v3/services/aiQueryService.server.ts
  • apps/webapp/test/components/code/tsql/tsqlCompletion.test.ts
**/*.{ts,tsx,js}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx,js}: Import from @trigger.dev/core using subpaths only; never import from root of @trigger.dev/core
Always import task definitions from @trigger.dev/sdk, never from @trigger.dev/sdk/v3 or deprecated client.defineJob pattern

Files:

  • apps/webapp/app/components/code/TSQLEditor.tsx
  • apps/webapp/app/services/queryConcurrencyLimiter.server.ts
  • internal-packages/tsql/src/query/schema.ts
  • internal-packages/tsql/src/query/printer.ts
  • apps/webapp/app/components/code/AIQueryInput.tsx
  • internal-packages/tsql/src/query/schema.test.ts
  • apps/webapp/app/components/code/TSQLResultsTable.tsx
  • apps/webapp/app/services/redisConcurrencyLimiter.server.ts
  • apps/webapp/app/services/queryService.server.ts
  • internal-packages/tsql/src/index.ts
  • apps/webapp/app/env.server.ts
  • apps/webapp/app/components/code/tsql/tsqlCompletion.ts
  • internal-packages/tsql/src/query/validator.ts
  • internal-packages/clickhouse/src/index.ts
  • internal-packages/clickhouse/src/client/tsql.ts
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.query/route.tsx
  • apps/webapp/app/v3/querySchemas.ts
  • apps/webapp/app/v3/services/aiQueryService.server.ts
  • apps/webapp/test/components/code/tsql/tsqlCompletion.test.ts
apps/webapp/app/services/**/*.server.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/webapp.mdc)

Separate testable services from configuration files; follow the pattern of realtimeClient.server.ts (testable service) and realtimeClientGlobal.server.ts (configuration) in the webapp

Files:

  • apps/webapp/app/services/queryConcurrencyLimiter.server.ts
  • apps/webapp/app/services/redisConcurrencyLimiter.server.ts
  • apps/webapp/app/services/queryService.server.ts
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/otel-metrics.mdc)

**/*.ts: When creating or editing OTEL metrics (counters, histograms, gauges), ensure metric attributes have low cardinality by using only enums, booleans, bounded error codes, or bounded shard IDs
Do not use high-cardinality attributes in OTEL metrics such as UUIDs/IDs (envId, userId, runId, projectId, organizationId), unbounded integers (itemCount, batchSize, retryCount), timestamps (createdAt, startTime), or free-form strings (errorMessage, taskName, queueName)
When exporting OTEL metrics via OTLP to Prometheus, be aware that the exporter automatically adds unit suffixes to metric names (e.g., 'my_duration_ms' becomes 'my_duration_ms_milliseconds', 'my_counter' becomes 'my_counter_total'). Account for these transformations when writing Grafana dashboards or Prometheus queries

Files:

  • apps/webapp/app/services/queryConcurrencyLimiter.server.ts
  • internal-packages/tsql/src/query/schema.ts
  • internal-packages/tsql/src/query/printer.ts
  • internal-packages/tsql/src/query/schema.test.ts
  • apps/webapp/app/services/redisConcurrencyLimiter.server.ts
  • apps/webapp/app/services/queryService.server.ts
  • internal-packages/tsql/src/index.ts
  • apps/webapp/app/env.server.ts
  • apps/webapp/app/components/code/tsql/tsqlCompletion.ts
  • internal-packages/tsql/src/query/validator.ts
  • internal-packages/clickhouse/src/index.ts
  • internal-packages/clickhouse/src/client/tsql.ts
  • apps/webapp/app/v3/querySchemas.ts
  • apps/webapp/app/v3/services/aiQueryService.server.ts
  • apps/webapp/test/components/code/tsql/tsqlCompletion.test.ts
**/*.{test,spec}.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use vitest for all tests in the Trigger.dev repository

Files:

  • internal-packages/tsql/src/query/schema.test.ts
  • apps/webapp/test/components/code/tsql/tsqlCompletion.test.ts
**/*.test.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.test.{ts,tsx,js,jsx}: Test files should live beside the files under test and use descriptive describe and it blocks
Avoid mocks or stubs in tests; use helpers from @internal/testcontainers when Redis or Postgres are needed
Use vitest for unit tests

Files:

  • internal-packages/tsql/src/query/schema.test.ts
  • apps/webapp/test/components/code/tsql/tsqlCompletion.test.ts
**/*.test.{ts,tsx,js}

📄 CodeRabbit inference engine (CLAUDE.md)

Use vitest exclusively for testing and never mock anything; use testcontainers instead

Files:

  • internal-packages/tsql/src/query/schema.test.ts
  • apps/webapp/test/components/code/tsql/tsqlCompletion.test.ts
**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.test.{ts,tsx}: Place test files next to source files with .test.ts naming convention (e.g., MyService.ts → MyService.test.ts)
Use testcontainers helpers (redisTest, postgresTest, containerTest from @internal/testcontainers) for Redis/PostgreSQL testing instead of mocks

Files:

  • internal-packages/tsql/src/query/schema.test.ts
  • apps/webapp/test/components/code/tsql/tsqlCompletion.test.ts
apps/webapp/app/v3/services/**/*.server.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/webapp.mdc)

Organize services in the webapp following the pattern app/v3/services/*/*.server.ts

Files:

  • apps/webapp/app/v3/services/aiQueryService.server.ts
apps/webapp/**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/webapp.mdc)

Test files should only import classes and functions from app/**/*.ts files and should not import env.server.ts directly or indirectly; pass configuration through options instead

Files:

  • apps/webapp/test/components/code/tsql/tsqlCompletion.test.ts
🧠 Learnings (13)
📚 Learning: 2025-11-14T16:03:06.917Z
Learnt from: matt-aitken
Repo: triggerdotdev/trigger.dev PR: 2681
File: apps/webapp/app/services/platform.v3.server.ts:258-302
Timestamp: 2025-11-14T16:03:06.917Z
Learning: In `apps/webapp/app/services/platform.v3.server.ts`, the `getDefaultEnvironmentConcurrencyLimit` function intentionally throws an error (rather than falling back to org.maximumConcurrencyLimit) when the billing client returns undefined plan limits. This fail-fast behavior prevents users from receiving more concurrency than their plan entitles them to. The org.maximumConcurrencyLimit fallback is only for self-hosted deployments where no billing client exists.

Applied to files:

  • apps/webapp/app/services/queryConcurrencyLimiter.server.ts
  • apps/webapp/app/services/redisConcurrencyLimiter.server.ts
  • apps/webapp/app/services/queryService.server.ts
  • apps/webapp/app/env.server.ts
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Control concurrency using the `queue` property with `concurrencyLimit` option

Applied to files:

  • apps/webapp/app/services/queryConcurrencyLimiter.server.ts
  • apps/webapp/app/services/redisConcurrencyLimiter.server.ts
  • apps/webapp/app/services/queryService.server.ts
📚 Learning: 2026-01-12T11:01:34.777Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-12T11:01:34.777Z
Learning: Applies to internal-packages/database/prisma/migrations/**/*.sql : Edit database schema in internal-packages/database/prisma/schema.prisma; remove extraneous lines from migrations related to BackgroundWorker, TaskRun tags, waitpoints, and SecretStore indexes

Applied to files:

  • internal-packages/tsql/src/query/schema.ts
  • internal-packages/tsql/src/query/schema.test.ts
  • apps/webapp/app/services/queryService.server.ts
  • apps/webapp/app/components/code/tsql/tsqlCompletion.ts
  • internal-packages/clickhouse/src/client/tsql.ts
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.query/route.tsx
  • apps/webapp/app/v3/querySchemas.ts
  • apps/webapp/app/v3/services/aiQueryService.server.ts
📚 Learning: 2025-11-27T16:26:37.432Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-27T16:26:37.432Z
Learning: Applies to internal-packages/database/**/*.{ts,tsx} : Use Prisma for database interactions in internal-packages/database with PostgreSQL

Applied to files:

  • apps/webapp/app/services/queryService.server.ts
📚 Learning: 2026-01-12T11:01:34.777Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-12T11:01:34.777Z
Learning: Applies to internal-packages/database/prisma/migrations/**/*.sql : Database indexes must use CONCURRENTLY to avoid table locks; CONCURRENTLY indexes must be in separate migration files and cannot be combined with other schema changes

Applied to files:

  • apps/webapp/app/services/queryService.server.ts
📚 Learning: 2025-11-27T16:26:58.661Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/webapp.mdc:0-0
Timestamp: 2025-11-27T16:26:58.661Z
Learning: Applies to apps/webapp/app/**/*.{ts,tsx} : Access all environment variables through the `env` export of `env.server.ts` instead of directly accessing `process.env` in the Trigger.dev webapp

Applied to files:

  • apps/webapp/app/env.server.ts
📚 Learning: 2026-01-12T11:01:34.777Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-12T11:01:34.777Z
Learning: Applies to apps/webapp/app/**/*.{ts,tsx} : In webapp development, access environment variables via env export from apps/webapp/app/env.server.ts; never use process.env directly

Applied to files:

  • apps/webapp/app/env.server.ts
📚 Learning: 2025-06-14T08:07:46.625Z
Learnt from: ericallam
Repo: triggerdotdev/trigger.dev PR: 2175
File: apps/webapp/app/services/environmentMetricsRepository.server.ts:202-207
Timestamp: 2025-06-14T08:07:46.625Z
Learning: In apps/webapp/app/services/environmentMetricsRepository.server.ts, the ClickHouse methods (getTaskActivity, getCurrentRunningStats, getAverageDurations) intentionally do not filter by the `tasks` parameter at the ClickHouse level, even though the tasks parameter is accepted by the public methods. This is done on purpose as there is not much benefit from adding that filtering at the ClickHouse layer.

Applied to files:

  • apps/webapp/app/env.server.ts
📚 Learning: 2025-11-27T16:26:37.432Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-27T16:26:37.432Z
Learning: Applies to {packages/core,apps/webapp}/**/*.{ts,tsx} : Use zod for validation in packages/core and apps/webapp

Applied to files:

  • internal-packages/clickhouse/src/client/tsql.ts
📚 Learning: 2025-12-08T15:19:56.823Z
Learnt from: 0ski
Repo: triggerdotdev/trigger.dev PR: 2760
File: apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx:278-281
Timestamp: 2025-12-08T15:19:56.823Z
Learning: In apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx, the tableState search parameter uses intentional double-encoding: the parameter value contains a URL-encoded URLSearchParams string, so decodeURIComponent(value("tableState") ?? "") is required to fully decode it before parsing with new URLSearchParams(). This pattern allows bundling multiple filter/pagination params as a single search parameter.

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.query/route.tsx
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use `schemaTask()` from `trigger.dev/sdk/v3` with Zod schema for payload validation

Applied to files:

  • apps/webapp/app/v3/querySchemas.ts
📚 Learning: 2025-07-12T18:06:04.133Z
Learnt from: matt-aitken
Repo: triggerdotdev/trigger.dev PR: 2264
File: apps/webapp/app/services/runsRepository.server.ts:172-174
Timestamp: 2025-07-12T18:06:04.133Z
Learning: In apps/webapp/app/services/runsRepository.server.ts, the in-memory status filtering after fetching runs from Prisma is intentionally used as a workaround for ClickHouse data delays. This approach is acceptable because the result set is limited to a maximum of 100 runs due to pagination, making the performance impact negligible.

Applied to files:

  • apps/webapp/app/v3/querySchemas.ts
📚 Learning: 2025-06-10T09:31:01.040Z
Learnt from: ericallam
Repo: triggerdotdev/trigger.dev PR: 2158
File: internal-packages/clickhouse/src/client/queryBuilder.ts:77-92
Timestamp: 2025-06-10T09:31:01.040Z
Learning: ClickHouse SQL syntax requires GROUP BY clause to come before ORDER BY clause, following standard SQL clause ordering: SELECT, FROM, WHERE, GROUP BY, HAVING, ORDER BY, LIMIT.

Applied to files:

  • apps/webapp/app/v3/services/aiQueryService.server.ts
🧬 Code graph analysis (7)
internal-packages/tsql/src/query/printer.ts (2)
internal-packages/tsql/src/index.ts (2)
  • PrintResult (109-109)
  • TableSchema (94-94)
internal-packages/tsql/src/query/schema.ts (1)
  • TableSchema (285-303)
internal-packages/tsql/src/query/schema.test.ts (2)
internal-packages/tsql/src/query/schema.ts (3)
  • TableSchema (285-303)
  • column (318-330)
  • sanitizeErrorMessage (747-875)
apps/webapp/app/v3/querySchemas.ts (1)
  • runsSchema (27-420)
apps/webapp/app/components/code/TSQLResultsTable.tsx (2)
internal-packages/tsql/src/index.ts (1)
  • column (62-62)
apps/webapp/app/components/primitives/Tooltip.tsx (1)
  • SimpleTooltip (141-141)
apps/webapp/app/services/redisConcurrencyLimiter.server.ts (1)
apps/webapp/app/redis.server.ts (2)
  • RedisWithClusterOptions (4-13)
  • RedisClient (15-15)
internal-packages/tsql/src/query/validator.ts (2)
internal-packages/tsql/src/query/ast.ts (1)
  • Field (468-472)
internal-packages/tsql/src/query/schema.ts (1)
  • getCoreColumns (712-716)
internal-packages/clickhouse/src/client/tsql.ts (4)
internal-packages/tsql/src/index.ts (3)
  • compileTSQL (262-281)
  • sanitizeErrorMessage (82-82)
  • transformResults (125-125)
internal-packages/tsql/src/query/schema.ts (1)
  • sanitizeErrorMessage (747-875)
internal-packages/tsql/src/query/errors.ts (1)
  • QueryError (43-45)
internal-packages/tsql/src/query/results.ts (1)
  • transformResults (73-88)
apps/webapp/app/v3/querySchemas.ts (3)
internal-packages/tsql/src/index.ts (1)
  • column (62-62)
internal-packages/tsql/src/query/schema.ts (1)
  • column (318-330)
apps/webapp/app/components/code/TSQLEditor.tsx (1)
  • autoFormatSQL (282-289)
🪛 ast-grep (0.40.4)
internal-packages/tsql/src/query/schema.ts

[warning] 772-772: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(\\b${escapeRegex(table.name)}\\s+AS\\s+${escapeRegex(table.name)}\\b, "gi")
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html

(regexp-from-variable)


[warning] 801-801: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(\\(${escaped}\\s*=\\s*'[^']*'\\)\\s*AND\\s*, "gi")
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html

(regexp-from-variable)


[warning] 803-803: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(\\s*AND\\s*\\(${escaped}\\s*=\\s*'[^']*'\\), "gi")
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html

(regexp-from-variable)


[warning] 805-805: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(\\(${escaped}\\s*=\\s*'[^']*'\\), "gi")
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html

(regexp-from-variable)


[warning] 891-891: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp((?<![a-zA-Z0-9_])${escapeRegex(search)}(?![a-zA-Z0-9_]), "g")
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html

(regexp-from-variable)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (24)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (6, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (4, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (2, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (1, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (8, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (8, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (7, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (2, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (1, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (5, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (4, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (3, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (6, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (7, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (5, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (3, 8)
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - pnpm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - npm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - pnpm)
  • GitHub Check: units / packages / 🧪 Unit Tests: Packages (1, 1)
  • GitHub Check: typecheck / typecheck
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - npm)
  • GitHub Check: claude-review
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (48)
apps/webapp/app/components/code/TSQLResultsTable.tsx (2)

137-140: Consider handling null/undefined before the JSON type check for consistency.

In non-pretty mode, JSON columns bypass the null check on line 141, but this is handled correctly since JSON.stringify(null) returns "null". The inconsistency with pretty mode (which checks null/undefined first at lines 160-166) is minor but worth noting for maintainability.


283-285: LGTM!

Good centralization of JSON rendering logic. The null/undefined checks above (lines 160-166) ensure this path is safe.

apps/webapp/app/components/code/AIQueryInput.tsx (3)

35-36: LGTM!

The new optional autoSubmitKey prop with clear documentation is a clean way to force re-submission for identical prompts.


56-56: LGTM!

Composite object type correctly tracks both prompt and key for the re-submission check.


203-218: LGTM!

The auto-submit logic correctly handles the composite (prompt, key) comparison:

  • Early return guards against empty prompts and concurrent submissions
  • The isDifferent check properly handles null, prompt changes, and key changes
  • Dependency array is complete
apps/webapp/app/components/code/tsql/tsqlCompletion.ts (6)

3-4: LGTM!

Using type keyword for type-only imports is the correct approach.


91-96: LGTM!

The ternary formatting for argument hints is cleaner and produces correct output for function signatures.

Also applies to: 110-115


213-245: LGTM!

The extended regex patterns correctly handle partial string values after operators (e.g., column = 'val). The patterns appropriately match:

  • Comparison operators (=, !=, <>) with optional partial quoted strings
  • IN clauses including after commas

This enables autocomplete to work mid-value entry.


328-342: LGTM!

The createEnumValueCompletions function cleanly derives completions from allowedValues, properly formats them with surrounding quotes, and assigns the highest boost priority for value context.


393-413: LGTM!

The value context handling correctly:

  1. Looks up the column schema to get enum values
  2. Detects auto-paired closing quotes after the cursor
  3. Extends the replacement range to include the closing quote, avoiding doubled quotes like 'Completed''

This is a thoughtful UX improvement for editor auto-pairing behavior.


469-478: LGTM!

Conditionally setting to only when needed keeps the result clean and avoids unnecessary property assignments.

apps/webapp/test/components/code/tsql/tsqlCompletion.test.ts (3)

6-28: LGTM!

Good addition of sliceString to the mock context—this is necessary to support the new quote-detection logic in the implementation.


31-67: LGTM!

The test schema is well-structured with realistic enum columns (status, machine, environment_type) using LowCardinality(String) type and proper allowedValues arrays. This provides good coverage for testing enum value completions.


192-326: Excellent test coverage for enum value completions.

The test suite comprehensively covers:

  • Basic enum suggestions after =, !=, IN ( operators
  • Quote-aware completion (opening quote, partial input)
  • Multiple enum columns (status, machine, environment_type)
  • Edge cases: auto-paired quote replacement range extension, no extension when no closing quote, empty results for non-enum columns

The tests at lines 292-312 specifically validate the quote handling behavior which is crucial for good UX with editor auto-pairing.

apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.query/route.tsx (9)

43-43: LGTM!

The Callout component import is correctly added to support the hidden columns warning UI.


118-161: LGTM!

The loader correctly:

  • Passes admin/impersonation flags to hasQueryAccess for authorization
  • Computes isAdmin for downstream UI gating (Explain button)
  • Returns isAdmin in the loader response for client consumption

163-167: LGTM!

The explain field uses a string enum which is appropriate for form data handling. Using z.enum(["true", "false"]).nullable().optional() correctly handles the optional nature of the explain parameter.


248-270: LGTM!

The explain logic correctly:

  • Gates the feature to admin/impersonating users only (line 251)
  • Passes the explain flag to executeQuery (line 264)
  • Skips saving to query history for impersonating users (line 268), preventing audit trail pollution during support sessions

385-395: LGTM!

The Explain button is correctly implemented:

  • Conditionally rendered for admin users only
  • Uses form submission with name="explain" value="true" to pass the flag
  • Appropriately disabled during loading or when query is empty
  • Uses a secondary variant to visually differentiate from the primary Query button

424-433: LGTM!

The AI fix request state management is well designed:

  • Uses a key property to force re-execution when the same error message needs to be retried
  • handleTryFixError correctly switches to the AI tab and triggers a new fix request
  • The callback has stable dependencies (state setters are stable)

524-577: LGTM!

The results rendering logic is well structured:

  • Error state includes a helpful "Try fix error" button that triggers the AI assistant
  • Explain output displays both generated SQL and execution plan with proper scrolling
  • Hidden columns callout clearly explains SELECT * limitations and how to include omitted columns
  • The conditional rendering chain correctly prioritizes error → explain → results → empty state

751-797: LGTM!

The sidebar refactoring correctly implements controlled tabs:

  • activeTab and onTabChange props enable programmatic tab switching (e.g., when clicking "Try fix error")
  • aiFixRequest is threaded through to AITabContent for AI-assisted error fixing
  • The controlled ClientTabs pattern maintains a single source of truth for tab state in the parent

622-635: LGTM!

The QueryHelpSidebar invocation correctly passes:

  • activeTab and onTabChange for controlled tab state
  • aiFixRequest for the error-fixing flow

The component integration is clean and maintains unidirectional data flow.

apps/webapp/app/components/code/TSQLEditor.tsx (1)

107-123: LGTM! Quote-triggered autocomplete for enum values.

The implementation correctly triggers autocomplete after quote characters are typed, enabling users to get suggestions for enum values in WHERE clauses. The 50ms timeout ensures the quote character is inserted before the completion popup appears.

Minor observation: event.code === "Quote" may be redundant since event.key === "'" should cover the single quote case, but it doesn't hurt to have it as a fallback for different keyboard layouts.

internal-packages/tsql/src/query/schema.ts (3)

136-152: LGTM! Well-documented core column property.

The coreColumn property is clearly documented with its purpose (marking essential columns for default queries) and includes a helpful usage example.


702-716: LGTM!

Clean implementation that correctly extracts core column names from the table schema.


747-875: Complex but necessary error sanitization logic.

The function handles multiple sanitization concerns in a well-ordered sequence. A few observations:

  1. The step-by-step approach (tenant filters → cleanup → name replacement → alias removal) is logical
  2. Sorting replacements by length (longest first) prevents partial matches
  3. The extensive cleanup in Step 2 handles various edge cases from tenant filter removal

Consider adding unit tests specifically for edge cases like:

  • Multiple tenant filters being removed
  • Nested parentheses after filter removal
  • Empty WHERE clauses at end of query
apps/webapp/app/v3/querySchemas.ts (2)

46-47: Good selection of core columns.

The four columns marked as core (run_id, status, task_identifier, triggered_at) represent the essential information users typically need when querying runs. This aligns well with the PR objective of guiding users away from SELECT *.

Also applies to: 92-93, 109-113, 186-193


430-433: LGTM! Default query is now formatted.

The default query is wrapped with autoFormatSQL for better readability in the editor.

apps/webapp/app/env.server.ts (1)

1178-1188: LGTM! Well-structured environment variables for query limits.

The new environment variables follow the established patterns in this file:

  • Uses z.coerce.number().int() for type-safe integer parsing
  • Sensible defaults: 10s execution time, 1GB memory, 3 concurrent queries per org, 50 global
  • Clear naming convention with QUERY_ prefix
  • Properly uses Zod for validation as per coding guidelines
internal-packages/clickhouse/src/index.ts (2)

1-2: LGTM! Type-only import for ClickHouseSettings.

Correctly uses import type and export type to re-export the ClickHouse settings type without runtime overhead.


48-49: LGTM! QueryError export.

Exporting QueryError from the public API allows consumers to properly type-check and handle query-specific errors.

internal-packages/tsql/src/index.ts (1)

67-68: LGTM! Clean public API exports.

The new exports (getCoreColumns, sanitizeErrorMessage) are properly added with descriptive comments that group related functionality. This maintains the organized structure of the module's public API.

Also applies to: 81-82

internal-packages/tsql/src/query/validator.ts (2)

175-186: LGTM! Correct SELECT * detection.

The isSelectStar helper correctly identifies both unqualified (SELECT *) and qualified (SELECT table.*) asterisk patterns by checking the field chain structure.


40-50: Type definition follows coding guidelines.

The ValidationIssue type is correctly extended with the new "select_star" discriminant and optional suggestedColumns field. Using a type (via interface) aligns with the TypeScript conventions in the codebase.

apps/webapp/app/services/queryConcurrencyLimiter.server.ts (1)

1-29: LGTM! Clean singleton initialization following project conventions.

The file correctly:

  • Accesses environment variables via the env export from ~/env.server.ts as per coding guidelines
  • Uses the singleton pattern for Redis client reuse
  • Separates configuration from the testable RedisConcurrencyLimiter service
internal-packages/tsql/src/query/schema.test.ts (1)

461-781: Comprehensive test coverage for error sanitization.

The test suite thoroughly covers:

  • Fully qualified table.column replacements
  • Standalone table/column name replacements
  • Quoted and backtick identifier handling
  • Tenant isolation filter removal
  • Redundant alias cleanup
  • Required filters (e.g., engine = 'V2')
  • Edge cases with field mappings

The tests are well-organized and descriptive, following vitest conventions.

apps/webapp/app/v3/services/aiQueryService.server.ts (2)

261-277: Good addition of core column guidance for AI schema awareness.

The core column identification and annotation help the AI understand which columns to prefer when users don't specify explicit columns. The [CORE] suffix in descriptions provides clear guidance.


366-377: Well-structured rules for query safety.

The updated rules appropriately:

  1. Prohibit SELECT * for ClickHouse columnar performance
  2. Guide toward core columns for ambiguous selections
  3. Add default 7-day time filtering for safety
apps/webapp/app/services/queryService.server.ts (3)

31-56: Well-structured ClickHouse query protection settings.

The default settings appropriately enforce:

  • Execution time limits
  • Memory usage limits
  • AST complexity limits to prevent expensive queries
  • Read-only mode for safety

Good reference to PostHog's HogQL settings for battle-tested defaults.


101-119: Good concurrency control with clear error messaging.

The acquisition flow properly:

  • Uses UUID for unique request tracking
  • Returns user-friendly error messages distinguishing org vs global limits
  • Fails fast when limits are exceeded

188-194: Proper resource cleanup with finally block.

The concurrency slot is always released regardless of success or failure, preventing slot leakage.

internal-packages/tsql/src/query/printer.ts (2)

73-78: Good extension of PrintResult for hidden columns notification.

The optional hiddenColumns field allows callers to notify users which columns were omitted when SELECT * was expanded to core columns only.


619-643: Core-column expansion with appropriate fallback.

The SELECT * expansion correctly:

  • Only returns core columns when defined
  • Falls back to all columns when no core columns exist
  • Tracks hidden columns for user notification
internal-packages/clickhouse/src/client/tsql.ts (2)

70-76: Good admin-only EXPLAIN mode for query debugging.

The explain option is well-documented and appropriately scoped for admin use only.


167-170: Good error sanitization for user-facing messages.

Sanitizing ClickHouse error messages to use TSQL names instead of internal identifiers improves the user experience and avoids leaking implementation details.

apps/webapp/app/services/redisConcurrencyLimiter.server.ts (2)

15-33: Well-designed two-level concurrency limiter.

The class design appropriately:

  • Uses sorted sets with timestamps for active request tracking
  • Implements automatic expiry as a safety net
  • Configures sensible defaults (5-minute expiry)

101-142: Robust Lua script for atomic two-level limiting.

The script correctly:

  1. Purges expired entries before checking limits
  2. Checks global limit first (more restrictive)
  3. Then checks key-specific limit
  4. Atomically adds to both sets on success
  5. Sets expiry on both keys

The implementation is thread-safe due to Lua script atomicity.

@vibe-kanban-cloud
Copy link

Review Complete

Your review story is ready!

View Story

Comment !reviewfast on this PR to re-generate the story.

@claude
Copy link

claude bot commented Jan 12, 2026

PR Review: TRQL/Query Improvements

This is a well-structured PR that adds several security and usability improvements to the query page. Here's my detailed review:

Code Quality ✅

Strengths:

  • Good separation of concerns with the new RedisConcurrencyLimiter class
  • The Lua script for atomic concurrency acquisition is well-designed with proper cleanup of stale entries
  • Error message sanitization in sanitizeErrorMessage() is thorough, handling table names, column names, and tenant isolation filters
  • The coreColumn concept for SELECT * handling is a pragmatic solution for columnar database performance

Minor Suggestions:

  1. In redisConcurrencyLimiter.server.ts:56-57, consider adding a TypeScript interface for the custom command to improve type safety:
interface RedisWithConcurrencyCommand extends RedisClient {
  acquireConcurrency: (...args: string[]) => Promise<number>;
}
  1. The sanitizeErrorMessage function in schema.ts is quite long (~150 lines). Consider breaking it into smaller helper functions for maintainability.

Potential Bugs/Issues ⚠️

  1. Race condition in concurrency release (queryService.server.ts:188-193): If an exception occurs between acquiring the slot and the try block, the slot may never be released. Consider wrapping the acquire call inside the try block:
let acquired = false;
try {
  const acquireResult = await queryConcurrencyLimiter.acquire({...});
  if (!acquireResult.success) { return [...]; }
  acquired = true;
  // ... rest of the query execution
} finally {
  if (acquired) {
    await queryConcurrencyLimiter.release({...});
  }
}
  1. Missing error handling in explain queries (tsql.ts:204): The catch block for additional explain queries silently ignores errors. Consider at minimum logging these failures for debugging purposes.

  2. Test file relocation (tsqlCompletion.test.ts): The test file was moved from apps/webapp/app/components/code/tsql/ to apps/webapp/test/components/code/tsql/. The old file is deleted but ensure no import path issues arise.

Performance Considerations 🚀

Good:

  • ClickHouse query limits (QUERY_CLICKHOUSE_MAX_EXECUTION_TIME, etc.) prevent runaway queries
  • The concurrency limiter uses sorted sets with automatic expiry, which is memory-efficient
  • SELECT * expansion to core columns only is excellent for columnar database performance

Suggestions:

  1. The sanitizeErrorMessage function iterates through schemas multiple times. For high-error-rate scenarios, consider caching the reverse lookup maps.

  2. In extractColumnBeforeOperator() (tsqlCompletion.ts), the regex patterns are compiled on every call. Consider moving them to module scope.

Security Concerns 🔒

Positive:

  • Tenant isolation is maintained through schema guards
  • Error messages properly sanitize internal details (ClickHouse table names, tenant columns)
  • EXPLAIN is restricted to admin/impersonating users only
  • Impersonation queries skip history to prevent audit log pollution

Recommendations:

  1. The readonly: "1" setting in getDefaultClickhouseSettings() is good. Verify this is enforced at the ClickHouse user level as well for defense-in-depth.

  2. Consider rate limiting on the AI query generation endpoint to prevent abuse.

Test Coverage 📊

  • Test coverage is solid for the TSQL completion functionality
  • The schema.test.ts has comprehensive tests for value mapping and error sanitization
  • Good coverage for enum value autocomplete scenarios

Missing Coverage:

  1. No tests for RedisConcurrencyLimiter - consider adding tests using @internal/testcontainers
  2. No integration tests for the concurrency limiting behavior in queryService.server.ts
  3. The AIQueryService changes for "Fix this error" prompt lack tests

Additional Notes

  1. AI fix feature (route.tsx:347-351): The "Try fixing with AI" button is a nice UX addition. The error message is passed directly to the AI prompt - ensure the AI service handles potentially malicious error message content safely.

  2. Core columns concept: Good documentation in ColumnSchema.coreColumn. Consider adding an example in the schema comments showing which columns should typically be marked as core.

  3. Explain button visibility: The admin-only EXPLAIN button is a nice debugging feature. Consider adding a tooltip explaining what it does for users who haven't used it before.

Summary

This is a solid PR that improves query safety, adds useful rate limiting, and enhances the developer experience with better autocomplete and AI assistance. The main areas to address are:

  1. Fix the potential race condition in concurrency slot release
  2. Add tests for the new RedisConcurrencyLimiter class
  3. Consider breaking up the large sanitizeErrorMessage function

Recommendation: Approve with minor fixes


Review generated by Claude Code

@matt-aitken matt-aitken merged commit 9942518 into main Jan 13, 2026
33 checks passed
@matt-aitken matt-aitken deleted the query-improvements branch January 13, 2026 11:12
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.

3 participants