fix(sqlserver): support legacy index metadata

This commit is contained in:
t8y2 2026-07-27 19:07:05 +08:00
parent c4b21cf666
commit 5babaea219
No known key found for this signature in database
1 changed files with 68 additions and 8 deletions

View File

@ -1835,8 +1835,14 @@ fn sqlserver_columns_sql(schema: &str, table: &str) -> String {
pub async fn list_indexes(client: &mut SqlServerClient, schema: &str, table: &str) -> Result<Vec<IndexInfo>, String> {
let sql = sqlserver_indexes_sql(schema, table);
let stream = client.query(&*sql, &[]).await.map_err(|e| e.to_string())?;
let rows = stream.into_first_result().await.map_err(|e| e.to_string())?;
let rows = match sqlserver_index_rows(client, &sql).await {
Ok(rows) => rows,
Err(error) if sqlserver_filter_definition_missing(&error) => {
let legacy_sql = sqlserver_legacy_indexes_sql(schema, table);
sqlserver_index_rows(client, &legacy_sql).await.map_err(|error| error.to_string())?
}
Err(error) => return Err(error.to_string()),
};
Ok(rows
.iter()
.map(|row| {
@ -1860,7 +1866,32 @@ pub async fn list_indexes(client: &mut SqlServerClient, schema: &str, table: &st
.collect())
}
async fn sqlserver_index_rows(client: &mut SqlServerClient, sql: &str) -> Result<Vec<Row>, tiberius::error::Error> {
client.query(sql, &[]).await?.into_first_result().await
}
fn sqlserver_filter_definition_missing(error: &tiberius::error::Error) -> bool {
sqlserver_filter_definition_error(error.code(), &error.to_string())
}
fn sqlserver_filter_definition_error(code: Option<u32>, message: &str) -> bool {
code == Some(207) && message.to_ascii_lowercase().contains("filter_definition")
}
fn sqlserver_indexes_sql(schema: &str, table: &str) -> String {
sqlserver_indexes_sql_with_filter_definition(schema, table, true)
}
fn sqlserver_legacy_indexes_sql(schema: &str, table: &str) -> String {
sqlserver_indexes_sql_with_filter_definition(schema, table, false)
}
fn sqlserver_indexes_sql_with_filter_definition(schema: &str, table: &str, include_filter_definition: bool) -> String {
let filter_definition = if include_filter_definition {
"i.filter_definition"
} else {
"CAST(NULL AS NVARCHAR(MAX)) AS filter_definition"
};
format!(
"SELECT i.name, \
STUFF((SELECT ',' + c2.name \
@ -1876,7 +1907,7 @@ fn sqlserver_indexes_sql(schema: &str, table: &str) -> String {
WHERE ic3.object_id = i.object_id AND ic3.index_id = i.index_id AND ic3.is_included_column = 1 \
ORDER BY ic3.index_column_id \
FOR XML PATH(''), TYPE).value('.', 'nvarchar(max)'), 1, 1, '') AS included_cols, \
i.filter_definition, \
{filter_definition}, \
ep.value AS index_comment \
FROM sys.indexes i \
OUTER APPLY (SELECT CAST(ep.value AS NVARCHAR(MAX)) AS value FROM sys.extended_properties ep WHERE ep.major_id = i.object_id AND ep.minor_id = i.index_id AND ep.name = N'MS_Description' AND ep.class = 7) ep \
@ -2298,11 +2329,11 @@ mod tests {
is_blocking_sqlserver_unsafe_probe_error, is_sqlserver_spatial_column, is_sqlserver_variant_column,
query_result_with_server_messages, requires_simple_query_batch, sqlserver_batch_can_use_execute,
sqlserver_cell_to_json, sqlserver_columns_sql, sqlserver_completion_assistant_sql,
sqlserver_dml_output_returns_rows, sqlserver_hidden_schema_names, sqlserver_indexes_sql,
sqlserver_legacy_probe, sqlserver_list_objects_sql, sqlserver_list_schemas_sql, sqlserver_list_tables_sql,
sqlserver_schema_name_predicate, sqlserver_table_comment_sql, sqlserver_visible_object_predicate,
strip_dbx_sqlserver_row_number_column, SqlServerDescribedColumn, SqlServerResultSet,
SQLSERVER_RESULT_TYPE_PROBE_SQL,
sqlserver_dml_output_returns_rows, sqlserver_filter_definition_error, sqlserver_hidden_schema_names,
sqlserver_indexes_sql, sqlserver_legacy_indexes_sql, sqlserver_legacy_probe, sqlserver_list_objects_sql,
sqlserver_list_schemas_sql, sqlserver_list_tables_sql, sqlserver_schema_name_predicate,
sqlserver_table_comment_sql, sqlserver_visible_object_predicate, strip_dbx_sqlserver_row_number_column,
SqlServerDescribedColumn, SqlServerResultSet, SQLSERVER_RESULT_TYPE_PROBE_SQL,
};
use crate::types::{
CompletionAssistantMatchMode, CompletionAssistantObjectKind, CompletionAssistantRequest, QueryResult,
@ -2636,6 +2667,35 @@ mod tests {
assert!(sql.contains("MS_Description"));
}
#[test]
fn sqlserver_legacy_indexes_sql_omits_filtered_index_metadata() {
let sql = sqlserver_legacy_indexes_sql("dbo", "orders");
assert!(!sql.contains("i.filter_definition"));
assert!(sql.contains("CAST(NULL AS NVARCHAR(MAX)) AS filter_definition"));
assert!(sql.contains("ep.value AS index_comment"));
}
#[test]
fn sqlserver_filter_definition_fallback_requires_column_error_207() {
assert!(sqlserver_filter_definition_error(
Some(207),
"Token error: Column name 'filter_definition' is invalid. (code: 207)"
));
assert!(sqlserver_filter_definition_error(
Some(207),
"Token error: 列名 'filter_definition' 无效。 (code: 207)"
));
assert!(!sqlserver_filter_definition_error(
Some(207),
"Token error: Column name 'other_column' is invalid. (code: 207)"
));
assert!(!sqlserver_filter_definition_error(
Some(229),
"Token error: SELECT permission denied for filter_definition. (code: 229)"
));
}
#[test]
fn sqlserver_columns_sql_reads_column_comment_by_column_id() {
let sql = sqlserver_columns_sql("dbo", "orders");