Merge upstream/main into narthex (pre-merge rehearsal, not for push)

Rehearsal merge of main (f8d8fe1071) into narthex (115d85b669) on a throwaway
local branch. 31 semantic conflicts resolved; workspace and all test targets
compile; promql 1215/1215 and mito2 series_index 185/185 pass.

Pre-merge pins and follow-ups are marked with TODO(narthex-merge):
- greptime-proto is temporarily pinned to the fork branch carrying
  GreptimeTeam/greptime-proto#355; replace with the upstream rev once it lands.
- native payload byte cache is disabled (main removed PageRangeCache); two
  samples tests are #[ignore]d until it gets a dedicated page-cache budget.
- columnar metadata now has its own CacheManager tier (a quarter of
  sst_meta_cache_size); the unified decoded tier was not kept.
- query.experimental_native_promql is registered as a query option, otherwise
  the native route is silently rejected by the option snapshot.

Signed-off-by: discord9 <discord9@163.com>
This commit is contained in:
discord9
2026-11-12 15:45:04 +08:00
1082 changed files with 152093 additions and 17153 deletions
+34
View File
@@ -32,3 +32,37 @@ impl ObjectNamePartExt for ObjectNamePart {
ident.value.clone()
}
}
/// Formats an object name by joining its identifier values without quotes.
pub fn object_name_to_string_unquoted(name: &ObjectName) -> String {
name.0
.iter()
.map(ObjectNamePartExt::to_string_unquoted)
.collect::<Vec<_>>()
.join(".")
}
#[cfg(test)]
mod tests {
use sqlparser::ast::{Ident, ObjectName, ObjectNamePart};
use super::object_name_to_string_unquoted;
#[test]
fn test_object_name_to_string_unquoted() {
let quoted = ObjectName(vec![ObjectNamePart::Identifier(Ident::with_quote(
'"',
"query.parallelism",
))]);
let unquoted = ObjectName(vec![
ObjectNamePart::Identifier(Ident::new("query")),
ObjectNamePart::Identifier(Ident::new("parallelism")),
]);
assert_eq!("query.parallelism", object_name_to_string_unquoted(&quoted));
assert_eq!(
object_name_to_string_unquoted(&unquoted),
object_name_to_string_unquoted(&quoted)
);
}
}
+13 -2
View File
@@ -28,8 +28,8 @@ pub(crate) use json::parse_json2_type_and_options;
pub use json::parse_json2_type_hint_path;
use snafu::{OptionExt, ResultExt, ensure};
use sqlparser::ast::{
ColumnOption, ColumnOptionDef, DataType, Expr, KeyOrIndexDisplay, NullsDistinctOption,
PrimaryKeyConstraint, UniqueConstraint,
ColumnOption, ColumnOptionDef, DataType, Expr, FunctionArg, FunctionArgExpr, FunctionArguments,
KeyOrIndexDisplay, NullsDistinctOption, PrimaryKeyConstraint, UniqueConstraint,
};
use sqlparser::dialect::keywords::Keyword;
use sqlparser::keywords::ALL_KEYWORDS;
@@ -1300,6 +1300,17 @@ fn ensure_one_expr(expr: &Expr, columns: &[&Column]) -> Result<()> {
);
Ok(())
}
// The function and its argument shape are validated when the rule is converted.
Expr::Function(function) => {
if let FunctionArguments::List(args) = &function.args {
for arg in &args.args {
if let FunctionArg::Unnamed(FunctionArgExpr::Expr(expr)) = arg {
ensure_one_expr(expr, columns)?;
}
}
}
Ok(())
}
Expr::Value(_) => Ok(()),
Expr::UnaryOp { expr, .. } => {
ensure_one_expr(expr, columns)?;
+196 -3
View File
@@ -14,6 +14,7 @@
use datatypes::extension::json::JSON2_REMAINDER_FIELD_NAME;
use datatypes::json::JSON2_MAX_STRUCTURED_DEPTH;
use datatypes::schema::FulltextOptions;
use snafu::{ResultExt, ensure};
use sqlparser::ast::{DataType, ExactNumberInfo, ObjectName};
use sqlparser::dialect::keywords::Keyword;
@@ -24,6 +25,7 @@ use crate::ast::Ident;
use crate::dialect::GreptimeDbDialect;
use crate::error::{InvalidSqlSnafu, Result, SyntaxSnafu};
use crate::parsers::create_parser::{INVERTED, SKIPPING};
use crate::statements::OptionMap;
use crate::statements::create::{Json2Options, JsonTypeHint};
const JSON2_TYPE_NAME: &str = "JSON2";
@@ -157,6 +159,8 @@ fn parse_json2_type_hint(parser: &mut Parser<'_>) -> Result<JsonTypeHint> {
let data_type = validate_json2_type_hint_type(data_type)?;
let mut inverted_index = false;
let mut skipping_index = None;
let mut fulltext_index = None;
loop {
if parser.parse_keywords(&[Keyword::NOT, Keyword::NULL])
@@ -194,14 +198,90 @@ fn parse_json2_type_hint(parser: &mut Parser<'_>) -> Result<JsonTypeHint> {
)
}
);
ensure!(
!path
.iter()
.any(|segment| segment == JSON2_REMAINDER_FIELD_NAME),
InvalidSqlSnafu {
msg: format!(
"JSON2 indexed type hint path cannot contain reserved field '{JSON2_REMAINDER_FIELD_NAME}'"
)
}
);
inverted_index = true;
} else if let Token::Word(word) = parser.peek_token().token
&& word.value.eq_ignore_ascii_case(SKIPPING)
{
return InvalidSqlSnafu {
msg: "JSON2 type hint SKIPPING INDEX is not supported yet".to_string(),
parser.next_token();
ensure!(
parser.parse_keyword(Keyword::INDEX),
InvalidSqlSnafu {
msg: "expect INDEX after SKIPPING keyword",
}
);
ensure!(
skipping_index.is_none(),
InvalidSqlSnafu {
msg: "duplicated SKIPPING INDEX option for JSON2 type hint",
}
);
let options = parser
.parse_options(Keyword::WITH)
.context(SyntaxSnafu)?
.into_iter()
.map(crate::util::parse_option_string)
.collect::<Result<Vec<_>>>()?;
for (key, _) in &options {
ensure!(
crate::parsers::utils::validate_column_skipping_index_create_option(key),
InvalidSqlSnafu {
msg: format!("invalid SKIPPING INDEX option: {key}"),
}
);
}
.fail();
skipping_index = Some(
datatypes::schema::SkippingIndexOptions::try_from(
OptionMap::new(options).into_map(),
)
.context(crate::error::SetSkippingIndexOptionSnafu)?,
);
} else if parser.parse_keyword(Keyword::FULLTEXT) {
ensure!(
matches!(data_type, DataType::String(None)),
InvalidSqlSnafu {
msg: "JSON2 FULLTEXT index only supports string type hints",
}
);
ensure!(
parser.parse_keyword(Keyword::INDEX),
InvalidSqlSnafu {
msg: "expect INDEX after FULLTEXT keyword",
}
);
ensure!(
fulltext_index.is_none(),
InvalidSqlSnafu {
msg: "duplicated FULLTEXT INDEX option for JSON2 type hint",
}
);
let options = parser
.parse_options(Keyword::WITH)
.context(SyntaxSnafu)?
.into_iter()
.map(crate::util::parse_option_string)
.collect::<Result<Vec<_>>>()?;
for (key, _) in &options {
ensure!(
crate::parsers::utils::validate_column_fulltext_create_option(key),
InvalidSqlSnafu {
msg: format!("invalid FULLTEXT INDEX option: {key}")
}
);
}
fulltext_index = Some(
FulltextOptions::try_from(OptionMap::new(options).into_map())
.context(crate::error::SetFulltextOptionSnafu)?,
);
} else if matches!(parser.peek_token().token, Token::Comma | Token::RParen) {
break;
} else {
@@ -215,6 +295,8 @@ fn parse_json2_type_hint(parser: &mut Parser<'_>) -> Result<JsonTypeHint> {
path,
data_type,
inverted_index,
skipping_index,
fulltext_index,
})
}
@@ -300,6 +382,117 @@ mod tests {
create_table.columns.remove(0)
}
#[test]
fn test_json2_fulltext_index() {
use datatypes::schema::FulltextBackend;
for (options, backend) in [
("", FulltextBackend::Bloom),
(
"WITH (backend='tantivy', analyzer='Chinese', case_sensitive='true')",
FulltextBackend::Tantivy,
),
(
"WITH (backend='bloom', granularity='8', false_positive_rate='0.05')",
FulltextBackend::Bloom,
),
] {
let sql = format!(
"CREATE TABLE logs (attrs JSON2 (message STRING FULLTEXT INDEX {options} SKIPPING INDEX INVERTED INDEX), ts TIMESTAMP TIME INDEX)"
);
let column = parse_json2_column(&sql);
let options = column.extensions.json2_options.unwrap();
let settings = options.build_json_settings().unwrap();
let hint = &settings.type_hints()[0];
assert_eq!(hint.fulltext_index.as_ref().unwrap().backend, backend);
assert!(hint.fulltext_index.as_ref().unwrap().enable);
assert!(hint.inverted_index);
assert!(hint.skipping_index.is_some());
let rebuilt = parse_json2_column(&format!(
"CREATE TABLE logs (attrs JSON2 {options}, ts TIMESTAMP TIME INDEX)"
));
assert_eq!(
settings,
rebuilt
.extensions
.json2_options
.unwrap()
.build_json_settings()
.unwrap()
);
}
for hint in [
"message BIGINT FULLTEXT INDEX",
"message STRING FULLTEXT INDEX FULLTEXT INDEX",
"message STRING FULLTEXT",
"message STRING FULLTEXT INDEX WITH (analyzer='unknown')",
"message STRING FULLTEXT INDEX WITH (backend='unknown')",
"message STRING FULLTEXT INDEX WITH (granularity='0')",
"message STRING FULLTEXT INDEX WITH (false_positive_rate='0')",
"message STRING FULLTEXT INDEX WITH (unknown='1')",
] {
let sql = format!("CREATE TABLE logs (attrs JSON2 ({hint}), ts TIMESTAMP TIME INDEX)");
let parsed = ParserContext::create_with_dialect(
&sql,
&GreptimeDbDialect {},
ParseOptions::default(),
);
assert!(parsed.is_err(), "{sql}");
}
}
#[test]
fn test_json2_skipping_index() {
let column = parse_json2_column(
"CREATE TABLE logs (attrs JSON2 (service.name STRING SKIPPING INDEX WITH (granularity='8', false_positive_rate='0.05') INVERTED INDEX, status BIGINT SKIPPING INDEX), ts TIMESTAMP TIME INDEX)",
);
let options = column.extensions.json2_options.as_ref().unwrap();
let settings = options.build_json_settings().unwrap();
let hint = &settings.type_hints()[0];
assert!(hint.inverted_index);
assert_eq!(hint.skipping_index.as_ref().unwrap().granularity, 8);
assert_eq!(
hint.skipping_index.as_ref().unwrap().false_positive_rate(),
0.05
);
assert_eq!(
settings.type_hints()[1].skipping_index,
Some(datatypes::schema::SkippingIndexOptions::default())
);
let rebuilt = parse_json2_column(&format!(
"CREATE TABLE logs (attrs JSON2 {options}, ts TIMESTAMP TIME INDEX)"
));
assert_eq!(
settings,
rebuilt
.extensions
.json2_options
.unwrap()
.build_json_settings()
.unwrap()
);
for option in [
"SKIPPING INDEX SKIPPING INDEX",
"SKIPPING",
"SKIPPING INDEX WITH (granularity='0')",
"SKIPPING INDEX WITH (false_positive_rate='0')",
"SKIPPING INDEX WITH (type='unknown')",
"SKIPPING INDEX WITH (unknown='1')",
] {
let sql = format!(
"CREATE TABLE logs (attrs JSON2 (a STRING {option}), ts TIMESTAMP TIME INDEX)"
);
assert!(
ParserContext::create_with_dialect(
&sql,
&GreptimeDbDialect {},
ParseOptions::default()
)
.is_err(),
"{sql}"
);
}
}
#[test]
fn test_parse_json2_type_hint_path() {
assert_eq!(
+74 -9
View File
@@ -18,10 +18,11 @@ use sqlparser::tokenizer::Token;
use crate::error::{self, InvalidSqlSnafu, InvalidTableNameSnafu, Result, UnexpectedTokenSnafu};
use crate::parser::ParserContext;
use crate::parsers::utils;
use crate::statements::statement::Statement;
use crate::statements::truncate::TruncateTable;
/// `TRUNCATE [TABLE] table_name;`
/// `TRUNCATE [TABLE] table_name [FILE RANGE ...] [WITH (wait = ..., timeout = ...)];`
impl ParserContext<'_> {
pub(crate) fn parse_truncate(&mut self) -> Result<Statement> {
let _ = self.parser.next_token();
@@ -46,7 +47,10 @@ impl ParserContext<'_> {
// if no range is specified, we just truncate the table
if !have_range {
return Ok(Statement::TruncateTable(TruncateTable::new(table_ident)));
let options = utils::parse_ddl_options(&mut self.parser)?;
return Ok(Statement::TruncateTable(
TruncateTable::new(table_ident).with_options(options),
));
}
// parse a list of time ranges consist of (Timestamp, Timestamp),?
@@ -100,6 +104,7 @@ impl ParserContext<'_> {
let peek = self.parser.peek_token().token;
match peek {
Token::Word(word) if word.keyword == Keyword::WITH => break,
sqlparser::tokenizer::Token::EOF | Token::SemiColon => {
if time_ranges.is_empty() {
return Err(InvalidSqlSnafu {
@@ -112,24 +117,26 @@ impl ParserContext<'_> {
Token::Comma => {
self.parser.next_token(); // Consume the comma
let next_peek = self.parser.peek_token().token; // Peek the token after the comma
if matches!(next_peek, Token::EOF | Token::SemiColon) {
if matches!(next_peek, Token::EOF | Token::SemiColon)
|| matches!(next_peek, Token::Word(word) if word.keyword == Keyword::WITH)
{
break; // Trailing comma, end of statement
}
// Otherwise, continue to parse next range
continue;
}
_ => UnexpectedTokenSnafu {
expected: "a comma or end of statement",
expected: "a comma, WITH or end of statement",
actual: self.peek_token_as_string(),
}
.fail()?,
}
}
Ok(Statement::TruncateTable(TruncateTable::new_with_ranges(
table_ident,
time_ranges,
)))
let options = utils::parse_ddl_options(&mut self.parser)?;
Ok(Statement::TruncateTable(
TruncateTable::new_with_ranges(table_ident, time_ranges).with_options(options),
))
}
}
@@ -141,6 +148,64 @@ mod tests {
use crate::dialect::GreptimeDbDialect;
use crate::parser::ParseOptions;
#[test]
fn test_parse_truncate_ddl_options() {
for prefix in [
"TRUNCATE foo",
"TRUNCATE TABLE foo",
"TRUNCATE TABLE foo FILE RANGE (0, 20)",
"TRUNCATE TABLE foo FILE RANGE (0, 20), (30, 40),",
] {
let sql = format!("{prefix} WITH (WAIT = false, TIMEOUT = '30s');");
let statements = ParserContext::create_with_dialect(
&sql,
&GreptimeDbDialect {},
ParseOptions::default(),
)
.unwrap();
let Statement::TruncateTable(truncate) = &statements[0] else {
unreachable!()
};
assert_eq!(truncate.options().get("wait"), Some("false"));
assert_eq!(truncate.options().get("timeout"), Some("30s"));
let formatted = truncate.to_string();
let reparsed = ParserContext::create_with_dialect(
&formatted,
&GreptimeDbDialect {},
ParseOptions::default(),
)
.unwrap();
let Statement::TruncateTable(reparsed) = &reparsed[0] else {
unreachable!()
};
assert_eq!(truncate.table_name(), reparsed.table_name());
assert_eq!(truncate.time_ranges(), reparsed.time_ranges());
assert_eq!(
truncate.options().to_str_map(),
reparsed.options().to_str_map()
);
}
for options in [
"ttl = '1d'",
"unknown = true",
"wait = NULL",
"timeout = ['30s']",
] {
let sql = format!("TRUNCATE TABLE foo WITH ({options})");
assert!(
ParserContext::create_with_dialect(
&sql,
&GreptimeDbDialect {},
ParseOptions::default(),
)
.is_err(),
"{sql}"
);
}
}
#[test]
pub fn test_parse_truncate_with_ranges() {
let sql = r#"TRUNCATE foo FILE RANGE (0, 20)"#;
@@ -451,7 +516,7 @@ mod tests {
assert!(
result.is_err()
&& format!("{result:?}")
.contains("expected: 'a comma or end of statement', found: ("),
.contains("expected: 'a comma, WITH or end of statement', found: ("),
"result is: {result:?}"
);
+28 -8
View File
@@ -28,18 +28,20 @@ use datafusion_expr::{AggregateUDF, Expr, HigherOrderUDF, ScalarUDF, TableSource
use datafusion_sql::planner::{ContextProvider, SqlToRel};
use datatypes::arrow::datatypes::DataType;
use datatypes::schema::{
COLUMN_COMPRESSION_OPT_KEY_LEVEL, COLUMN_COMPRESSION_OPT_KEY_PAGE_SIZE,
COLUMN_COMPRESSION_OPT_KEY_TYPE, COLUMN_FULLTEXT_OPT_KEY_ANALYZER,
COLUMN_FULLTEXT_OPT_KEY_BACKEND, COLUMN_FULLTEXT_OPT_KEY_CASE_SENSITIVE,
COLUMN_FULLTEXT_OPT_KEY_FALSE_POSITIVE_RATE, COLUMN_FULLTEXT_OPT_KEY_GRANULARITY,
COLUMN_SKIPPING_INDEX_OPT_KEY_FALSE_POSITIVE_RATE, COLUMN_SKIPPING_INDEX_OPT_KEY_GRANULARITY,
COLUMN_SKIPPING_INDEX_OPT_KEY_TYPE,
COLUMN_COMPRESSION_OPT_KEY_ENCODING, COLUMN_COMPRESSION_OPT_KEY_LEVEL,
COLUMN_COMPRESSION_OPT_KEY_PAGE_SIZE, COLUMN_COMPRESSION_OPT_KEY_TYPE,
COLUMN_FULLTEXT_OPT_KEY_ANALYZER, COLUMN_FULLTEXT_OPT_KEY_BACKEND,
COLUMN_FULLTEXT_OPT_KEY_CASE_SENSITIVE, COLUMN_FULLTEXT_OPT_KEY_FALSE_POSITIVE_RATE,
COLUMN_FULLTEXT_OPT_KEY_GRANULARITY, COLUMN_SKIPPING_INDEX_OPT_KEY_FALSE_POSITIVE_RATE,
COLUMN_SKIPPING_INDEX_OPT_KEY_GRANULARITY, COLUMN_SKIPPING_INDEX_OPT_KEY_TYPE,
};
use snafu::{ResultExt, ensure};
use sqlparser::dialect::Dialect;
use sqlparser::keywords::Keyword;
use sqlparser::parser::Parser;
use table::requests::{SEMANTIC_PREFIX, validate_semantic_option, validate_table_option};
use table::requests::{
SEMANTIC_PREFIX, VALID_DDL_OPTION_KEYS, validate_semantic_option, validate_table_option,
};
use crate::error::{
ConvertToLogicalExpressionSnafu, InvalidSqlSnafu, InvalidTableOptionSnafu, ParseSqlValueSnafu,
@@ -382,6 +384,7 @@ pub fn validate_column_compression_create_option(key: &str) -> bool {
COLUMN_COMPRESSION_OPT_KEY_TYPE,
COLUMN_COMPRESSION_OPT_KEY_LEVEL,
COLUMN_COMPRESSION_OPT_KEY_PAGE_SIZE,
COLUMN_COMPRESSION_OPT_KEY_ENCODING,
]
.contains(&key)
}
@@ -422,15 +425,32 @@ pub fn convert_month_day_nano_to_duration(
Ok(std::time::Duration::new(adjusted_seconds, nanos_remainder))
}
/// Parses submission options, accepting only scalar `wait` and `timeout` values.
pub(crate) fn parse_ddl_options(parser: &mut Parser) -> Result<OptionMap> {
parse_with_options_inner(parser, true)
}
pub fn parse_with_options(parser: &mut Parser) -> Result<OptionMap> {
parse_with_options_inner(parser, false)
}
fn parse_with_options_inner(parser: &mut Parser, ddl_only: bool) -> Result<OptionMap> {
let options = parser
.parse_options(Keyword::WITH)
.context(SyntaxSnafu)?
.into_iter()
.map(parse_option_string)
.collect::<Result<HashMap<String, OptionValue>>>()?;
// Validate before OptionMap can discard non-scalar secret options.
for (key, value) in &options {
if key.starts_with(SEMANTIC_PREFIX) {
if ddl_only {
ensure!(
VALID_DDL_OPTION_KEYS.contains(&key.as_str()) && value.as_string().is_some(),
InvalidSqlSnafu {
msg: format!("invalid DDL option '{key}': '{value}'")
}
);
} else if key.starts_with(SEMANTIC_PREFIX) {
// Semantic keys are whitelisted and value-checked against their domain,
// so a user cannot set an unknown key or an out-of-range value.
let value = value.as_string().unwrap_or_default();
+2
View File
@@ -733,6 +733,8 @@ mod tests {
path: vec!["kind".to_string()],
data_type: ConcreteDataType::string_datatype(),
inverted_index: false,
skipping_index: None,
fulltext_index: None,
}],
None,
)?)?;
+60 -2
View File
@@ -14,6 +14,7 @@
use std::collections::HashMap;
use std::fmt::{Display, Formatter, Write};
use std::ops::ControlFlow;
use common_catalog::consts::FILE_ENGINE;
use datatypes::json::{JSON2_DEFAULT_MAX_AUTO_EXPANDED_PATHS, JsonSettings};
@@ -22,7 +23,7 @@ use datatypes::schema::{CompressionOptions, FulltextOptions, SkippingIndexOption
use itertools::Itertools;
use serde::Serialize;
use snafu::ResultExt;
use sqlparser::ast::{ColumnOptionDef, DataType, Expr};
use sqlparser::ast::{ColumnOptionDef, DataType, Expr, visit_expressions_mut};
use sqlparser_derive::{Visit, VisitMut};
use crate::ast::{ColumnDef, Ident, ObjectName};
@@ -200,6 +201,33 @@ impl Display for Json2Options {
}
f.write_char(' ')?;
write_data_type(f, &hint.data_type)?;
if let Some(options) = &hint.skipping_index {
write!(
f,
" SKIPPING INDEX WITH (granularity = '{}', false_positive_rate = '{}', type = '{}')",
options.granularity,
options.false_positive_rate(),
options.index_type
)?;
}
if let Some(options) = &hint.fulltext_index
&& options.enable
{
write!(
f,
" FULLTEXT INDEX WITH (analyzer = '{}', case_sensitive = '{}', backend = '{}'",
options.analyzer, options.case_sensitive, options.backend
)?;
if options.backend == datatypes::schema::FulltextBackend::Bloom {
write!(
f,
", granularity = '{}', false_positive_rate = '{}'",
options.granularity,
options.false_positive_rate()
)?;
}
f.write_char(')')?;
}
if hint.inverted_index {
f.write_str(" INVERTED INDEX")?;
}
@@ -218,6 +246,8 @@ impl Json2Options {
path: hint.path.clone(),
data_type: sql_data_type_to_concrete_data_type(&hint.data_type)?,
inverted_index: hint.inverted_index,
skipping_index: hint.skipping_index.clone(),
fulltext_index: hint.fulltext_index.clone(),
})
})
.collect::<Result<Vec<_>>>()?;
@@ -233,6 +263,10 @@ pub struct JsonTypeHint {
pub path: Vec<String>,
pub data_type: DataType,
pub inverted_index: bool,
/// Skipping index options for this hinted path.
pub skipping_index: Option<SkippingIndexOptions>,
/// Fulltext index options for a string hinted path.
pub fulltext_index: Option<FulltextOptions>,
}
impl Column {
@@ -353,6 +387,8 @@ impl ColumnExtensions {
path: hint.path,
data_type,
inverted_index: hint.inverted_index,
skipping_index: hint.skipping_index,
fulltext_index: hint.fulltext_index,
})
})
.collect::<Result<Vec<_>>>()?;
@@ -395,11 +431,17 @@ pub struct Partitions {
}
impl Partitions {
/// set quotes to all [Ident]s from column list
/// Sets quotes to all column [Ident]s, in the column list and in the expressions.
pub fn set_quote(&mut self, quote_style: char) {
self.column_list
.iter_mut()
.for_each(|c| c.quote_style = Some(quote_style));
let _ = visit_expressions_mut(&mut self.exprs, |expr| {
if let Expr::Identifier(ident) = expr {
ident.quote_style = Some(quote_style);
}
ControlFlow::<()>::Continue(())
});
}
}
@@ -811,6 +853,8 @@ mod tests {
path: vec!["x".repeat(8192)],
data_type: DataType::Text,
inverted_index: true,
skipping_index: None,
fulltext_index: None,
}],
});
assert!(!create.consume_sql_size_budget(&mut 4096));
@@ -831,6 +875,8 @@ mod tests {
path: vec![path.into()],
data_type: DataType::Text,
inverted_index: false,
skipping_index: None,
fulltext_index: None,
}],
};
assert_eq!(
@@ -1156,26 +1202,36 @@ ENGINE=mito
path: vec!["i".to_string()],
data_type: ConcreteDataType::int64_datatype(),
inverted_index: false,
skipping_index: None,
fulltext_index: None,
},
DatatypeJsonTypeHint {
path: vec!["f".to_string()],
data_type: ConcreteDataType::float64_datatype(),
inverted_index: false,
skipping_index: None,
fulltext_index: None,
},
DatatypeJsonTypeHint {
path: vec!["u".to_string()],
data_type: ConcreteDataType::uint64_datatype(),
inverted_index: false,
skipping_index: None,
fulltext_index: None,
},
DatatypeJsonTypeHint {
path: vec!["s".to_string()],
data_type: ConcreteDataType::string_datatype(),
inverted_index: false,
skipping_index: None,
fulltext_index: None,
},
DatatypeJsonTypeHint {
path: vec!["b".to_string()],
data_type: ConcreteDataType::boolean_datatype(),
inverted_index: false,
skipping_index: None,
fulltext_index: None,
},
],
None,
@@ -1202,6 +1258,8 @@ ENGINE=mito
path: vec!["u".to_string()],
data_type: ConcreteDataType::date_datatype(),
inverted_index: false,
skipping_index: None,
fulltext_index: None,
}],
None,
)
+35 -12
View File
@@ -18,11 +18,15 @@ use itertools::Itertools;
use serde::Serialize;
use sqlparser::ast::{ObjectName, Visit, VisitMut, Visitor, VisitorMut};
use crate::statements::OptionMap;
/// TRUNCATE TABLE statement.
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct TruncateTable {
table_name: ObjectName,
time_ranges: Vec<(sqlparser::ast::Value, sqlparser::ast::Value)>,
/// Options for this DDL submission, never persisted as table options.
options: OptionMap,
}
impl Visit for TruncateTable {
@@ -32,6 +36,7 @@ impl Visit for TruncateTable {
start.visit(visitor)?;
end.visit(visitor)?;
}
Visit::visit(&self.options, visitor)?;
::std::ops::ControlFlow::Continue(())
}
}
@@ -43,6 +48,7 @@ impl VisitMut for TruncateTable {
start.visit(visitor)?;
end.visit(visitor)?;
}
VisitMut::visit(&mut self.options, visitor)?;
::std::ops::ControlFlow::Continue(())
}
}
@@ -53,6 +59,7 @@ impl TruncateTable {
Self {
table_name,
time_ranges: Vec::new(),
options: OptionMap::default(),
}
}
@@ -64,9 +71,21 @@ impl TruncateTable {
Self {
table_name,
time_ranges,
options: OptionMap::default(),
}
}
/// Sets the options for this DDL submission.
pub fn with_options(mut self, options: OptionMap) -> Self {
self.options = options;
self
}
/// Returns the options for this DDL submission.
pub fn options(&self) -> &OptionMap {
&self.options
}
pub fn table_name(&self) -> &ObjectName {
&self.table_name
}
@@ -80,19 +99,23 @@ impl Display for TruncateTable {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let table_name = self.table_name();
write!(f, r#"TRUNCATE TABLE {table_name}"#)?;
if self.time_ranges.is_empty() {
return Ok(());
if !self.time_ranges.is_empty() {
write!(f, " FILE RANGE ")?;
write!(
f,
"{}",
self.time_ranges
.iter()
.map(|(start, end)| format!("({}, {})", start, end))
.join(", ")
)?;
}
write!(f, " FILE RANGE ")?;
write!(
f,
"{}",
self.time_ranges
.iter()
.map(|(start, end)| format!("({}, {})", start, end))
.join(", ")
)
if !self.options.is_empty() {
f.write_str(" WITH (")?;
self.options.write_sql(f, ", ", "", false)?;
f.write_str(")")?;
}
Ok(())
}
}