Skip to content

Conversation

@jerrinot
Copy link
Contributor

depends on questdb/questdb#5720

disclosure: most of the code was generated by Claude Code

depends on questdb/questdb#5720

disclosure: most of the code was generated by Claude Code
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR adds support for views in the QuestDB web console schema explorer. The implementation introduces a new table_type field to differentiate between regular tables ('T'), materialized views ('M'), and views ('V'), providing backward compatibility with older servers that don't return this field.

Key Changes

  • Added TableType and View types, plus new API methods showViewDDL() and showViews()
  • Views are now categorized using the table_type field from the tables API instead of relying on separate state to distinguish materialized views from regular tables
  • Views display with a dedicated eye icon and exclude storage details (which are only relevant for physical tables)

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
src/utils/questdb/types.ts Added TableType and View type definitions, added optional table_type field to Table type for backward compatibility
src/utils/questdb/client.ts Added showViewDDL() and showViews() API methods
src/scenes/Schema/table-icon.tsx Added ViewIcon component and rendering logic with tooltip
src/scenes/Schema/localStorageUtils.ts Added VIEWS_GROUP_KEY constant for localStorage
src/scenes/Schema/index.tsx Integrated views fetching, DDL export support, and categorization logic based on table_type
src/scenes/Schema/VirtualTables/utils.ts Updated createTableNode() to handle views, conditionally hide storage details for views
src/scenes/Schema/VirtualTables/index.tsx Added views tree section, filtering logic, and context menu support
src/scenes/Schema/Row/index.tsx Updated TreeNodeKind type and rendering logic to include "view"
Comments suppressed due to low confidence (1)

src/scenes/Schema/VirtualTables/utils.ts:167

  • Views are being passed table properties (partitionBy, walEnabled, designatedTimestamp) from the Table object, but regular views typically don't have these physical storage properties since they're virtual. These properties should either be undefined/null for views or the Table type returned by the server for views should handle this. Verify that the backend API returns appropriate values for these fields when table_type is 'V'.
    partitionBy: table.partitionBy,
    walEnabled: table.walEnabled,
    designatedTimestamp: table.designatedTimestamp,

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

<SuspensionDialog
walTableData={item.walTableData}
kind={item.kind}
kind={item.kind as "table" | "matview"}
Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

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

Type assertion might be unsafe here. The item.kind could theoretically be "view", but the SuspensionDialog is being passed item.kind as "table" | "matview" which excludes "view". While the code does check canSuspend before rendering the dialog (which ensures kind !== "view"), TypeScript doesn't recognize this relationship. Consider refactoring to make the type narrowing more explicit, or ensure SuspensionDialog can handle all valid kinds.

Copilot uses AI. Check for mistakes.
placement="bottom"
>
<Tooltip>
View - a stored query that can be referenced like a table.
Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

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

The tooltip text could be more consistent with the other tooltips in the file. Consider providing more details about what makes it different from materialized views, or keep it brief but informative like the materialized view tooltip.

Suggested change
View - a stored query that can be referenced like a table.
View a stored SELECT query that returns results on demand and does not store data like a materialized view.

Copilot uses AI. Check for mistakes.
Comment on lines +248 to +251
if (response?.type === QuestDB.Type.DQL && response.data?.[0]?.ddl) {
return response.data[0].ddl
}
tablesWithError.push(table)
Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

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

The ddls array may contain undefined values when a response doesn't contain valid DDL data (line 251 is reached). This would cause issues when joining the array on line 258. Consider filtering out undefined values or explicitly returning undefined from the map function and filtering them out.

Copilot uses AI. Check for mistakes.
Comment on lines 206 to +210
if (shownIfFilteredSuspendedOnly && shownIfFilteredWithQuery) {
acc[table.matView ? 1 : 0].push({
// Use table_type to categorize: 'T' = table, 'M' = matview, 'V' = view
// Default to 'T' (table) for backward compatibility with older servers
const tableType = table.table_type ?? "T"
const categoryIndex = tableType === "M" ? 1 : tableType === "V" ? 2 : 0
Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

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

Views are being filtered based on suspension status when filterSuspendedOnly is true. However, regular views typically don't have WAL support since they're virtual and cannot be suspended. This logic may incorrectly filter out views when the "Show suspended only" filter is active. Consider excluding views (table_type === 'V') from the suspension filter logic.

Copilot uses AI. Check for mistakes.

async showViewDDL(viewName: string): Promise<QueryResult<{ ddl: string }>> {
return await this.query<{ ddl: string }>(
`SHOW CREATE VIEW '${viewName}';`,
Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

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

SQL injection vulnerability: The viewName parameter is directly interpolated into the SQL query without any sanitization or parameterization. An attacker could pass a malicious viewName containing SQL commands. Consider using parameterized queries or properly escaping the input.

Copilot uses AI. Check for mistakes.
Comment on lines 174 to +175
void fetchMaterializedViews()
void fetchViews()
Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

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

The materializedViews state variable is still being fetched and maintained, but it's no longer used in the allSelectableTables memo dependency array (line 353). However, it's still passed down to VirtualTables and used there to populate matViewData. This is intentional since materialized views are now identified via table_type === "M", but the separate materializedViews state is still needed to provide additional metadata like base_table_name and invalidation_reason. Consider adding a comment explaining why both the table_type field and the materializedViews state are needed.

Copilot uses AI. Check for mistakes.
if (item.id === TABLES_GROUP_KEY || item.id === MATVIEWS_GROUP_KEY || item.id === VIEWS_GROUP_KEY) {
const isTable = item.id === TABLES_GROUP_KEY
const isMatView = item.id === MATVIEWS_GROUP_KEY
const isView = item.id === VIEWS_GROUP_KEY
Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

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

Unused variable isView.

Suggested change
const isView = item.id === VIEWS_GROUP_KEY

Copilot uses AI. Check for mistakes.
@jerrinot jerrinot requested a review from emrberk December 22, 2025 17:11
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.

2 participants