Skip to content

Conversation

@glasstiger
Copy link
Contributor

@glasstiger glasstiger commented Jun 5, 2025

Support for Database Views

This PR introduces support for database views.

Views are virtual tables defined by a SELECT statement. They do not store data themselves; instead, their defining query is executed as a sub-query whenever the view is referenced in a SQL statement.

How Views Work in the Engine

In SQL queries views behave like regular tables or materialized views, and can be used anywhere a table is allowed.
During query compilation, if a view is referenced, its defining SQL is compiled and inserted into the execution model as a nested query, allowing the optimizer to consider the entire plan holistically.

Metadata and State Management

  • View metadata is persisted in the views directory, mirroring how table metadata is stored.
  • Each view has a view definition file that contains its defining SQL statement.
  • The view's state, whether it is valid or invalidated, is tracked in memory only. The state is not persisted since it can always be reproduced by compiling the view definition.

Automatic Recompilation

Views are automatically recompiled when operations occur on its dependencies. The view's dependencies are the tables, materialized views or other views referenced in its defining query.

The operations that trigger recompilation include:

  • CREATE / DROP table, materialized view, or view
  • RENAME TABLE
  • DROP / RENAME column
  • Changing column type

Recompilation always updates the view's state depending on the outcome of the operation.
For example:

  • If a view depends on a table, and the table is dropped, the view becomes invalid.
  • However, if the table is recreated, the view becomes valid again automatically.

Any query that fails will publish a recompilation event for all involved table-like objects too, allowing the view compiler job to refresh the state of all views dependent on the table-like objects involved in the failed query. This also helps to keep the state of the views up-to-date.

Manual View Compilation

The COMPILE VIEW command can be used to manually test if a view is valid.
This command will force recompilation of the view, and its state will be updated.

Discoverability

Views can be listed using the views() command, which also displays their current state.


required by https://github.com/questdb/questdb-enterprise/pull/633


TODOs

  • Implement CREATE VIEW <viewName> AS (query) @glasstiger
  • Load views on startup @glasstiger
  • Add views enabled/disabled config flag @glasstiger
  • Implement DROP VIEW <viewName> @glasstiger
  • BootstrapTest to test views via HTTP and PGWire @glasstiger
  • Query plan tests @glasstiger
  • Test error scenarios of create/drop view @glasstiger
  • Concurrent view create/drop test @glasstiger
  • Implement views() statement @glasstiger
  • Implement SHOW CREATE VIEW <viewName> @glasstiger
  • Adjust tables(), introduce table type: T, V, MV @glasstiger
  • Add lastUpdatedTimestamp field to view state and views() @glasstiger
  • Concurrent test for compile job @glasstiger
  • Tests with DECLARE @glasstiger
  • Tests with view aliases and with uppercase/lowercase view names in queries @glasstiger
  • Tests with non-ascii characters (rus/hun) in view names @glasstiger
  • Rejecting view modification statements, such as RENAME TABLE viewName @glasstiger
  • Invalidate views when used but cannot compile @glasstiger
  • Publish view compile event when a table or a column dropped/renamed @glasstiger
  • Implement COMPILE VIEW <viewName> to test view state @glasstiger
  • Views telemetry, what goes into this? Do we need it at all? @jerrinot
  • Add view related permission checks @glasstiger
  • No permission checks for the view's underlying tables/columns, need access to the view only @glasstiger
  • Replication of views @glasstiger
  • Bump WAL and replication format version?
  • Sync view metadata in compile job (only update column type for now) @glasstiger
  • Permission checks when factory comes from query cache @glasstiger
  • Add views to database checkpoint @jerrinot
  • Implement ALTER VIEW <viewName> AS (query) @glasstiger
  • Replication for ALTER VIEW @glasstiger
  • WalWriter not to create empty column files for views, get rid of _meta file too @glasstiger
  • Sequencer not to create _meta file for views @glasstiger
  • SELECT permission check refactoring to allow row level security via views @glasstiger
  • View metadata in-memory only @glasstiger
  • Fuzz tests @glasstiger
  • Views support in web console @jerrinot - feat: views support ui#512
  • Update documentation
  • DECLARE override for views @glasstiger
  • Extend DECLARE's syntax to allow const variables which cannot be overridden with view parameters @jerrinot
  • Test for filter push-down in views @glasstiger
  • Views with non-deterministic functions, such as today() @jerrinot
  • Detect and reject cycles in view graph (v1 references v2, while v2 references v1 -> infinite compile loop) @jerrinot
  • Implement CREATE OR REPLACE VIEW @glasstiger
  • Remove ALTER VIEW syntax
  • Check if executionContext.isValidationOnly() handled on all view related SQL commands @glasstiger
  • Throw TableReferenceOutOfDateException if view definition has changed between query compilation and execution. This will make sure factories taken from query cache are recompiled.

Issues from review (Javier):

  • views() should fail when cairo.view.enabled is disabled (currently runs even when CREATE VIEW fails)
  • Views should not be queryable/listed as regular tables when views are disabled
  • Outer LIMIT ignored on views that contain internal LIMIT (e.g., select * from view_with_limit_20 limit 10 ignores the limit 10) @glasstiger
  • LATEST BY partially broken on views - chaining LATEST BY on view query fails expecting "on" keyword, but works as CTE @glasstiger
  • View state not updating after table rename-back: renaming table away invalidates view (correct), but renaming back does not revalidate it (view shows invalid but is actually queryable) @glasstiger
  • ORDER BY timestamp DESC in view breaks SAMPLE BY - querying view with DESC timestamp fails with "ASC order over TIMESTAMP column is required" even when wrapping with ORDER BY timestamp, but equivalent CTE works @glasstiger
  • Timestamp override in view definition (timestamp(timestamp)) appears to be ignored
  • COMPILE VIEW view1 AS ... should fail, the syntax should allow only COMPILE VIEW view1 @glasstiger

@glasstiger glasstiger added the DO NOT MERGE These changes should not be merged to main branch label Jun 5, 2025

import static io.questdb.cairo.wal.seq.TableSequencer.NO_TXN;

public class ViewWalWriter extends WalWriterBase implements AutoCloseable {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why do we need a specialized WAL writer? Please add a comment

Copy link
Contributor Author

Choose a reason for hiding this comment

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

added a comment in 0bae0c4

}

@Override
public void commit() {
Copy link
Collaborator

Choose a reason for hiding this comment

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

There is nothing to commit; no rows added.It looks like this method does not do anything, can it be throw new NotSupported()? Same with rollback()

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It makes the API a bit strange, but removed rollback() and commit() in 96a8532.
Moved event file sync into replaceViewDefinition().

final WalEventCursor.ViewDefinitionInfo info = walEventCursor.getViewDefinitionInfo();
engine.updateViewDefinition(viewToken, info.getViewSql(), info.getViewDependencies(), seqTxn, blockFileWriter, path);
} catch (CairoException e) {
LOG.error().$("could not update view definition [view=").$(viewToken)
Copy link
Collaborator

Choose a reason for hiding this comment

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

What if the error occurs on the replica, for example, due to an FD limit? How will it go back, in sync with the primary if this transaction is skipped?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 46bbc9f by re-throwing the exception.
View will be suspended, when resumed the view definition should be updated.

}
// WAL-E files can be deleted by the purge job after a commit.
// Update the view state before committing the transaction.
writer.setSeqTxn(seqTxn);
Copy link
Collaborator

Choose a reason for hiding this comment

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

This line is redundant, the same call is inside markSeqTxnCommitted()

Copy link
Contributor Author

Choose a reason for hiding this comment

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

removed redundant setSeqTxn() calls in 1f9c1c4

@bluestreak01 bluestreak01 removed the DO NOT MERGE These changes should not be merged to main branch label Jan 6, 2026
@glasstiger
Copy link
Contributor Author

[PR Coverage check]

😍 pass : 2079 / 2342 (88.77%)

file detail

path covered line new line coverage
🔵 io/questdb/cairo/wal/WalWriterMetadata.java 0 4 00.00%
🔵 io/questdb/cairo/wal/seq/TableMetadataChange.java 0 1 00.00%
🔵 io/questdb/cairo/CairoException.java 0 1 00.00%
🔵 io/questdb/cutlass/line/udp/LineUdpParserImpl.java 1 5 20.00%
🔵 io/questdb/DefaultServerConfiguration.java 1 3 33.33%
🔵 io/questdb/cutlass/text/CairoTextWriter.java 2 6 33.33%
🔵 io/questdb/cairo/wal/ApplyWal2TableJob.java 8 23 34.78%
🔵 io/questdb/cairo/wal/WalEventReader.java 1 2 50.00%
🔵 io/questdb/cairo/TableStructure.java 1 2 50.00%
🔵 io/questdb/griffin/engine/StaleViewCheckFactory.java 21 35 60.00%
🔵 io/questdb/cairo/view/ViewCompilerTask.java 5 8 62.50%
🔵 io/questdb/cairo/view/NoOpViewStateStore.java 5 8 62.50%
🔵 io/questdb/cairo/pool/ReaderPool.java 2 3 66.67%
🔵 io/questdb/cairo/GrowOnlyTableNameRegistryStore.java 3 4 75.00%
🔵 io/questdb/ServerMain.java 12 16 75.00%
🔵 io/questdb/tasks/TableWriterTask.java 3 4 75.00%
🔵 io/questdb/griffin/model/CompileViewModel.java 14 18 77.78%
🔵 io/questdb/cairo/wal/ViewWalWriter.java 67 83 80.72%
🔵 io/questdb/cairo/CairoEngine.java 85 104 81.73%
🔵 io/questdb/cairo/view/ViewMetadata.java 23 28 82.14%
🔵 io/questdb/griffin/engine/ops/CreateViewOperationImpl.java 63 76 82.89%
🔵 io/questdb/cairo/pool/ViewWalWriterPool.java 28 34 82.35%
🔵 io/questdb/cairo/wal/WalWriterBase.java 69 83 83.13%
🔵 io/questdb/griffin/engine/ops/CreateViewOperationBuilderImpl.java 18 21 85.71%
🔵 io/questdb/griffin/engine/table/ShowCreateViewRecordCursorFactory.java 69 81 85.19%
🔵 io/questdb/griffin/SqlCompilerImpl.java 304 348 87.36%
🔵 io/questdb/cairo/view/ViewCompilerJob.java 96 109 88.07%
🔵 io/questdb/cairo/DatabaseCheckpointAgent.java 39 44 88.64%
🔵 io/questdb/cairo/view/ViewDefinition.java 72 80 90.00%
🔵 io/questdb/griffin/engine/ops/AlterOperation.java 19 21 90.48%
🔵 io/questdb/cairo/TableToken.java 12 13 92.31%
🔵 io/questdb/griffin/engine/functions/catalogue/ViewsFunctionFactory.java 64 69 92.75%
🔵 io/questdb/griffin/SqlParser.java 161 171 94.15%
🔵 io/questdb/cairo/TableUtils.java 47 50 94.00%
🔵 io/questdb/cairo/view/ViewGraph.java 65 68 95.59%
🔵 io/questdb/cairo/wal/WalEventCursor.java 23 24 95.83%
🔵 io/questdb/cairo/wal/WalWriter.java 87 91 95.60%
🔵 io/questdb/griffin/model/QueryModel.java 57 60 95.00%
🔵 io/questdb/cairo/view/ViewStateStoreImpl.java 26 27 96.30%
🔵 io/questdb/cairo/view/ViewState.java 28 29 96.55%
🔵 io/questdb/cairo/AbstractPartitionFrameCursorFactory.java 30 31 96.77%
🔵 io/questdb/PropServerConfiguration.java 27 28 96.43%
🔵 io/questdb/griffin/SqlUtil.java 111 115 96.52%
🔵 io/questdb/griffin/engine/functions/catalogue/TablesFunctionFactory.java 36 37 97.30%
🔵 io/questdb/cairo/TableConverter.java 3 3 100.00%
🔵 io/questdb/cutlass/line/tcp/LineTcpMeasurementScheduler.java 5 5 100.00%
🔵 io/questdb/cairo/view/ViewCompilerExecutionContext.java 3 3 100.00%
🔵 io/questdb/griffin/engine/ops/CreateTableOperationBuilderImpl.java 1 1 100.00%
🔵 io/questdb/cutlass/http/processors/LineHttpTudCache.java 2 2 100.00%
🔵 io/questdb/cutlass/http/processors/SqlValidationProcessor.java 2 2 100.00%
🔵 io/questdb/cutlass/http/processors/JsonQueryProcessor.java 3 3 100.00%
🔵 io/questdb/griffin/SqlParserCallback.java 10 10 100.00%
🔵 io/questdb/cairo/DefaultCairoConfiguration.java 4 4 100.00%
🔵 io/questdb/DynamicPropServerConfiguration.java 1 1 100.00%
🔵 io/questdb/griffin/SqlExecutionContextImpl.java 4 4 100.00%
🔵 io/questdb/cairo/PartitionBy.java 12 12 100.00%
🔵 io/questdb/PropertyKey.java 11 11 100.00%
🔵 io/questdb/griffin/engine/table/PageFrameRecordCursorFactory.java 1 1 100.00%
🔵 io/questdb/std/GenericLexer.java 4 4 100.00%
🔵 io/questdb/cairo/pool/WalWriterPool.java 1 1 100.00%
🔵 io/questdb/cairo/pool/SqlCompilerPool.java 1 1 100.00%
🔵 io/questdb/cairo/TableWriter.java 1 1 100.00%
🔵 io/questdb/cairo/NanosTimestampDriver.java 1 1 100.00%
🔵 io/questdb/griffin/SqlCodeGenerator.java 48 48 100.00%
🔵 io/questdb/cairo/CairoConfigurationWrapper.java 4 4 100.00%
🔵 io/questdb/griffin/SqlOptimiser.java 5 5 100.00%
🔵 io/questdb/cairo/view/ViewDependencyList.java 4 4 100.00%
🔵 io/questdb/cairo/mv/MatViewGraph.java 8 8 100.00%
🔵 io/questdb/cutlass/pgwire/PGConnectionContext.java 1 1 100.00%
🔵 io/questdb/griffin/SqlKeywords.java 28 28 100.00%
🔵 io/questdb/cairo/security/AllowAllSecurityContext.java 5 5 100.00%
🔵 io/questdb/cairo/TableNameRegistryStore.java 10 10 100.00%
🔵 io/questdb/cairo/FullPartitionFrameCursorFactory.java 2 2 100.00%
🔵 io/questdb/griffin/engine/ops/UpdateOperation.java 1 1 100.00%
🔵 io/questdb/griffin/engine/ops/CreateViewOperationBuilder.java 1 1 100.00%
🔵 io/questdb/cairo/wal/WalEventWriter.java 21 21 100.00%
🔵 io/questdb/griffin/engine/ops/AlterOperationBuilder.java 6 6 100.00%
🔵 io/questdb/WorkerPoolManager.java 1 1 100.00%
🔵 io/questdb/cairo/wal/seq/SequencerMetadata.java 6 6 100.00%
🔵 io/questdb/cutlass/parquet/SerialParquetExporter.java 1 1 100.00%
🔵 io/questdb/std/AbstractLowerCaseCharSequenceHashSet.java 2 2 100.00%
🔵 io/questdb/cutlass/pgwire/PGPipelineEntry.java 1 1 100.00%
🔵 io/questdb/griffin/ExpressionParser.java 1 1 100.00%
🔵 io/questdb/cairo/security/DenyAllSecurityContext.java 1 1 100.00%
🔵 io/questdb/griffin/engine/table/DeferredSingleSymbolFilterPageFrameRecordCursorFactory.java 1 1 100.00%
🔵 io/questdb/cairo/security/ReadOnlySecurityContext.java 5 5 100.00%
🔵 io/questdb/cairo/MicrosTimestampDriver.java 1 1 100.00%
🔵 io/questdb/cairo/TableNameRegistryRW.java 4 4 100.00%
🔵 io/questdb/griffin/CompiledQueryImpl.java 10 10 100.00%
🔵 io/questdb/griffin/engine/table/AbstractPageFrameRecordCursorFactory.java 1 1 100.00%
🔵 io/questdb/cutlass/text/ParallelCsvFileImporter.java 1 1 100.00%
🔵 io/questdb/griffin/SqlException.java 1 1 100.00%
🔵 io/questdb/griffin/engine/table/ShowColumnsRecordCursorFactory.java 11 11 100.00%
🔵 io/questdb/cairo/sql/TableReferenceOutOfDateException.java 7 7 100.00%
🔵 io/questdb/griffin/engine/ops/CreateMatViewOperationBuilderImpl.java 1 1 100.00%
🔵 io/questdb/griffin/model/ExecutionModel.java 2 2 100.00%
🔵 io/questdb/cairo/IntervalPartitionFrameCursorFactory.java 2 2 100.00%

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.

6 participants