The temporal module adds SQL:2011 bi-temporal table support to ThemisDB. It covers: system-versioned tables (transaction time), application-versioned tables (valid time), time-travel queries (AS OF, FROM…TO, BETWEEN…AND), temporal indexes (B-tree and interval-tree), automated retention and archival policies, temporal aggregations (tumbling, sliding, session windows), temporal foreign keys with period-aware referential integrity, and CDC streaming of version change events. Affected source files: temporal_manager.cpp, temporal_query_engine.cpp, temporal_index_manager.cpp, retention_policy_manager.cpp, and associated headers under include/temporal/.
- All temporal operations must preserve transaction atomicity; history table writes and current-table writes occur in the same RocksDB
WriteBatch. - System time columns (
sys_start,sys_end) are database-generated and must never be writable by application code; DML attempts to set them directly returnPermissionDenied. - Time-travel queries must be read-only and must not acquire write locks; they operate on immutable history table snapshots.
- Retention enforcement runs as a background task and must not block foreground read/write operations; incremental deletion caps per-run latency at ≤ 100 ms per batch.
- The bi-temporal model uses UTC timestamps with nanosecond precision throughout; no timezone conversion is applied at storage time.
- New SQL syntax extensions (
FOR SYSTEM_TIME,FOR APPLICATION_TIME) are additive and must not break or alter the behavior of existing non-temporal queries.
| Interface | Consumer | Notes |
|---|---|---|
SystemVersionedTable::insert(table, doc) |
TemporalManager, SQL DML layer |
Writes current row + history row atomically in one WriteBatch |
TemporalQueryEngine::executeTemporalQuery(table, query, spec) |
AQL engine, REST query endpoint | spec encodes clause type (AS_OF, FROM_TO, BETWEEN_AND) and timestamps |
TemporalIndexManager::createTemporalIndex(table, name, spec) |
DDL handler | Creates B-tree or interval-tree index depending on spec.type |
RetentionPolicyManager::setRetentionPolicy(table, policy) |
Admin API, ALTER TABLE … SET RETENTION DDL |
Policy enforced by scheduled background job |
TemporalCDC::subscribeToChanges(table, callback) |
Replication module, Kafka connector | Delivers typed ChangeEvent per committed version write |
Priority: High
Target Version: v1.1.0
Status: ✅ Delivered — see include/temporal/system_versioned_table.h and src/temporal/system_versioned_table.cpp
Complete implementation of SQL:2011 temporal table standard.
Implemented Features:
- ✅ Automatic history table creation and management (
history_table_nameinConfig) - ✅ Transparent version tracking on all DML operations (
insert,update,upsert,deleteRow) - ✅ System-generated transaction time columns (
sys_timewith auto-assignednow()) - ✅ Efficient storage of historical versions (configurable
retention_period;enforceRetentionPolicy()) - ✅ Integration with table DDL operations (
createVersionedTablestatic factory with JSON schema) - ✅ User attribution tracking (
track_user_id/modified_by) - ✅ Compression flag (
compress_history) stored in config
Delivered API:
struct SystemVersionedTable::Config {
std::string history_table_name; // defaults to "<table>_history"
bool compress_history = true;
std::chrono::milliseconds retention_period{365LL * 24 * 3600 * 1000}; // 1 year
bool track_user_id = true;
};
// DDL factory — schema stored in getStatistics()["schema"]
static SystemVersionedTable createVersionedTable(
const std::string& table_name,
const Document& schema,
Config config,
const std::string& source_node = "local");
// Atomic insert-or-update (returns true = insert, false = update)
bool upsert(const std::string& key, const Document& doc);
// Apply config.retention_period; purge closed versions older than cutoff
size_t enforceRetentionPolicy();
const Config& getConfig() const noexcept;DDL Syntax (planned for AQL layer — Phase 3b):
CREATE TABLE employees (
id INTEGER PRIMARY KEY,
name VARCHAR(100),
salary DECIMAL(10,2),
department VARCHAR(50),
sys_start TIMESTAMP GENERATED ALWAYS AS ROW START,
sys_end TIMESTAMP GENERATED ALWAYS AS ROW END,
PERIOD FOR SYSTEM_TIME (sys_start, sys_end)
)
WITH SYSTEM VERSIONING;Performance Notes:
- History table write overhead: <15% (single in-memory append per DML operation)
- History table storage: 2-3x with compression (compress_history flag ready; codec integration is Phase 4)
- Time-travel query performance: 80-95% of current table queries (O(n) scan; temporal index integration in Phase 3b)
Priority: High
Target Version: v1.2.0
Status: ✅ Delivered — see include/temporal/bi_temporal.h and src/temporal/bi_temporal.cpp
Support for user-defined valid time periods alongside system transaction time.
Implemented Features:
- ✅ User-controlled valid time periods (
BiTemporalTable::insertWithValidTime,updateForValidTime) - ✅ Bi-temporal queries (transaction time + valid time) (
BiTemporalTable::queryBiTemporal,scanBiTemporal) - ✅ Temporal foreign keys (
TemporalForeignKey::validate()— period-aware referential integrity check) - ✅ Temporal uniqueness constraints (
BiTemporalTable::hasUniquenessConflict+ overlap rejection on insert) - ✅ Gap and overlap detection (
BiTemporalTable::findGaps,BiTemporalTable::findOverlaps)
Delivered API:
// Temporal uniqueness constraint check
bool BiTemporalTable::hasUniquenessConflict(
const std::string& key,
const TimeRange& period) const;
// Gap detection in valid-time coverage
std::vector<TimeRange> BiTemporalTable::findGaps(
const std::string& key,
Timestamp from,
Timestamp to) const;
// Period-aware referential integrity
struct TemporalForeignKey {
std::string parent_table_name;
bool validate(const BiTemporalTable& parent_table,
const std::string& parent_key,
const TimeRange& child_period) const;
};DDL Syntax (planned for AQL layer — Phase 3b):
CREATE TABLE contracts (
contract_id INTEGER PRIMARY KEY,
customer_id INTEGER,
amount DECIMAL(10,2),
valid_from DATE,
valid_to DATE,
PERIOD FOR APPLICATION_TIME (valid_from, valid_to),
sys_start TIMESTAMP GENERATED ALWAYS AS ROW START,
sys_end TIMESTAMP GENERATED ALWAYS AS ROW END,
PERIOD FOR SYSTEM_TIME (sys_start, sys_end)
)
WITH SYSTEM VERSIONING;Priority: High
Target Version: v1.2.0
Full SQL:2011 temporal query support with optimization.
Features:
- FOR SYSTEM_TIME AS OF queries
- FOR SYSTEM_TIME BETWEEN...AND queries
- FOR SYSTEM_TIME FROM...TO queries
- FOR APPLICATION_TIME queries
- Temporal predicates (OVERLAPS, CONTAINS, etc.)
- Query optimization for temporal operations
Implementation:
class TemporalQueryEngine {
public:
enum class TemporalClause {
AS_OF,
FROM_TO,
BETWEEN_AND,
CONTAINED_IN,
OVERLAPS
};
struct TemporalQuery {
TemporalClause clause;
std::chrono::system_clock::time_point start_time;
std::chrono::system_clock::time_point end_time;
bool include_deleted = false;
};
// Execute temporal query
Result<std::vector<Document>> executeTemporalQuery(
const std::string& table_name,
const std::string& base_query,
const TemporalQuery& temporal_spec
);
// Temporal join
Result<std::vector<Document>> temporalJoin(
const std::string& left_table,
const std::string& right_table,
const std::string& join_condition,
const TemporalQuery& left_temporal,
const TemporalQuery& right_temporal
);
};Query Examples:
-- As of specific time
SELECT * FROM employees
FOR SYSTEM_TIME AS OF TIMESTAMP '2024-01-01 00:00:00'
WHERE department = 'Engineering';
-- All versions in range
SELECT * FROM employees
FOR SYSTEM_TIME FROM TIMESTAMP '2024-01-01'
TO TIMESTAMP '2024-12-31'
WHERE employee_id = 12345;
-- Temporal join
SELECT e.name, d.dept_name
FROM employees FOR SYSTEM_TIME AS OF TIMESTAMP '2024-06-01' e
JOIN departments FOR SYSTEM_TIME AS OF TIMESTAMP '2024-06-01' d
ON e.department_id = d.dept_id;Optimization:
- Temporal index utilization
- Version pruning for AS OF queries
- Batch fetching for range queries
- Result caching for frequently accessed historical data
Priority: High
Target Version: v1.2.0
Specialized indexes for efficient temporal queries.
Features:
- Time-range indexes for efficient period queries
- Bi-temporal indexes (transaction + valid time)
- Covering indexes for common temporal patterns
- Index-only scans for temporal queries
- Automatic index selection
Implementation:
class TemporalIndexManager {
public:
enum class TemporalIndexType {
TIME_RANGE, // Optimized for range queries
BI_TEMPORAL, // Transaction + Valid time
SNAPSHOT, // Point-in-time queries
INTERVAL_TREE // Overlapping period detection
};
struct TemporalIndexSpec {
TemporalIndexType type;
std::vector<std::string> columns;
std::string time_column;
bool include_current = true;
bool include_history = true;
};
// Create temporal index
Result<std::string> createTemporalIndex(
const std::string& table_name,
const std::string& index_name,
const TemporalIndexSpec& spec
);
// Query using temporal index
Result<std::vector<VersionedDocument>> queryWithTemporalIndex(
const std::string& index_name,
const TimeRange& range
);
};Index Types:
- Time-Range Index: B-tree on (start_time, end_time) for range queries
- Bi-Temporal Index: Composite index on transaction time + valid time
- Snapshot Index: Optimized for AS OF queries
- Interval Tree: Efficient overlap detection for period queries
Performance Impact:
- AS OF queries: 10-100x speedup with snapshot index
- Range queries: 5-50x speedup with time-range index
- Overlap detection: 50-1000x speedup with interval tree
Priority: Medium
Target Version: v1.3.0
Automated lifecycle management for historical data.
Features:
- Time-based retention policies
- Storage-based retention policies
- Selective retention by table or column
- Archived data migration to cold storage
- Compliance-aware retention rules
Implementation:
class RetentionPolicyManager {
public:
enum class RetentionType {
TIME_BASED, // Keep data for N days/months/years
STORAGE_BASED, // Keep latest N GB of history
COUNT_BASED, // Keep latest N versions
CUSTOM // User-defined policy
};
struct RetentionPolicy {
RetentionType type;
std::chrono::seconds retention_period;
uint64_t max_storage_bytes;
size_t max_version_count;
bool archive_before_delete = true;
std::string archive_location;
std::function<bool(const Document&)> custom_predicate;
};
// Set retention policy for table
Result<bool> setRetentionPolicy(
const std::string& table_name,
const RetentionPolicy& policy
);
// Execute retention policy
Result<RetentionStats> enforceRetention(
const std::string& table_name
);
// Archive historical data
Result<bool> archiveHistory(
const std::string& table_name,
const TimeRange& range,
const std::string& archive_location
);
};
struct RetentionStats {
size_t versions_deleted;
size_t versions_archived;
uint64_t space_freed_bytes;
std::chrono::milliseconds execution_time;
};Policy Examples:
-- Time-based retention
ALTER TABLE employees
SET RETENTION_PERIOD = INTERVAL '2 YEARS';
-- Storage-based retention
ALTER TABLE audit_log
SET MAX_HISTORY_SIZE = '100 GB';
-- Custom retention with archiving
ALTER TABLE transactions
SET RETENTION_POLICY = (
PERIOD = INTERVAL '1 YEAR',
ARCHIVE_BEFORE_DELETE = TRUE,
ARCHIVE_LOCATION = 's3://archive-bucket/transactions/'
);Background Processing:
- Scheduled retention enforcement (daily/weekly)
- Incremental cleanup to avoid performance impact
- Progress tracking and logging
- Automatic retry on failures
Priority: Medium
Target Version: v1.3.0
Advanced aggregation operations over temporal data.
Features:
- Time-window aggregations
- Sliding window calculations
- Temporal GROUP BY operations
- Snapshot aggregations at regular intervals
- Trend analysis functions
Implementation:
class TemporalAggregator {
public:
enum class WindowType {
TUMBLING, // Non-overlapping windows
SLIDING, // Overlapping windows
SESSION // Gap-based windows
};
struct AggregationSpec {
WindowType window_type;
std::chrono::seconds window_size;
std::chrono::seconds slide_interval; // For sliding windows
std::string aggregation_function; // SUM, AVG, COUNT, etc.
std::vector<std::string> group_by_columns;
};
// Aggregate over time windows
Result<std::vector<AggregateResult>> aggregateOverTime(
const std::string& table_name,
const std::string& measure_column,
const AggregationSpec& spec,
const TimeRange& range
);
// Calculate trends
Result<TrendAnalysis> analyzeTrend(
const std::string& table_name,
const std::string& measure_column,
const TimeRange& range
);
};
struct AggregateResult {
std::chrono::system_clock::time_point window_start;
std::chrono::system_clock::time_point window_end;
double aggregate_value;
size_t record_count;
std::map<std::string, std::string> group_values;
};Query Examples:
-- Monthly sales aggregation
SELECT
YEAR(sys_start) as year,
MONTH(sys_start) as month,
SUM(amount) as total_sales
FROM sales
FOR SYSTEM_TIME FROM '2024-01-01' TO '2024-12-31'
GROUP BY YEAR(sys_start), MONTH(sys_start);
-- Moving average over time
SELECT
sys_start,
AVG(price) OVER (
ORDER BY sys_start
ROWS BETWEEN 29 PRECEDING AND CURRENT ROW
) as moving_avg_30day
FROM products
FOR SYSTEM_TIME FROM '2024-01-01' TO '2024-12-31';Priority: Medium
Target Version: v1.4.0
Referential integrity for temporal relationships.
Features:
- Temporal foreign key constraints
- Period-aware referential integrity
- Cascade operations for temporal updates
- Temporal referential actions (CASCADE, RESTRICT, SET NULL)
Implementation:
class TemporalConstraintManager {
public:
struct TemporalForeignKey {
std::string constraint_name;
std::string source_table;
std::string target_table;
std::vector<std::string> source_columns;
std::vector<std::string> target_columns;
bool check_temporal_overlap = true;
std::string on_delete_action; // CASCADE, RESTRICT, SET NULL
std::string on_update_action;
};
// Add temporal foreign key
Result<bool> addTemporalForeignKey(
const TemporalForeignKey& fk_spec
);
// Validate temporal referential integrity
Result<std::vector<IntegrityViolation>> validateIntegrity(
const std::string& table_name
);
};DDL Syntax:
ALTER TABLE employee_assignments
ADD CONSTRAINT fk_temporal_employee
FOREIGN KEY (employee_id, PERIOD FOR valid_time)
REFERENCES employees (employee_id, PERIOD FOR valid_time);Priority: High
Target Version: v1.4.0
Status: Delivered in src/temporal/interval_tree_index.cpp + include/temporal/interval_tree_index.h
Augmented BST-based interval tree providing O(log n) insert/remove and O(log n + k) overlap detection for valid-time period predicates and period-aware foreign-key enforcement.
Delivered Features:
- ✅ Max-end tracking per node for O(log n + k) overlap queries
- ✅ Half-open interval semantics
[start, end) - ✅
insert(),remove(),removeKey(),clear()operations - ✅
queryPoint(t),queryOverlap(from, to),queryKey(key, range)queries - ✅
stats()snapshot for observability (total entries, query counters, tree height) - ✅ Thread-safe via
std::mutex
Priority: Medium
Target Version: v1.4.0
Status: Delivered in src/temporal/temporal_cdc.cpp + include/temporal/temporal_cdc.h
Stream temporal change events for real-time processing.
Features:
- Real-time change event streaming
- Version-aware CDC
- Temporal diff generation
- Integration with event systems (Kafka, etc.)
- Replay historical changes
Actual implementation — see include/temporal/temporal_cdc.h:
std::string subscribeToChanges(
const std::string& table_name,
std::function<void(const ChangeEvent&)> callback);
bool unsubscribe(const std::string& sub_id);
void publishEvent(const ChangeEvent& event);
std::vector<ChangeEvent> replayChanges(
const std::string& table_name,
const TimeRange& range) const;
size_t logSize() const;
uint64_t totalPublished() const noexcept;
void clearLog();Priority: High
Target Version: v1.1.0
Status: Delivered in src/temporal/temporal_conflict_resolver.cpp + include/temporal/temporal_conflict_resolver.h
Enhanced conflict detection for distributed temporal databases.
Features:
- ✅ Multi-version concurrency control (MVCC) — concurrent HLC detection via
detectConcurrentUpdate - ✅ Optimistic locking for temporal updates — CONCURRENT_UPDATE conflict type
- ✅ Automatic conflict resolution strategies —
autoResolveConflictwith configurableConflictPolicy - ✅ Manual conflict resolution interface —
queueForManualResolution,getQueuedConflicts,clearQueue - ✅ Conflict audit trail — inherited from
TemporalConflictResolver::exportAuditLog
Implementation:
class TemporalConflictDetector {
public:
enum class ConflictType {
CONCURRENT_UPDATE,
OVERLAPPING_PERIODS,
REFERENTIAL_INTEGRITY,
UNIQUENESS_VIOLATION
};
struct Conflict {
ConflictType type;
std::string entity_id;
TemporalSnapshot local_version;
TemporalSnapshot remote_version;
std::vector<std::string> affected_columns;
};
// Detect conflicts
Result<std::vector<Conflict>> detectConflicts(
const std::string& table_name,
const TemporalSnapshot& local,
const TemporalSnapshot& remote
);
// Auto-resolve conflicts
Result<TemporalSnapshot> autoResolveConflict(
const Conflict& conflict,
ConflictPolicy policy
);
// Queue for manual resolution
Result<bool> queueForManualResolution(
const Conflict& conflict
);
};Priority: High
Target Version: v1.1.0
Transactional isolation for temporal queries.
Features:
- Consistent snapshot creation
- Repeatable read isolation for temporal queries
- Snapshot versioning
- Snapshot garbage collection
- Distributed snapshot coordination
Implementation:
class TemporalSnapshotManager {
public:
// Create consistent snapshot
Result<SnapshotHandle> createSnapshot(
const std::vector<std::string>& tables
);
// Query using snapshot
Result<std::vector<Document>> querySnapshot(
const SnapshotHandle& snapshot,
const std::string& query
);
// Release snapshot
Result<bool> releaseSnapshot(
const SnapshotHandle& snapshot
);
};
struct SnapshotHandle {
std::string snapshot_id;
std::chrono::system_clock::time_point creation_time;
std::vector<std::string> included_tables;
uint64_t version_number;
};Priority: Medium
Target Version: v1.3.0
Status: Delivered in src/temporal/temporal_compressor.cpp + include/temporal/temporal_compressor.h
Efficient compression for historical data.
Features:
- Delta compression between versions
- Time-series specific compression algorithms
- Selective column compression
- Transparent decompression on query
- Compression ratio monitoring
Implementation:
class TemporalCompressor {
public:
enum class CompressionAlgorithm {
DELTA, // Store deltas between versions
ZSTD, // General-purpose compression
GORILLA, // Time-series optimized
DICTIONARY // Dictionary encoding
};
struct CompressionConfig {
CompressionAlgorithm algorithm;
int compression_level = 3;
bool compress_immediately = false;
std::chrono::seconds delay_before_compression{24 * 3600}; // 1 day
};
// Compress historical versions
CompressionStats compressHistory(
const SystemVersionedTable& table,
const TimeRange& range,
const CompressionConfig& config
);
};
struct CompressionStats {
size_t versions_processed;
size_t versions_compressed;
size_t versions_skipped;
size_t original_size_bytes;
size_t compressed_size_bytes;
double compression_ratio;
std::chrono::milliseconds compression_time;
};Expected Compression Ratios:
- Delta compression: 5-10x for similar versions
- ZSTD: 2-5x for general data
- Gorilla: 10-20x for time-series numeric data
- Dictionary: 3-8x for high-cardinality string data
- System-versioned insert overhead: <15%
- AS OF query performance: 80-95% of current table
- History table compression: 3-5x space savings
- Conflict resolution latency: <10ms
- Time-travel query optimization: 10-100x speedup with indexes
- Bi-temporal query support: <2x overhead vs single time dimension
- Temporal join performance: Within 50% of non-temporal joins
- Retention enforcement: Process 1M versions/minute
- Temporal aggregation: 100K records/second
- Data compression: Achieve 5x average compression ratio
Target Version: v1.1.0
Migration path for existing tables to system-versioned.
Steps:
- Analyze existing table structure
- Create history table with matching schema
- Add system time columns (sys_start, sys_end)
- Backfill history from audit logs or change tracking
- Enable system versioning
- Verify data integrity
Tools:
class TemporalMigrator {
public:
// Analyze table for temporal migration
Result<MigrationPlan> analyzeMigration(
const std::string& table_name
);
// Execute migration
Result<bool> migrateToTemporal(
const std::string& table_name,
const MigrationPlan& plan
);
// Verify migration
Result<MigrationReport> verifyMigration(
const std::string& table_name
);
};Severity: High
Versions: v1.0.x
History table must be manually created and managed.
Workaround:
- Use TemporalConflictResolver for version tracking
- Manual history table management
- Application-level version tracking
Planned Fix: v1.1.0 - Automatic history table creation
Severity: High
Versions: v1.0.x, v1.1.x
No SQL syntax for time-travel queries.
Workaround:
- Use API-level temporal queries
- Manual timestamp filtering
- Application-level time-travel logic
Planned Fix: v1.2.0 - Full temporal query syntax
Severity: Medium
Versions: v1.0.x, v1.1.x, v1.2.x
Manual cleanup of old historical data required.
Workaround:
- Scheduled cleanup jobs
- Manual data purging
- Archive to external storage
Planned Fix: v1.3.0 - Automated retention policies
High Priority:
- System-versioned table implementation
- Time-travel query engine
- Temporal indexes
- Conflict detection enhancements
Medium Priority:
- Retention policy automation
- Temporal aggregations
- Data compression
- CDC integration
Low Priority:
- Temporal foreign keys
- Advanced temporal predicates
- Bi-temporal query optimization
- Follow SQL:2011 Standard: Maintain compatibility with standard temporal SQL
- Add Tests: Unit, integration, and performance tests required
- Document: Update README and API docs
- Benchmark: Include temporal query benchmarks
- Backward Compatibility: Maintain existing temporal APIs
For detailed guidelines, see CONTRIBUTING.md.
| Test Type | Coverage Target | Notes |
|---|---|---|
| Unit | ≥ 85% line coverage in temporal_manager.cpp and temporal_query_engine.cpp |
Cover AS OF, FROM…TO, BETWEEN…AND, gap/overlap detection, all four conflict types |
| Integration | All SQL:2011 temporal query clauses produce correct result sets | Run against an in-process RocksDB instance; result sets compared with hand-verified expected outputs |
| Performance | AS OF query with snapshot index ≤ 2× baseline non-temporal query latency at 1M rows |
Benchmark in benchmarks/bench_temporal.cpp; runs in CI for all temporal module PRs |
| Retention | Retention job processes ≥ 1M versions/minute without blocking concurrent foreground writes | Measured under a parallel read/write load during retention enforcement run |
| Bi-temporal | Bi-temporal join returns correct rows for all four period overlap predicates (OVERLAPS, CONTAINS, EQUALS, PRECEDES) |
Dedicated test fixture with 100 hand-crafted period combinations |
| Regression | Zero existing non-temporal AQL query failures after merging temporal changes | Full AQL test suite runs as a mandatory gate for all temporal PRs |
- System time columns (
sys_start,sys_end) are set exclusively by the database engine; any DML attempt to write these columns returns aPermissionDeniederror and is logged to the audit trail. - History table rows are append-only;
UPDATEandDELETEon the history table are rejected at the API layer with a descriptive error. - Retention deletion is irreversible: enforcement jobs require an explicit
RETENTION_POLICYDDL statement and log every batch deletion (table name, row count, time range) to the audit trail before executing. - Time-travel queries against sensitive tables are subject to the same
PolicyEngineRBAC controls as current-data queries; historical access does not bypass column-level redaction rules. - User-specified archive locations for retention policies are validated against the configured
data_dirallow-list to prevent directory traversal attacks. - Snapshot handles are released on transaction commit or abort; a snapshot GC watchdog forcibly releases leaked snapshots older than the configured
snapshot_timeoutto prevent unbounded memory growth.
- README.md - Current module documentation
- Header Documentation - Public API
- Replication Module - Distributed temporal support
- Storage Module - Temporal storage optimization
Last Updated: April 2026
Module Version: v1.6.0
Next Review: v1.7.0 Release