Databend 的系統(tǒng)表位于 query/storage 目錄下,當(dāng)然标沪,如果因為一些特殊的構(gòu)建原因無法放在這個位置的話忌卤,也可以考慮臨時放到 service/databases/system
這個目錄(不推薦)淳玩。
系統(tǒng)表的定義主要關(guān)注兩個內(nèi)容:一個是表的信息苫纤,會包含表名莫绣、Schema 這些未斑;另一個就是表中數(shù)據(jù)的生成/獲取伞辛。剛好可以對應(yīng)到 SyncSystemTable
和 AsyncSystemTable
這兩個 Trait 中的 get_table_info
和 get_full_data
西疤。到底是同步還是異步烦粒,取決于在獲取數(shù)據(jù)時,是否涉及到異步函數(shù)的調(diào)用代赁。
實現(xiàn)
本文將會以 credits
表的實現(xiàn)為例扰她,介紹 Databend 系統(tǒng)表的實現(xiàn),代碼位于 https://github.com/datafuselabs/databend/blob/main/src/query/storages/system/src/credits_table.rs 芭碍。credits
會返回 Databend 所用到的上游依賴的信息徒役,包括名字、版本和許可三個字段窖壕。
首先忧勿,需要參考其他系統(tǒng)表的實現(xiàn),去定義表對應(yīng)的結(jié)構(gòu)瞻讽,只需要保有表信息的字段就可以了鸳吸。
pub struct CreditsTable {
table_info: TableInfo,
}
接下來是為 CreditsTable
表實現(xiàn) create
方法,對應(yīng)的函數(shù)簽名如下:
pub fn create(table_id: u64) -> Arc<dyn Table>
傳入的 table_id
會在創(chuàng)建表時由 sys_db_meta.next_table_id()
生成卸夕。
schema
用于描述表的結(jié)構(gòu)层释,需要使用 TableSchemaRefExt
和 TableField
來創(chuàng)建,字段名字和類型取決于表中的數(shù)據(jù)快集。
let schema = TableSchemaRefExt::create(vec![
TableField::new("name", TableDataType::String),
TableField::new("version", TableDataType::String),
TableField::new("license", TableDataType::String),
]);
對于字符串類數(shù)據(jù)贡羔,可以使用 TableDataType::String
廉白,其他基礎(chǔ)類型也類似。但如果你需要允許字段中存在空值乖寒,比如字段是可以為空的 64 位無符號整數(shù)猴蹂,則可以使用 TableDataType::Nullable(Box::new(TableDataType::Number(NumberDataType::UInt64)))
的方式,TableDataType::Nullable
表示允許空值楣嘁,TableDataType::Number(NumberDataType::UInt64)
表征類型是 64 位無符號整數(shù)磅轻。
接下來就是定義表的信息,基本上只需要依葫蘆畫瓢逐虚,把描述聋溜、表名、元數(shù)據(jù)填上就好叭爱。
let table_info = TableInfo {
desc: "'system'.'credits'".to_string(),
name: "credits".to_string(),
ident: TableIdent::new(table_id, 0),
meta: TableMeta {
schema,
engine: "SystemCredits".to_string(),
..Default::default()
},
..Default::default()
};
SyncOneBlockSystemTable::create(CreditsTable { table_info })
對于同步類型的表往往使用 SyncOneBlockSystemTable
創(chuàng)建撮躁,異步類型的則使用 AsyncOneBlockSystemTable
。
接下來买雾,則是實現(xiàn) SyncSystemTable
把曼,SyncSystemTable
除了需要定義 NAME
之外,還需要實現(xiàn) 4 個函數(shù) get_table_info
漓穿、get_full_data
嗤军、get_partitions
和 truncate
,由于后兩個有默認(rèn)實現(xiàn)晃危,大多數(shù)時候不需要考慮實現(xiàn)自己的叙赚。(AsyncSystemTable
類似,只是沒有 truncate
)
NAME
的值遵循 system.<name>
的格式山害。
const NAME: &'static str = "system.credits";
get_table_info
只需要返回結(jié)構(gòu)體中的表信息纠俭。
fn get_table_info(&self) -> &TableInfo {
&self.table_info
}
get_full_data
是相對重要的部分,因為每個表的邏輯都不太一樣浪慌,credits
的三個字段基本類似冤荆,就只舉 license
字段為例。
let licenses: Vec<Vec<u8>> = env!("DATABEND_CREDITS_LICENSES")
.split_terminator(',')
.map(|x| x.trim().as_bytes().to_vec())
.collect();
license
字段的信息是從名為 DATABEND_CREDITS_LICENSES
的環(huán)境變量(參見 common-building
)獲取的权纤,每條數(shù)據(jù)都用 ,
進(jìn)行分隔钓简。
字符串類型的列最后是從 Vec<Vec<u8>>
轉(zhuǎn)化過來,其中字符串需要轉(zhuǎn)化為 Vec<u8>
汹想,所以在迭代的時候使用 .as_bytes().to_vec()
做了處理外邓。
在獲取所有數(shù)據(jù)后,就可以按 DataBlock
的形式返回表中的數(shù)據(jù)古掏。非空類型损话,使用 from_data
,可空類型使用 from_opt_data
。
Ok(DataBlock::new_from_columns(vec![
StringType::from_data(names),
StringType::from_data(versions),
StringType::from_data(licenses),
]))
最后丧枪,要想將其集成到 Databend 中光涂,還需要編輯 src/query/service/src/databases/system/system_database.rs
,將其注冊到 SystemDatabase
中 拧烦。
impl SystemDatabase {
pub fn create(sys_db_meta: &mut InMemoryMetas, config: &Config) -> Self {
...
CreditsTable::create(sys_db_meta.next_table_id()),
...
}
}
測試
系統(tǒng)表的相關(guān)測試位于 src/query/service/tests/it/storages/system.rs
忘闻。
對于內(nèi)容不會經(jīng)常動態(tài)變化的表,可以使用 Golden File 測試恋博,其運行邏輯是將對應(yīng)的表寫入指定的文件中齐佳,然后對比每次測試時文件內(nèi)容是否發(fā)生變化。
#[tokio::test(flavor = "multi_thread")]
async fn test_columns_table() -> Result<()> {
let (_guard, ctx) = crate::tests::create_query_context().await?;
let mut mint = Mint::new("tests/it/storages/testdata");
let file = &mut mint.new_goldenfile("columns_table.txt").unwrap();
let table = ColumnsTable::create(1);
run_table_tests(file, ctx, table).await?;
Ok(())
}
對于內(nèi)容可能會變化的表债沮,目前缺乏充分的測試手段炼吴。可以選擇測試其中模式相對固定的部分秦士,比如行和列的數(shù)目缺厉;也可以驗證輸出中是否包含特定的內(nèi)容永高。
#[tokio::test(flavor = "multi_thread")]
async fn test_metrics_table() -> Result<()> {
...
let result = stream.try_collect::<Vec<_>>().await?;
let block = &result[0];
assert_eq!(block.num_columns(), 4);
assert!(block.num_rows() >= 1);
let output = pretty_format_blocks(result.as_slice())?;
assert!(output.contains("test_test_metrics_table_count"));
#[cfg(feature = "enable_histogram")]
assert!(output.contains("test_test_metrics_table_histogram"));
Ok(())
}
關(guān)于 Databend
Databend 是一款開源隧土、彈性、低成本命爬,基于對象存儲也可以做實時分析的新式數(shù)倉曹傀。期待您的關(guān)注,一起探索云原生數(shù)倉解決方案饲宛,打造新一代開源 Data Cloud皆愉。
???? Databend Cloud:https://databend.cn
?? Databend 文檔:https://databend.rs/
?? Wechat:Databend