feat(catalog): add public-global information schema scope (#9957)

Signed-off-by: shuiyisong <xixing.sys@gmail.com>
This commit is contained in:
shuiyisong
2026-10-26 07:23:30 +00:00
committed by GitHub
parent e87573e286
commit 513b9cee58
8 changed files with 117 additions and 72 deletions
@@ -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
};
+12 -2
View File
@@ -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());
+8 -3
View File
@@ -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"));