Skip to content

Commit dabe3f7

Browse files
authored
chore(query): Compatible with the MySQL BI Ecosystem (#18909)
* fix(query): jdbc test * test * fix * fix * next * test * fix load * fix * try * bump opensrv-mysql 0.9.0 * fix * fix ci
1 parent 346c94c commit dabe3f7

File tree

25 files changed

+1105
-637
lines changed

25 files changed

+1105
-637
lines changed

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -418,7 +418,7 @@ openraft = { version = "0.10.0", features = [
418418
"serde",
419419
"tracing-log",
420420
] }
421-
opensrv-mysql = { git = "https://github.com/databendlabs/opensrv.git", rev = "a1fb4da", features = ["tls"] }
421+
opensrv-mysql = { git = "https://github.com/databendlabs/opensrv.git", tag = "v0.9.0", features = ["tls"] }
422422
orc-rust = "0.6.0"
423423
ordered-float = { version = "5.0.0", default-features = false }
424424
ordq = "0.2.0"

src/query/service/src/databases/information_schema/information_schema_database.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,11 @@ use databend_common_meta_app::schema::DatabaseInfo;
2020
use databend_common_meta_app::schema::DatabaseMeta;
2121
use databend_common_meta_app::tenant::Tenant;
2222
use databend_common_meta_types::SeqV;
23+
use databend_common_storages_information_schema::CharacterSetsTable;
2324
use databend_common_storages_information_schema::ColumnsTable;
2425
use databend_common_storages_information_schema::KeyColumnUsageTable;
2526
use databend_common_storages_information_schema::KeywordsTable;
27+
use databend_common_storages_information_schema::ReferentialConstraintsTable;
2628
use databend_common_storages_information_schema::SchemataTable;
2729
use databend_common_storages_information_schema::StatisticsTable;
2830
use databend_common_storages_information_schema::TablesTable;
@@ -41,12 +43,14 @@ impl InformationSchemaDatabase {
4143
pub fn create(sys_db_meta: &mut InMemoryMetas, ctl_name: &str) -> Self {
4244
let table_list: Vec<Arc<dyn Table>> = vec![
4345
ColumnsTable::create(sys_db_meta.next_table_id(), ctl_name),
46+
CharacterSetsTable::create(sys_db_meta.next_table_id(), ctl_name),
4447
TablesTable::create(sys_db_meta.next_table_id(), ctl_name),
4548
KeywordsTable::create(sys_db_meta.next_table_id(), ctl_name),
4649
ViewsTable::create(sys_db_meta.next_table_id(), ctl_name),
4750
SchemataTable::create(sys_db_meta.next_table_id(), ctl_name),
4851
StatisticsTable::create(sys_db_meta.next_table_id(), ctl_name),
4952
KeyColumnUsageTable::create(sys_db_meta.next_table_id(), ctl_name),
53+
ReferentialConstraintsTable::create(sys_db_meta.next_table_id(), ctl_name),
5054
];
5155

5256
let db = "information_schema";

src/query/service/src/servers/mysql/mysql_federated.rs

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,77 @@ impl MySQLFederated {
6464
Some((schema, block))
6565
}
6666

67+
// Returns empty result set with MySQL-compatible schema for SHOW KEYS/INDEX commands.
68+
// Required for PowerBI + MySQL ODBC 9.5 compatibility.
69+
// Reference: https://dev.mysql.com/doc/refman/8.0/en/show-index.html
70+
fn show_keys_block() -> Option<(TableSchemaRef, DataBlock)> {
71+
use databend_common_expression::types::number::*;
72+
73+
let schema = TableSchemaRefExt::create(vec![
74+
TableField::new("Table", TableDataType::String),
75+
TableField::new(
76+
"Non_unique",
77+
TableDataType::Number(databend_common_expression::types::NumberDataType::Int32),
78+
),
79+
TableField::new("Key_name", TableDataType::String),
80+
TableField::new(
81+
"Seq_in_index",
82+
TableDataType::Number(databend_common_expression::types::NumberDataType::Int32),
83+
),
84+
TableField::new("Column_name", TableDataType::String),
85+
TableField::new(
86+
"Collation",
87+
TableDataType::Nullable(Box::new(TableDataType::String)),
88+
),
89+
TableField::new(
90+
"Cardinality",
91+
TableDataType::Nullable(Box::new(TableDataType::Number(
92+
databend_common_expression::types::NumberDataType::Int64,
93+
))),
94+
),
95+
TableField::new(
96+
"Sub_part",
97+
TableDataType::Nullable(Box::new(TableDataType::Number(
98+
databend_common_expression::types::NumberDataType::Int64,
99+
))),
100+
),
101+
TableField::new(
102+
"Packed",
103+
TableDataType::Nullable(Box::new(TableDataType::String)),
104+
),
105+
TableField::new("Null", TableDataType::String),
106+
TableField::new("Index_type", TableDataType::String),
107+
TableField::new("Comment", TableDataType::String),
108+
TableField::new("Index_comment", TableDataType::String),
109+
TableField::new("Visible", TableDataType::String),
110+
TableField::new(
111+
"Expression",
112+
TableDataType::Nullable(Box::new(TableDataType::String)),
113+
),
114+
]);
115+
116+
// Create empty block with proper column types
117+
let block = DataBlock::new_from_columns(vec![
118+
StringType::from_data(Vec::<String>::new()), // Table
119+
Int32Type::from_data(Vec::<i32>::new()), // Non_unique
120+
StringType::from_data(Vec::<String>::new()), // Key_name
121+
Int32Type::from_data(Vec::<i32>::new()), // Seq_in_index
122+
StringType::from_data(Vec::<String>::new()), // Column_name
123+
StringType::from_data_with_validity(Vec::<&str>::new(), Vec::<bool>::new()), /* Collation */
124+
Int64Type::from_data_with_validity(Vec::<i64>::new(), Vec::<bool>::new()), /* Cardinality */
125+
Int64Type::from_data_with_validity(Vec::<i64>::new(), Vec::<bool>::new()), // Sub_part
126+
StringType::from_data_with_validity(Vec::<&str>::new(), Vec::<bool>::new()), // Packed
127+
StringType::from_data(Vec::<String>::new()), // Null
128+
StringType::from_data(Vec::<String>::new()), // Index_type
129+
StringType::from_data(Vec::<String>::new()), // Comment
130+
StringType::from_data(Vec::<String>::new()), // Index_comment
131+
StringType::from_data(Vec::<String>::new()), // Visible
132+
StringType::from_data_with_validity(Vec::<&str>::new(), Vec::<bool>::new()), /* Expression */
133+
]);
134+
135+
Some((schema, block))
136+
}
137+
67138
// SELECT @@aa, @@bb as cc, @dd...
68139
// Block is built by the variables.
69140
fn select_variable_data_block(query: &str) -> Option<(TableSchemaRef, DataBlock)> {
@@ -217,6 +288,9 @@ impl MySQLFederated {
217288
(Regex::new("(?i)^(LOCK TABLES FOR BACKUP)").unwrap(), None),
218289
(Regex::new("(?i)^(UNLOCK BINLOG(.*))").unwrap(), None),
219290
(Regex::new("(?i)^(/\\*!40101 SET(.*) \\*/)$").unwrap(), None),
291+
// PowerBI.
292+
(Regex::new("(?i)^(SET SQL_AUTO_IS_NULL(.*))").unwrap(), None),
293+
(Regex::new("(?i)^(SHOW KEYS FROM(.*))").unwrap(), MySQLFederated::show_keys_block()),
220294
// DBeaver.
221295
(Regex::new("(?i)^(SHOW WARNINGS)").unwrap(), None),
222296
(Regex::new("(?i)^(/\\* ApplicationName=(.*)SHOW WARNINGS)").unwrap(), None),

src/query/service/src/servers/mysql/mysql_interactive_worker.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ impl<W: AsyncWrite + Send + Sync + Unpin> AsyncMysqlShim<W> for InteractiveWorke
105105
}
106106

107107
#[async_backtrace::framed]
108-
async fn auth_plugin_for_username(&self, _user: &[u8]) -> &str {
108+
async fn auth_plugin_for_username(&self, _user: &[u8]) -> &'static str {
109109
"mysql_native_password"
110110
}
111111

@@ -218,7 +218,6 @@ impl<W: AsyncWrite + Send + Sync + Unpin> AsyncMysqlShim<W> for InteractiveWorke
218218
let query_id = Uuid::now_v7();
219219
// Ensure the query id shares the same representation as trace_id.
220220
let query_id_str = query_id.simple().to_string();
221-
222221
let sampled =
223222
thread_rng().gen_range(0..100) <= self.base.session.get_trace_sample_rate()?;
224223
let span_context =
@@ -402,7 +401,11 @@ impl InteractiveWorkerBase {
402401
if data_block.num_rows() > 0 {
403402
info!("Federated response: {:?}", data_block);
404403
}
405-
let has_result = data_block.num_rows() > 0;
404+
// Fix for MySQL ODBC 9.5 + PowerBI compatibility:
405+
// Empty result sets (0 rows but with schema) must set has_result=true,
406+
// otherwise ODBC throws "invalid cursor state" error on commands like "SHOW KEYS FROM table".
407+
// Check for column definitions instead of row count.
408+
let has_result = !schema.fields().is_empty() || data_block.num_columns() > 0;
406409
Ok((
407410
QueryResult::create(
408411
DataBlockStream::create(None, vec![data_block]).boxed(),

src/query/service/src/servers/mysql/writers/query_result_writer.rs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,10 +201,55 @@ impl<'a, W: AsyncWrite + Send + Unpin> DFQueryResultWriter<'a, W> {
201201
}
202202
}
203203

204+
fn compute_length(data_type: &DataType) -> u32 {
205+
match data_type {
206+
DataType::Null | DataType::EmptyArray | DataType::EmptyMap => 0,
207+
DataType::Boolean => 1,
208+
DataType::Binary => 16382,
209+
DataType::String => 16382 * 4,
210+
DataType::Number(num_ty) => match num_ty {
211+
NumberDataType::Int8 | NumberDataType::UInt8 => 3,
212+
NumberDataType::Int16 | NumberDataType::UInt16 => 5,
213+
NumberDataType::Int32 | NumberDataType::UInt32 => 10,
214+
NumberDataType::Int64 => 19,
215+
NumberDataType::UInt64 => 20,
216+
NumberDataType::Float32 => 12,
217+
NumberDataType::Float64 => 22,
218+
},
219+
DataType::Decimal(size) => size.precision() as u32,
220+
DataType::Date => 10,
221+
DataType::Timestamp => 26,
222+
DataType::Interval => 64,
223+
DataType::Geometry | DataType::Geography => 1024,
224+
DataType::Vector(_) => 1024,
225+
DataType::Bitmap | DataType::Variant | DataType::StageLocation => 1024,
226+
DataType::Array(inner) | DataType::Map(inner) => {
227+
let inner_len = compute_length(inner);
228+
(inner_len.saturating_mul(4)).min(16382)
229+
}
230+
DataType::Tuple(fields) => fields
231+
.iter()
232+
.map(compute_length)
233+
.max()
234+
.unwrap_or(1024)
235+
.min(1024),
236+
DataType::Opaque(_) => 1024,
237+
DataType::Nullable(inner) => compute_length(inner),
238+
DataType::Generic(_) => 1024,
239+
DataType::TimestampTz => 1024,
240+
}
241+
}
242+
243+
fn column_length(field: &DataField) -> u32 {
244+
let length = compute_length(field.data_type());
245+
length.clamp(1, 16382)
246+
}
247+
204248
fn make_column_from_field(field: &DataField) -> Result<Column> {
205249
convert_field_type(field).map(|column_type| Column {
206250
table: "".to_string(),
207251
column: field.name().to_string(),
252+
collen: column_length(field),
208253
coltype: column_type,
209254
colflags: ColumnFlags::empty(),
210255
})

src/query/service/tests/it/storages/system.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ use databend_common_meta_app::storage::StorageFsConfig;
3232
use databend_common_meta_app::storage::StorageParams;
3333
use databend_common_meta_app::storage::StorageS3Config;
3434
use databend_common_sql::executor::table_read_plan::ToReadDataSourcePlan;
35+
use databend_common_storages_basic::view_table::QUERY;
36+
use databend_common_storages_information_schema::CharacterSetsTable;
3537
use databend_common_storages_system::BuildOptionsTable;
3638
use databend_common_storages_system::CachesTable;
3739
use databend_common_storages_system::CatalogsTable;
@@ -139,6 +141,21 @@ async fn test_columns_table() -> Result<()> {
139141
Ok(())
140142
}
141143

144+
#[test]
145+
fn test_information_schema_character_sets_table_metadata() -> Result<()> {
146+
let table = CharacterSetsTable::create(1, "default");
147+
let table_info = table.get_table_info();
148+
assert_eq!(table_info.name, "character_sets");
149+
let query = table_info
150+
.meta
151+
.options
152+
.get(QUERY)
153+
.expect("view query must be set");
154+
assert!(query.contains("utf8mb4"));
155+
assert!(query.contains("character_set_name"));
156+
Ok(())
157+
}
158+
142159
#[tokio::test(flavor = "multi_thread")]
143160
async fn test_clusters_table() -> Result<()> {
144161
let fixture = TestFixture::setup().await?;

0 commit comments

Comments
 (0)