feat: add ddl timeout/wait options, repartition WITH parsing, meta-client startup refactor (#7589)

* feat: add ddl request timeouts and unify meta client startup

Signed-off-by: WenyXu <wenymedia@gmail.com>

* feat: omplement ALTER TABLE repartition DDL options parsing

Signed-off-by: WenyXu <wenymedia@gmail.com>

* test: add sqlness tests

Signed-off-by: WenyXu <wenymedia@gmail.com>

* test: add unit tests

Signed-off-by: WenyXu <wenymedia@gmail.com>

* feat: pass timeout argument to procedure

Signed-off-by: WenyXu <wenymedia@gmail.com>

* chore: apply suggestions

Signed-off-by: WenyXu <wenymedia@gmail.com>

* chore: refine comments

Signed-off-by: WenyXu <wenymedia@gmail.com>

* test: assert timeout

Signed-off-by: WenyXu <wenymedia@gmail.com>

* chore: apply suggestions

Signed-off-by: WenyXu <wenymedia@gmail.com>

* chore: update proto

Signed-off-by: WenyXu <wenymedia@gmail.com>

---------

Signed-off-by: WenyXu <wenymedia@gmail.com>
This commit is contained in:
Weny Xu
2026-01-20 17:26:53 +08:00
committed by GitHub
parent aa3daf7053
commit 25687bb282
33 changed files with 716 additions and 322 deletions

View File

@@ -30,7 +30,8 @@ use crate::error::{self, InvalidColumnOptionSnafu, Result, SetFulltextOptionSnaf
use crate::parser::ParserContext;
use crate::parsers::create_parser::INVERTED;
use crate::parsers::utils::{
validate_column_fulltext_create_option, validate_column_skipping_index_create_option,
parse_with_options, validate_column_fulltext_create_option,
validate_column_skipping_index_create_option,
};
use crate::statements::OptionMap;
use crate::statements::alter::{
@@ -181,7 +182,10 @@ impl ParserContext<'_> {
}
unexpected => self.unsupported(unexpected.to_string())?,
};
Ok(AlterTable::new(table_name, alter_operation))
let options = parse_with_options(&mut self.parser)?;
// TODO(weny): Respect the DDL options (e.g., WAIT and TIMEOUT) after the ALTER TABLE statement.
Ok(AlterTable::new(table_name, alter_operation, options))
}
fn parse_alter_table_unset(&mut self) -> Result<AlterTableOperation> {
@@ -1043,6 +1047,50 @@ ALTER TABLE metrics MERGE PARTITION (
}
}
#[test]
fn test_parse_alter_table_merge_partition_with_options() {
let sql = r#"
ALTER TABLE alter_repartition_table MERGE PARTITION (
device_id < 100 AND area < 'South',
device_id < 100 AND area >= 'South'
) WITH (
TIMEOUT = '5m',
WAIT = false
);"#;
let mut result =
ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
.unwrap();
assert_eq!(1, result.len());
let statement = result.remove(0);
assert_matches!(statement, Statement::AlterTable { .. });
if let Statement::AlterTable(alter_table) = statement {
assert_matches!(
alter_table.alter_operation(),
AlterTableOperation::Repartition { .. }
);
if let AlterTableOperation::Repartition { operation } = alter_table.alter_operation() {
assert_eq!(operation.from_exprs.len(), 2);
assert_eq!(
operation.from_exprs[0].to_string(),
"device_id < 100 AND area < 'South'"
);
assert_eq!(
operation.from_exprs[1].to_string(),
"device_id < 100 AND area >= 'South'"
);
assert_eq!(operation.into_exprs.len(), 1);
}
// Verify WITH options are parsed
let options = alter_table.options().to_str_map();
assert_eq!(options.get("timeout").unwrap(), &"5m");
assert_eq!(options.get("wait").unwrap(), &"false");
assert_eq!(options.len(), 2);
}
}
#[test]
fn test_parse_alter_table_repartition_multiple() {
let sql = r#"

View File

@@ -31,19 +31,19 @@ use sqlparser::keywords::ALL_KEYWORDS;
use sqlparser::parser::IsOptional::Mandatory;
use sqlparser::parser::{Parser, ParserError};
use sqlparser::tokenizer::{Token, TokenWithSpan, Word};
use table::requests::{validate_database_option, validate_table_option};
use table::requests::validate_database_option;
use crate::ast::{ColumnDef, Ident, ObjectNamePartExt};
use crate::error::{
self, InvalidColumnOptionSnafu, InvalidDatabaseOptionSnafu, InvalidIntervalSnafu,
InvalidSqlSnafu, InvalidTableOptionSnafu, InvalidTimeIndexSnafu, MissingTimeIndexSnafu, Result,
SyntaxSnafu, UnexpectedSnafu, UnsupportedSnafu,
InvalidSqlSnafu, InvalidTimeIndexSnafu, MissingTimeIndexSnafu, Result, SyntaxSnafu,
UnexpectedSnafu, UnsupportedSnafu,
};
use crate::parser::{FLOW, ParserContext};
use crate::parsers::tql_parser;
use crate::parsers::utils::{
self, validate_column_fulltext_create_option, validate_column_skipping_index_create_option,
validate_column_vector_index_create_option,
self, parse_with_options, validate_column_fulltext_create_option,
validate_column_skipping_index_create_option, validate_column_vector_index_create_option,
};
use crate::statements::create::{
Column, ColumnExtensions, CreateDatabase, CreateExternalTable, CreateFlow, CreateTable,
@@ -447,17 +447,7 @@ impl<'a> ParserContext<'a> {
}
fn parse_create_table_options(&mut self) -> Result<OptionMap> {
let options = self
.parser
.parse_options(Keyword::WITH)
.context(SyntaxSnafu)?
.into_iter()
.map(parse_option_string)
.collect::<Result<HashMap<String, OptionValue>>>()?;
for key in options.keys() {
ensure!(validate_table_option(key), InvalidTableOptionSnafu { key });
}
Ok(OptionMap::new(options))
parse_with_options(&mut self.parser)
}
/// "PARTITION ON COLUMNS (...)" clause

View File

@@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use std::collections::HashMap;
use std::sync::Arc;
use chrono::Utc;
@@ -39,13 +40,18 @@ use datatypes::schema::{
};
use snafu::{ResultExt, ensure};
use sqlparser::dialect::Dialect;
use sqlparser::keywords::Keyword;
use sqlparser::parser::Parser;
use table::requests::validate_table_option;
use crate::error::{
ConvertToLogicalExpressionSnafu, InvalidSqlSnafu, ParseSqlValueSnafu, Result,
SimplificationSnafu,
ConvertToLogicalExpressionSnafu, InvalidSqlSnafu, InvalidTableOptionSnafu, ParseSqlValueSnafu,
Result, SimplificationSnafu, SyntaxSnafu,
};
use crate::parser::{ParseOptions, ParserContext};
use crate::statements::OptionMap;
use crate::statements::statement::Statement;
use crate::util::{OptionValue, parse_option_string};
/// Check if the given SQL query is a TQL statement.
pub fn is_tql(dialect: &dyn Dialect, sql: &str) -> Result<bool> {
@@ -272,6 +278,19 @@ pub fn convert_month_day_nano_to_duration(
Ok(std::time::Duration::new(adjusted_seconds, nanos_remainder))
}
pub fn parse_with_options(parser: &mut Parser) -> Result<OptionMap> {
let options = parser
.parse_options(Keyword::WITH)
.context(SyntaxSnafu)?
.into_iter()
.map(parse_option_string)
.collect::<Result<HashMap<String, OptionValue>>>()?;
for key in options.keys() {
ensure!(validate_table_option(key), InvalidTableOptionSnafu { key });
}
Ok(OptionMap::new(options))
}
#[cfg(test)]
mod tests {
use std::sync::Arc;

View File

@@ -25,17 +25,26 @@ use serde::Serialize;
use sqlparser::ast::{ColumnDef, DataType, Expr, Ident, ObjectName, TableConstraint};
use sqlparser_derive::{Visit, VisitMut};
use crate::statements::OptionMap;
#[derive(Debug, Clone, PartialEq, Eq, Visit, VisitMut, Serialize)]
pub struct AlterTable {
pub table_name: ObjectName,
pub alter_operation: AlterTableOperation,
/// Table options in `WITH`. All keys are lowercase.
pub options: OptionMap,
}
impl AlterTable {
pub(crate) fn new(table_name: ObjectName, alter_operation: AlterTableOperation) -> Self {
pub(crate) fn new(
table_name: ObjectName,
alter_operation: AlterTableOperation,
options: OptionMap,
) -> Self {
Self {
table_name,
alter_operation,
options,
}
}
@@ -47,6 +56,10 @@ impl AlterTable {
&self.alter_operation
}
pub fn options(&self) -> &OptionMap {
&self.options
}
pub fn alter_operation_mut(&mut self) -> &mut AlterTableOperation {
&mut self.alter_operation
}

View File

@@ -90,6 +90,9 @@ impl OptionMap {
self.options.len() + self.secrets.len()
}
/// Convert the option map to a string map.
///
/// Notes: Not all values can be converted to a string, refer to [OptionValue::expr_as_string] for more details.
pub fn to_str_map(&self) -> HashMap<&str, &str> {
let mut map = HashMap::with_capacity(self.len());
map.extend(
@@ -105,6 +108,9 @@ impl OptionMap {
map
}
/// Convert the option map to a string map.
///
/// Notes: Not all values can be converted to a string, refer to [OptionValue::expr_as_string] for more details.
pub fn into_map(self) -> HashMap<String, String> {
let mut map = HashMap::with_capacity(self.len());
map.extend(

View File

@@ -87,6 +87,7 @@ impl OptionValue {
| Value::HexStringLiteral(s) => Some(s),
Value::DollarQuotedString(s) => Some(&s.value),
Value::Number(s, _) => Some(s),
Value::Boolean(b) => Some(if *b { "true" } else { "false" }),
_ => None,
},
Expr::Identifier(ident) => Some(&ident.value),
@@ -94,6 +95,9 @@ impl OptionValue {
}
}
/// Convert the option value to a string.
///
/// Notes: Not all values can be converted to a string, refer to [Self::expr_as_string] for more details.
pub fn as_string(&self) -> Option<&str> {
Self::expr_as_string(&self.0)
}