// Table schema registry. A table entry is either materialized in memory or
// backed by a lazy scan source (see source.mbt); either way the schema is
// available without touching the data, which is all the binder needs.
///|
/// One column of a table schema: its name and columnar type.
pub(all) struct Column {
name : String
dtype : @types.DataType
} derive(Eq, Debug)
///|
pub extend Column with Eq::{equal, not_equal}
///|
pub extend Column with @debug.Debug::{to_repr}
///|
///|
/// A table's schema: its name and ordered columns. Field names are the
/// user-facing SQL identifiers.
pub(all) struct TableSchema {
table : String
columns : Array[Column]
} derive(Eq, Debug)
///|
pub extend TableSchema with Eq::{equal, not_equal}
///|
pub extend TableSchema with @debug.Debug::{to_repr}
///|
pub fn TableSchema::index_of(self : TableSchema, name : String) -> Int32? {
for i, col in self.columns {
if col.name == name {
return Some(i)
}
}
None
}
///|
/// A registered table: its schema, and either materialized batches or a lazy
/// source. Exactly one of `batches` / `source` carries data — a lazy entry
/// keeps `batches` empty and answers `scan` from `source`.
pub(all) struct TableEntry {
schema : TableSchema
batches : Array[@types.DataBatch]
source : &ScanSource?
}
///|
/// A table already in memory (tests, derived scratch tables, the spill bench).
pub fn TableEntry::new(
schema : TableSchema,
batches : Array[@types.DataBatch],
) -> TableEntry {
{ schema, batches, source: None, }
}
///|
/// A table that scans its data on demand. Registering it reads no rows.
pub fn TableEntry::from_source(
schema : TableSchema,
source : &ScanSource,
) -> TableEntry {
{ schema, batches: [], source: Some(source), }
}
///|
/// Whether this entry streams from a source instead of holding its batches.
pub fn TableEntry::is_lazy(self : TableEntry) -> Bool {
self.source is Some(_)
}
///|
/// Produce the batches one query needs: only `request.projection` columns,
/// in that order, from the materialized batches or from a fresh stream.
/// Predicates are advisory (see ScanPredicate); the caller still filters.
pub fn TableEntry::scan(
self : TableEntry,
request : ScanRequest,
) -> Array[@types.DataBatch] raise @types.SqlError {
match self.source {
Some(src) => drain_stream(src.open(request))
None => project_batches(self.batches, request.projection)
}
}
///|
/// Planning information for EXPLAIN: row groups statistics eliminated.
/// Materialized entries have no plan to report.
pub fn TableEntry::plan(
self : TableEntry,
request : ScanRequest,
) -> ScanPlan raise @types.SqlError {
match self.source {
Some(src) => src.plan(request)
None =>
if self.batches.length() == 0 {
ScanPlan::none()
} else {
let mut rows = 0L
for b in self.batches {
rows += b.row_count().to_int64()
}
{ files: 1, row_groups: 0, row_groups_skipped: 0, rows: Some(rows), }
}
}
}
///|
/// The registry `execute` reads from: map table name to its entry.
/// Plain values everywhere — build one per execution (or share one
/// across threads that never mutate it).
pub struct Catalog {
tables : Map[String, TableEntry]
}
///|
/// An empty catalog; register scanned tables into it.
pub fn Catalog::new() -> Catalog {
{ tables: {}, }
}
///|
/// Register (or replace) a table entry.
pub fn Catalog::register(self : Catalog, entry : TableEntry) -> Unit {
self.tables[entry.schema.table] = entry
}
///|
pub fn Catalog::lookup(self : Catalog, name : String) -> TableEntry? {
self.tables.get(name)
}