mirror of
https://github.com/GreptimeTeam/greptimedb.git
synced 2026-10-29 07:06:20 +00:00
feat(catalog): add public-global information schema scope (#9957)
Signed-off-by: shuiyisong <xixing.sys@gmail.com>
This commit is contained in:
@@ -139,7 +139,7 @@ lazy_static! {
|
||||
}
|
||||
|
||||
macro_rules! setup_memory_table {
|
||||
($name: expr) => {
|
||||
($name: expr, $scope: ident) => {
|
||||
paste! {
|
||||
{
|
||||
let (schema, columns) = get_schema_columns($name);
|
||||
@@ -147,7 +147,8 @@ macro_rules! setup_memory_table {
|
||||
consts::[<INFORMATION_SCHEMA_ $name _TABLE_ID>],
|
||||
$name,
|
||||
schema,
|
||||
columns
|
||||
columns,
|
||||
InformationSchemaTableScope::$scope
|
||||
)) as _)
|
||||
}
|
||||
}
|
||||
@@ -165,7 +166,9 @@ pub struct MakeInformationTableRequest {
|
||||
pub enum InformationSchemaTableScope {
|
||||
/// Every query is filtered to the catalog supplied when constructing the provider.
|
||||
Catalog,
|
||||
/// The table contains catalog-independent or shared data, including compatibility stubs.
|
||||
/// Catalog-independent data that is safe for authenticated non-admin users to read.
|
||||
PublicGlobal,
|
||||
/// Shared data without a non-admin visibility guarantee, including compatibility stubs.
|
||||
Global,
|
||||
}
|
||||
|
||||
@@ -173,6 +176,7 @@ impl InformationSchemaTableScope {
|
||||
pub const fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
Self::Catalog => "catalog",
|
||||
Self::PublicGlobal => "public_global",
|
||||
Self::Global => "global",
|
||||
}
|
||||
}
|
||||
@@ -228,30 +232,30 @@ impl InformationSchemaProvider {
|
||||
self.catalog_name.clone(),
|
||||
self.catalog_manager.clone(),
|
||||
)) as _),
|
||||
ENGINES => setup_memory_table!(ENGINES),
|
||||
COLUMN_PRIVILEGES => setup_memory_table!(COLUMN_PRIVILEGES),
|
||||
COLUMN_STATISTICS => setup_memory_table!(COLUMN_STATISTICS),
|
||||
BUILD_INFO => setup_memory_table!(BUILD_INFO),
|
||||
CHARACTER_SETS => setup_memory_table!(CHARACTER_SETS),
|
||||
COLLATIONS => setup_memory_table!(COLLATIONS),
|
||||
ENGINES => setup_memory_table!(ENGINES, PublicGlobal),
|
||||
COLUMN_PRIVILEGES => setup_memory_table!(COLUMN_PRIVILEGES, Global),
|
||||
COLUMN_STATISTICS => setup_memory_table!(COLUMN_STATISTICS, Global),
|
||||
BUILD_INFO => setup_memory_table!(BUILD_INFO, PublicGlobal),
|
||||
CHARACTER_SETS => setup_memory_table!(CHARACTER_SETS, PublicGlobal),
|
||||
COLLATIONS => setup_memory_table!(COLLATIONS, PublicGlobal),
|
||||
COLLATION_CHARACTER_SET_APPLICABILITY => {
|
||||
setup_memory_table!(COLLATION_CHARACTER_SET_APPLICABILITY)
|
||||
setup_memory_table!(COLLATION_CHARACTER_SET_APPLICABILITY, PublicGlobal)
|
||||
}
|
||||
CHECK_CONSTRAINTS => setup_memory_table!(CHECK_CONSTRAINTS),
|
||||
EVENTS => setup_memory_table!(EVENTS),
|
||||
FILES => setup_memory_table!(FILES),
|
||||
OPTIMIZER_TRACE => setup_memory_table!(OPTIMIZER_TRACE),
|
||||
PARAMETERS => setup_memory_table!(PARAMETERS),
|
||||
PROFILING => setup_memory_table!(PROFILING),
|
||||
REFERENTIAL_CONSTRAINTS => setup_memory_table!(REFERENTIAL_CONSTRAINTS),
|
||||
ROUTINES => setup_memory_table!(ROUTINES),
|
||||
SCHEMA_PRIVILEGES => setup_memory_table!(SCHEMA_PRIVILEGES),
|
||||
TABLE_PRIVILEGES => setup_memory_table!(TABLE_PRIVILEGES),
|
||||
GLOBAL_STATUS => setup_memory_table!(GLOBAL_STATUS),
|
||||
SESSION_STATUS => setup_memory_table!(SESSION_STATUS),
|
||||
PLUGINS => setup_memory_table!(PLUGINS),
|
||||
USER_PRIVILEGES => setup_memory_table!(USER_PRIVILEGES),
|
||||
PROCESSLIST => setup_memory_table!(PROCESSLIST),
|
||||
CHECK_CONSTRAINTS => setup_memory_table!(CHECK_CONSTRAINTS, Global),
|
||||
EVENTS => setup_memory_table!(EVENTS, Global),
|
||||
FILES => setup_memory_table!(FILES, Global),
|
||||
OPTIMIZER_TRACE => setup_memory_table!(OPTIMIZER_TRACE, Global),
|
||||
PARAMETERS => setup_memory_table!(PARAMETERS, Global),
|
||||
PROFILING => setup_memory_table!(PROFILING, Global),
|
||||
REFERENTIAL_CONSTRAINTS => setup_memory_table!(REFERENTIAL_CONSTRAINTS, Global),
|
||||
ROUTINES => setup_memory_table!(ROUTINES, Global),
|
||||
SCHEMA_PRIVILEGES => setup_memory_table!(SCHEMA_PRIVILEGES, Global),
|
||||
TABLE_PRIVILEGES => setup_memory_table!(TABLE_PRIVILEGES, Global),
|
||||
GLOBAL_STATUS => setup_memory_table!(GLOBAL_STATUS, Global),
|
||||
SESSION_STATUS => setup_memory_table!(SESSION_STATUS, Global),
|
||||
PLUGINS => setup_memory_table!(PLUGINS, Global),
|
||||
USER_PRIVILEGES => setup_memory_table!(USER_PRIVILEGES, Global),
|
||||
PROCESSLIST => setup_memory_table!(PROCESSLIST, Global),
|
||||
KEY_COLUMN_USAGE => Some(Arc::new(InformationSchemaKeyColumnUsage::new(
|
||||
self.catalog_name.clone(),
|
||||
self.catalog_manager.clone(),
|
||||
@@ -374,7 +378,6 @@ impl InformationSchemaProvider {
|
||||
// Only put these tables under `greptime` catalog to prevent info leak.
|
||||
if self.catalog_name == DEFAULT_CATALOG_NAME {
|
||||
table_names.extend([
|
||||
BUILD_INFO,
|
||||
REGION_PEERS,
|
||||
CLUSTER_INFO,
|
||||
PROCEDURE_INFO,
|
||||
@@ -387,6 +390,7 @@ impl InformationSchemaProvider {
|
||||
}
|
||||
|
||||
table_names.extend([
|
||||
BUILD_INFO,
|
||||
TABLES,
|
||||
VIEWS,
|
||||
SCHEMATA,
|
||||
@@ -605,14 +609,15 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn every_registered_information_schema_table_has_scope() {
|
||||
use InformationSchemaTableScope::{Catalog, Global};
|
||||
use InformationSchemaTableScope::{Catalog, Global, PublicGlobal};
|
||||
|
||||
let manager: Arc<dyn CatalogManager> = MemoryCatalogManager::new();
|
||||
let backend = Arc::new(MemoryKvBackend::default());
|
||||
let factories: HashMap<_, InformationSchemaTableFactoryRef> = [
|
||||
("future_catalog_table", Catalog),
|
||||
("future_global_table", Global),
|
||||
(ENGINES, Catalog),
|
||||
("future_public_table", PublicGlobal),
|
||||
(BUILD_INFO, Global),
|
||||
]
|
||||
.into_iter()
|
||||
.map(|(name, scope)| {
|
||||
@@ -638,7 +643,13 @@ mod tests {
|
||||
#[cfg(feature = "enterprise")]
|
||||
RECYCLE_BIN,
|
||||
"future_catalog_table",
|
||||
];
|
||||
let public_global_tables = [
|
||||
ENGINES,
|
||||
CHARACTER_SETS,
|
||||
COLLATIONS,
|
||||
COLLATION_CHARACTER_SET_APPLICABILITY,
|
||||
"future_public_table",
|
||||
];
|
||||
|
||||
for (catalog, with_process_manager) in [
|
||||
@@ -654,31 +665,42 @@ mod tests {
|
||||
with_process_manager.then(|| Arc::new(ProcessManager::new(String::new(), None))),
|
||||
backend.clone(),
|
||||
);
|
||||
assert_eq!(
|
||||
provider
|
||||
.table(ENGINES)
|
||||
.unwrap()
|
||||
.table_info()
|
||||
.meta
|
||||
.options
|
||||
.extra_options
|
||||
.get(SEMANTIC_TABLE_SCOPE)
|
||||
.map(String::as_str),
|
||||
Some(Global.as_str()),
|
||||
);
|
||||
for name in [
|
||||
BUILD_INFO,
|
||||
ENGINES,
|
||||
CHARACTER_SETS,
|
||||
COLLATIONS,
|
||||
COLLATION_CHARACTER_SET_APPLICABILITY,
|
||||
] {
|
||||
assert_eq!(
|
||||
provider
|
||||
.table(name)
|
||||
.unwrap()
|
||||
.table_info()
|
||||
.meta
|
||||
.options
|
||||
.extra_options
|
||||
.get(SEMANTIC_TABLE_SCOPE)
|
||||
.map(String::as_str),
|
||||
Some(PublicGlobal.as_str()),
|
||||
"{catalog}.{name}"
|
||||
);
|
||||
}
|
||||
// An extension replacing a built-in supplies the replacement table's scope.
|
||||
let provider = provider.with_extra_table_factories(factories.clone());
|
||||
let expected_count =
|
||||
43 + usize::from(with_process_manager) + usize::from(cfg!(feature = "enterprise"))
|
||||
44 + usize::from(with_process_manager) + usize::from(cfg!(feature = "enterprise"))
|
||||
- if catalog == DEFAULT_CATALOG_NAME {
|
||||
0
|
||||
} else {
|
||||
9
|
||||
8
|
||||
};
|
||||
assert_eq!(provider.tables().len(), expected_count);
|
||||
for (name, table) in provider.tables() {
|
||||
let scope = if catalog_tables.contains(&name.as_str()) {
|
||||
Catalog
|
||||
} else if public_global_tables.contains(&name.as_str()) {
|
||||
PublicGlobal
|
||||
} else {
|
||||
Global
|
||||
};
|
||||
|
||||
@@ -40,6 +40,7 @@ pub(crate) struct MemoryTable {
|
||||
pub(crate) table_name: &'static str,
|
||||
pub(crate) schema: SchemaRef,
|
||||
pub(crate) columns: Vec<VectorRef>,
|
||||
scope: InformationSchemaTableScope,
|
||||
}
|
||||
|
||||
impl MemoryTable {
|
||||
@@ -49,12 +50,14 @@ impl MemoryTable {
|
||||
table_name: &'static str,
|
||||
schema: SchemaRef,
|
||||
columns: Vec<VectorRef>,
|
||||
scope: InformationSchemaTableScope,
|
||||
) -> Self {
|
||||
Self {
|
||||
table_id,
|
||||
table_name,
|
||||
schema,
|
||||
columns,
|
||||
scope,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,7 +110,7 @@ impl DfPartitionStream for MemoryTable {
|
||||
|
||||
impl InformationTable for MemoryTable {
|
||||
fn scope(&self) -> InformationSchemaTableScope {
|
||||
InformationSchemaTableScope::Global
|
||||
self.scope
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,6 +175,7 @@ mod tests {
|
||||
Arc::new(StringVector::from(vec!["a1", "a2"])),
|
||||
Arc::new(StringVector::from(vec!["b1", "b2"])),
|
||||
],
|
||||
InformationSchemaTableScope::Global,
|
||||
);
|
||||
|
||||
assert_eq!(42, table.table_id());
|
||||
@@ -201,7 +205,13 @@ mod tests {
|
||||
ColumnSchema::new("b", ConcreteDataType::string_datatype(), false),
|
||||
]));
|
||||
|
||||
let table = MemoryTable::new(42, "test", schema.clone(), vec![]);
|
||||
let table = MemoryTable::new(
|
||||
42,
|
||||
"test",
|
||||
schema.clone(),
|
||||
vec![],
|
||||
InformationSchemaTableScope::Global,
|
||||
);
|
||||
|
||||
assert_eq!(42, table.table_id());
|
||||
assert_eq!("test", table.table_name());
|
||||
|
||||
@@ -50,8 +50,9 @@ pub const SEMANTIC_PER_TABLE_INDEX_KEY: &str = "greptime.internal.semantic.per_t
|
||||
// ---- Common keys (all signals) ----
|
||||
|
||||
/// Information-schema visibility: `catalog` for providers that filter rows to
|
||||
/// their catalog, or `global` for catalog-independent data. Only registered
|
||||
/// system-table metadata is trusted for authorization.
|
||||
/// their catalog, `public_global` for shared data safe for non-admin reads, or
|
||||
/// `global` for shared data without that guarantee. Only registered system-table
|
||||
/// metadata is trusted for authorization.
|
||||
pub const SEMANTIC_TABLE_SCOPE: &str = "greptime.semantic.table_scope";
|
||||
|
||||
/// Signal kind: one of [`SIGNAL_TYPE_TRACE`] / [`SIGNAL_TYPE_LOG`] /
|
||||
@@ -288,7 +289,7 @@ pub fn validate_semantic_option(key: &str, value: &str) -> bool {
|
||||
return !value.is_empty() && value.split(',').all(|column| !column.trim().is_empty());
|
||||
}
|
||||
match key {
|
||||
SEMANTIC_TABLE_SCOPE => matches!(value, "catalog" | "global"),
|
||||
SEMANTIC_TABLE_SCOPE => matches!(value, "catalog" | "public_global" | "global"),
|
||||
SEMANTIC_PIPELINE
|
||||
| SEMANTIC_SOURCE_VERSION
|
||||
| SEMANTIC_METRIC_UNIT
|
||||
@@ -412,6 +413,10 @@ mod tests {
|
||||
fn test_validate_semantic_option() {
|
||||
// Enum keys reject out-of-domain values.
|
||||
assert!(validate_semantic_option(SEMANTIC_TABLE_SCOPE, "catalog"));
|
||||
assert!(validate_semantic_option(
|
||||
SEMANTIC_TABLE_SCOPE,
|
||||
"public_global"
|
||||
));
|
||||
assert!(validate_semantic_option(SEMANTIC_TABLE_SCOPE, "global"));
|
||||
assert!(!validate_semantic_option(SEMANTIC_TABLE_SCOPE, "cluster"));
|
||||
assert!(!validate_semantic_option(SEMANTIC_TABLE_SCOPE, "unknown"));
|
||||
|
||||
Reference in New Issue
Block a user