feat(manticore): add Manticore Search support

Add Manticore Search table data editing, structure editing, metadata handling, and SQL completion support.
This commit is contained in:
Vergil Lai authored and GitHub committed 2026-06-14 16:01:02 +08:00
1 parent 93d2e7dc18
commit f385386284
34 files changed
+1250 -130

No files matched your search

+83 -9
View File
@@ -252,7 +252,10 @@ pub fn build_data_grid_copy_update_statements(options: DataGridCopyUpdateStateme
})
.collect::<Vec<_>>()
.join(" AND ");
statements.push(format!("UPDATE {table} SET {sets} WHERE {where_clause};"));
statements.push(data_grid_statement(
options.database_type,
format!("UPDATE {table} SET {sets} WHERE {where_clause}"),
));
}
statements
}
@@ -528,7 +531,10 @@ fn build_data_grid_save_statements(options: &DataGridSaveStatementOptions) -> Ve
row,
column_info,
);
statements.push(format!("UPDATE {table} SET {sets} WHERE {where_clause};"));
statements.push(data_grid_statement(
options.database_type,
format!("UPDATE {table} SET {sets} WHERE {where_clause}"),
));
}
for row_index in &options.deleted_rows {
@@ -542,7 +548,8 @@ fn build_data_grid_save_statements(options: &DataGridSaveStatementOptions) -> Ve
row,
column_info,
);
statements.push(format!("DELETE FROM {table} WHERE {where_clause};"));
statements
.push(data_grid_statement(options.database_type, format!("DELETE FROM {table} WHERE {where_clause}")));
}
for row in &options.new_rows {
@@ -568,7 +575,10 @@ fn build_data_grid_save_statements(options: &DataGridSaveStatementOptions) -> Ve
})
.collect::<Vec<_>>()
.join(", ");
statements.push(format!("INSERT INTO {table} ({columns}) VALUES ({values});"));
statements.push(data_grid_statement(
options.database_type,
format!("INSERT INTO {table} ({columns}) VALUES ({values})"),
));
}
statements
@@ -591,7 +601,8 @@ fn build_data_grid_rollback_statements(options: &DataGridSaveStatementOptions) -
for row in &options.new_rows {
let where_clause = build_row_where(options.database_type, &save_columns, row, column_info);
if !where_clause.is_empty() {
statements.push(format!("DELETE FROM {table} WHERE {where_clause};"));
statements
.push(data_grid_statement(options.database_type, format!("DELETE FROM {table} WHERE {where_clause}")));
}
}
@@ -617,7 +628,10 @@ fn build_data_grid_rollback_statements(options: &DataGridSaveStatementOptions) -
})
.collect::<Vec<_>>()
.join(", ");
statements.push(format!("INSERT INTO {table} ({columns}) VALUES ({values});"));
statements.push(data_grid_statement(
options.database_type,
format!("INSERT INTO {table} ({columns}) VALUES ({values})"),
));
}
for (row_index, changes) in &options.dirty_rows {
@@ -668,9 +682,12 @@ fn build_data_grid_rollback_statements(options: &DataGridSaveStatementOptions) -
predicates.extend(writable_changes.iter().map(|((_, value), column)| {
build_column_predicate(options.database_type, column, value, column_info_for(column_info, column))
}));
statements.push(format!(
"UPDATE {table} SET {sets} WHERE {};",
predicates.into_iter().filter(|part| !part.is_empty()).collect::<Vec<_>>().join(" AND ")
statements.push(data_grid_statement(
options.database_type,
format!(
"UPDATE {table} SET {sets} WHERE {}",
predicates.into_iter().filter(|part| !part.is_empty()).collect::<Vec<_>>().join(" AND ")
),
));
}
@@ -749,6 +766,11 @@ pub fn format_grid_sql_literal(
return format_pg_array_sql_literal(arr);
}
let text = value.as_str().map_or_else(|| value.to_string(), ToString::to_string);
if database_type == Some(DatabaseType::ManticoreSearch) {
if let Some(typed_value) = manticore_typed_attribute_value(&text, column_info) {
return format_grid_sql_literal(&typed_value, database_type, column_info);
}
}
if text.is_empty() {
return if database_type == Some(DatabaseType::SqlServer) { "N''" } else { "''" }.to_string();
}
@@ -779,6 +801,20 @@ fn is_bit_column_type(data_type: &str) -> bool {
lower.split(|ch: char| !ch.is_ascii_alphanumeric()).any(|token| token == "bit")
}
fn manticore_typed_attribute_value(text: &str, column_info: Option<&DataGridColumnInfo>) -> Option<Value> {
let data_type = column_info?.data_type.to_ascii_lowercase();
if is_boolean_type(&data_type) && text.eq_ignore_ascii_case("true") {
return Some(Value::Bool(true));
}
if is_boolean_type(&data_type) && text.eq_ignore_ascii_case("false") {
return Some(Value::Bool(false));
}
if is_numeric_type(&data_type) && is_numeric_literal(text) {
return text.parse::<serde_json::Number>().ok().map(Value::Number);
}
None
}
fn format_mysql_bit_literal_text(text: &str) -> Option<String> {
let trimmed = text.trim();
if trimmed.eq_ignore_ascii_case("true") {
@@ -1032,6 +1068,14 @@ fn build_column_predicate(
}
}
fn data_grid_statement(database_type: Option<DatabaseType>, sql: String) -> String {
if database_type == Some(DatabaseType::ManticoreSearch) {
sql
} else {
format!("{sql};")
}
}
fn uses_mysql_binary_text_predicate(
database_type: Option<DatabaseType>,
value: &Value,
@@ -1250,6 +1294,7 @@ fn uses_keyless_row_predicate(database_type: Option<DatabaseType>) -> bool {
database_type,
Some(
DatabaseType::Mysql
| DatabaseType::ManticoreSearch
| DatabaseType::Postgres
| DatabaseType::Sqlite
| DatabaseType::DuckDb
@@ -1568,6 +1613,35 @@ mod tests {
);
}
#[test]
fn prepares_manticore_save_statements_without_trailing_semicolons() {
let result = prepare_data_grid_save(DataGridSaveStatementOptions {
database_type: Some(DatabaseType::ManticoreSearch),
table_meta: DataGridTableMeta {
schema: None,
table_name: "rt_products".to_string(),
primary_keys: vec![],
columns: Some(vec![column("id", "bigint", false, None), column("title", "text", true, None)]),
},
columns: vec!["id".to_string(), "title".to_string()],
source_columns: None,
rows: vec![vec![json!("1"), json!("old")], vec![json!("2"), json!("deleted")]],
dirty_rows: vec![(0, vec![(1, json!("new"))])],
deleted_rows: vec![1],
new_rows: vec![vec![json!("3"), json!("inserted")]],
});
assert_eq!(
result.statements,
vec![
"UPDATE `rt_products` SET `title` = 'new' WHERE `id` = 1 AND `title` = 'old'",
"DELETE FROM `rt_products` WHERE `id` = 2 AND `title` = 'deleted'",
"INSERT INTO `rt_products` (`id`, `title`) VALUES (3, 'inserted')",
]
);
assert!(result.rollback_statements.iter().all(|statement| !statement.ends_with(';')));
}
#[test]
fn validates_duplicate_inserted_primary_keys() {
let result = prepare_data_grid_save(DataGridSaveStatementOptions {