1use std::collections::HashMap;
16use std::net::SocketAddr;
17use std::sync::Arc;
18use std::sync::atomic::{AtomicU32, Ordering};
19use std::time::Duration;
20
21use ::auth::{BEARER_TOKEN_USER, Identity, MysqlAuthMethod, Password, UserProviderRef};
22use async_trait::async_trait;
23use chrono::{NaiveDate, NaiveDateTime};
24use common_catalog::parse_optional_catalog_and_schema_from_db_string;
25use common_error::ext::ErrorExt;
26use common_frontend::metrics;
27use common_query::Output;
28use common_telemetry::{debug, error, tracing, warn};
29use common_time::Timezone;
30use datafusion_common::ParamValues;
31use datafusion_expr::LogicalPlan;
32use datatypes::prelude::ConcreteDataType;
33use datatypes::schema::Schema;
34use itertools::Itertools;
35use mysql_common::Value as MysqlValue;
36use opensrv_mysql::{
37 AsyncMysqlShim, Column, ErrorKind, InitWriter, ParamParser, ParamValue, QueryResultWriter,
38 StatementMetaWriter, ValueInner,
39};
40use parking_lot::RwLock;
41use query::planner::DfLogicalPlanner;
42use query::query_engine::DescribeResult;
43use rand::RngCore;
44use session::context::{Channel, QueryContextRef};
45use session::{Session, SessionRef};
46use snafu::{ResultExt, ensure};
47use sql::dialect::MySqlDialect;
48use sql::parser::{ParseOptions, ParserContext};
49use sql::statements::statement::Statement;
50use tokio::io::AsyncWrite;
51
52use crate::SqlPlan;
53use crate::error::{
54 self, DataFrameSnafu, InferParameterTypesSnafu, InvalidPrepareStatementSnafu, Result,
55};
56use crate::metrics::METRIC_AUTH_FAILURE;
57use crate::mysql::helper::{self, format_placeholder, transform_placeholders_with_count};
58use crate::mysql::writer;
59use crate::mysql::writer::{create_mysql_column, handle_err};
60use crate::query_handler::sql::ServerSqlQueryHandlerRef;
61
62enum Params<'a> {
64 ProtocolParams(Vec<ParamValue<'a>>),
66 CliParams(Vec<sql::ast::Expr>),
68}
69
70impl Params<'_> {
71 fn len(&self) -> usize {
72 match self {
73 Params::ProtocolParams(params) => params.len(),
74 Params::CliParams(params) => params.len(),
75 }
76 }
77}
78
79pub struct MysqlInstanceShim {
81 query_handler: ServerSqlQueryHandlerRef,
82 salt: [u8; 20],
83 session: SessionRef,
84 user_provider: Option<UserProviderRef>,
85 prepared_stmts: Arc<RwLock<HashMap<String, SqlPlan>>>,
86 prepared_stmts_counter: AtomicU32,
87 process_id: u32,
88 prepared_stmt_cache_size: usize,
89 batching_enabled: bool,
90}
91
92impl MysqlInstanceShim {
93 pub fn create(
94 query_handler: ServerSqlQueryHandlerRef,
95 user_provider: Option<UserProviderRef>,
96 client_addr: SocketAddr,
97 process_id: u32,
98 prepared_stmt_cache_size: usize,
99 ) -> MysqlInstanceShim {
100 let mut bs = vec![0u8; 20];
102 let mut rng = rand::rng();
103 rng.fill_bytes(bs.as_mut());
104
105 let mut scramble: [u8; 20] = [0; 20];
106 for i in 0..20 {
107 scramble[i] = bs[i] & 0x7fu8;
108 if scramble[i] == b'\0' || scramble[i] == b'$' {
109 scramble[i] += 1;
110 }
111 }
112
113 MysqlInstanceShim {
114 query_handler,
115 salt: scramble,
116 session: Arc::new(Session::new(
117 Some(client_addr),
118 Channel::Mysql,
119 Default::default(),
120 process_id,
121 )),
122 user_provider,
123 prepared_stmts: Default::default(),
124 prepared_stmts_counter: AtomicU32::new(1),
125 process_id,
126 prepared_stmt_cache_size,
127 batching_enabled: false,
128 }
129 }
130
131 pub fn with_batching_enabled(mut self, enabled: bool) -> Self {
133 self.batching_enabled = enabled;
134 self
135 }
136
137 fn new_query_context(&self) -> QueryContextRef {
138 let mut ctx = self.session.new_query_context();
139 Arc::make_mut(&mut ctx).set_batching_enabled(self.batching_enabled);
140 ctx
141 }
142
143 #[tracing::instrument(skip_all, name = "mysql::do_query")]
144 async fn do_query(&self, query: &str, query_ctx: QueryContextRef) -> Vec<Result<Output>> {
145 if let Some(output) =
146 crate::mysql::federated::check(query, query_ctx.clone(), self.session.clone())
147 {
148 vec![Ok(output)]
149 } else {
150 self.query_handler.do_query(query, query_ctx.clone()).await
151 }
152 }
153
154 async fn do_describe(
156 &self,
157 statement: Statement,
158 query_ctx: QueryContextRef,
159 ) -> Result<Option<DescribeResult>> {
160 self.query_handler.do_describe(statement, query_ctx).await
161 }
162
163 fn save_plan(&self, plan: SqlPlan, stmt_key: String) -> Result<()> {
165 let mut prepared_stmts = self.prepared_stmts.write();
166 let max_capacity = self.prepared_stmt_cache_size;
167
168 let is_update = prepared_stmts.contains_key(&stmt_key);
169
170 if !is_update && prepared_stmts.len() >= max_capacity {
171 return error::InternalSnafu {
172 err_msg: format!(
173 "Prepared statement cache is full, max capacity: {}",
174 max_capacity
175 ),
176 }
177 .fail();
178 }
179
180 let _ = prepared_stmts.insert(stmt_key, plan);
181 Ok(())
182 }
183
184 fn plan(&self, stmt_key: &str) -> Option<SqlPlan> {
186 let plan = self.prepared_stmts.read().get(stmt_key).cloned();
187 metrics::record_cache_lookup("mysql_prepared_stmt", plan.is_some());
188 plan
189 }
190
191 async fn do_prepare(
193 &mut self,
194 raw_query: &str,
195 query_ctx: QueryContextRef,
196 stmt_key: String,
197 ) -> Result<(Vec<Column>, Vec<Column>)> {
198 if crate::mysql::federated::check(raw_query, query_ctx.clone(), self.session.clone())
199 .is_some()
200 {
201 self.save_plan(SqlPlan::Shortcut(raw_query.to_string()), stmt_key)
202 .inspect_err(|e| {
203 error!(e; "Failed to save prepared statement");
204 })?;
205 return Ok((vec![], vec![]));
206 }
207
208 let statement = validate_query(raw_query).await?;
209
210 let (statement, placeholder_count) = transform_placeholders_with_count(statement);
213 let param_num = placeholder_count + 1;
214
215 let describe_result = self
216 .do_describe(statement.clone(), query_ctx.clone())
217 .await?;
218 let plan = describe_result.map(|DescribeResult { logical_plan }| logical_plan);
219
220 let (params, can_cache_as_plan) = if let Some(plan) = &plan {
221 let param_types = DfLogicalPlanner::get_inferred_parameter_types(plan)
222 .context(InferParameterTypesSnafu)?
223 .into_iter()
224 .map(|(k, v)| (k, v.map(|v| ConcreteDataType::from_arrow_type(&v))))
225 .collect();
226
227 (
228 prepared_params(¶m_types, param_num)?,
229 all_params_have_types(¶m_types, param_num),
230 )
231 } else {
232 (dummy_params(param_num)?, false)
233 };
234
235 let columns =
236 plan.as_ref()
237 .map(|plan| {
238 let schema: Schema = plan.schema().clone().try_into().map_err(
239 |e: datatypes::error::Error| {
240 error::InternalSnafu {
241 err_msg: e.to_string(),
242 }
243 .build()
244 },
245 )?;
246 schema
247 .column_schemas()
248 .iter()
249 .map(|column_schema| {
250 create_mysql_column(&column_schema.data_type, &column_schema.name)
251 })
252 .collect::<Result<Vec<_>>>()
253 })
254 .transpose()?
255 .unwrap_or_default();
256
257 match plan {
258 Some(plan) if can_cache_as_plan => {
259 self.save_plan(SqlPlan::Plan(plan, statement), stmt_key)
260 .inspect_err(|e| {
261 error!(e; "Failed to save prepared statement");
262 })?;
263 }
264 _ => {
265 self.save_plan(
266 SqlPlan::Statement(statement, raw_query.to_string()),
267 stmt_key,
268 )
269 .inspect_err(|e| {
270 error!(e; "Failed to save prepared statement");
271 })?;
272 }
273 }
274
275 Ok((params, columns))
276 }
277
278 async fn do_execute(
279 &mut self,
280 query_ctx: QueryContextRef,
281 stmt_key: String,
282 params: Params<'_>,
283 ) -> Result<Vec<std::result::Result<Output, error::Error>>> {
284 let sql_plan = match self.plan(&stmt_key) {
285 None => {
286 return error::PrepareStatementNotFoundSnafu { name: stmt_key }.fail();
287 }
288 Some(sql_plan) => sql_plan,
289 };
290
291 let outputs = match sql_plan {
292 SqlPlan::Plan(plan, stmt) => {
293 let param_types = DfLogicalPlanner::get_inferred_parameter_types(&plan)
294 .context(InferParameterTypesSnafu)?
295 .into_iter()
296 .map(|(k, v)| (k, v.map(|v| ConcreteDataType::from_arrow_type(&v))))
297 .collect::<HashMap<_, _>>();
298
299 if params.len() != param_types.len() {
300 return error::InternalSnafu {
301 err_msg: "Prepare statement params number mismatch".to_string(),
302 }
303 .fail();
304 }
305
306 let timezone = query_ctx.timezone();
307 let replaced_plan = match params {
308 Params::ProtocolParams(params) => {
309 replace_params_with_values(&plan, param_types, ¶ms, &timezone)
310 }
311 Params::CliParams(params) => {
312 replace_params_with_exprs(&plan, param_types, ¶ms, &timezone)
313 }
314 }?;
315
316 debug!(
317 "Mysql execute prepared plan: {}",
318 replaced_plan.display_indent()
319 );
320 vec![
321 self.query_handler
322 .do_exec_plan(replaced_plan, Some(stmt), query_ctx.clone())
323 .await,
324 ]
325 }
326 SqlPlan::Shortcut(query) => {
327 if let Some(output) =
328 crate::mysql::federated::check(&query, query_ctx.clone(), self.session.clone())
329 {
330 vec![Ok(output)]
331 } else {
332 self.do_query(&query, query_ctx.clone()).await
333 }
334 }
335 SqlPlan::Statement(stmt, query) => {
336 let param_strs = match params {
337 Params::ProtocolParams(params) => {
338 params.iter().map(convert_param_value_to_string).collect()
339 }
340 Params::CliParams(params) => params.iter().map(|x| x.to_string()).collect(),
341 };
342 debug!(
343 "do_execute Replacing with Params: {:?}, Original Query: {}",
344 param_strs, query
345 );
346 let query = replace_params(param_strs, stmt, query)?;
347 debug!("Mysql execute replaced query: {}", query);
348 self.do_query(&query, query_ctx.clone()).await
349 }
350 _ => {
351 return error::PrepareStatementNotFoundSnafu { name: stmt_key }.fail();
352 }
353 };
354
355 Ok(outputs)
356 }
357
358 fn do_close(&mut self, stmt_key: String) {
360 let mut guard = self.prepared_stmts.write();
361 let _ = guard.remove(&stmt_key);
362 }
363
364 fn auth_plugin(&self) -> &'static str {
365 self.user_provider
366 .as_ref()
367 .map(|provider| provider.mysql_auth_method())
368 .unwrap_or(MysqlAuthMethod::NativePassword)
369 .plugin_name()
370 }
371}
372
373#[async_trait]
374impl<W: AsyncWrite + Send + Sync + Unpin> AsyncMysqlShim<W> for MysqlInstanceShim {
375 type Error = error::Error;
376
377 fn version(&self) -> String {
378 std::env::var("GREPTIMEDB_MYSQL_SERVER_VERSION").unwrap_or_else(|_| "8.4.2".to_string())
379 }
380
381 fn connect_id(&self) -> u32 {
382 self.process_id
383 }
384
385 fn default_auth_plugin(&self) -> &str {
386 self.auth_plugin()
387 }
388
389 async fn auth_plugin_for_username(&self, user: &[u8]) -> &'static str {
390 if user == BEARER_TOKEN_USER.as_bytes() {
391 return MysqlAuthMethod::ClearPassword.plugin_name();
392 }
393 if let Some(provider) = &self.user_provider {
394 let username = String::from_utf8_lossy(user);
395 match provider.mysql_auth_method_for_user(&username).await {
396 Ok(method) => return method.plugin_name(),
397 Err(e) => warn!(e; "Failed to select MySQL authentication method"),
400 }
401 }
402 self.auth_plugin()
403 }
404
405 fn salt(&self) -> [u8; 20] {
406 self.salt
407 }
408
409 async fn authenticate(
410 &self,
411 auth_plugin: &str,
412 username: &[u8],
413 salt: &[u8],
414 auth_data: &[u8],
415 ) -> bool {
416 <Self as AsyncMysqlShim<W>>::authenticate_with_database(
417 self,
418 auth_plugin,
419 username,
420 salt,
421 auth_data,
422 None,
423 )
424 .await
425 }
426
427 async fn authenticate_with_database(
428 &self,
429 auth_plugin: &str,
430 username: &[u8],
431 salt: &[u8],
432 auth_data: &[u8],
433 database: Option<&[u8]>,
434 ) -> bool {
435 let username = String::from_utf8_lossy(username);
437
438 let mut user_info = None;
439 let addr = self
440 .session
441 .conn_info()
442 .client_addr
443 .map(|addr| addr.to_string());
444 if let Some(user_provider) = &self.user_provider {
445 let result = if username.as_ref() == BEARER_TOKEN_USER {
446 if auth_plugin != MysqlAuthMethod::CLEAR_PASSWORD_PLUGIN {
447 warn!("Bearer-token MySQL authentication requires mysql_clear_password");
448 return false;
449 }
450 let token = auth_data.strip_suffix(&[0]).unwrap_or(auth_data);
451 let Ok(token) = std::str::from_utf8(token) else {
452 warn!("Bearer token is not valid UTF-8");
453 return false;
454 };
455 let catalog = if let Some(database) = database {
456 let Ok(database) = std::str::from_utf8(database) else {
457 warn!("MySQL database is not valid UTF-8");
458 return false;
459 };
460 parse_optional_catalog_and_schema_from_db_string(database)
461 .0
462 .unwrap_or_else(|| self.session.catalog())
463 } else {
464 self.session.catalog()
465 };
466 user_provider
467 .authenticate_bearer_token(token, &catalog)
468 .await
469 } else {
470 let user_id = Identity::UserId(&username, addr.as_deref());
471 let password = match auth_plugin {
472 MysqlAuthMethod::NATIVE_PASSWORD_PLUGIN => {
473 Password::MysqlNativePassword(auth_data, salt)
474 }
475 MysqlAuthMethod::CLEAR_PASSWORD_PLUGIN => {
476 let password = auth_data.strip_suffix(&[0]).unwrap_or(auth_data);
479 Password::PlainText(String::from_utf8_lossy(password).to_string().into())
480 }
481 other => {
482 error!("Unsupported mysql auth plugin: {}", other);
483 return false;
484 }
485 };
486 user_provider.authenticate(user_id, password).await
487 };
488 match result {
489 Ok(userinfo) => {
490 user_info = Some(userinfo);
491 }
492 Err(e) => {
493 METRIC_AUTH_FAILURE
494 .with_label_values(&[e.status_code().as_ref()])
495 .inc();
496 warn!(e; "Failed to auth");
497 return false;
498 }
499 };
500 }
501 let user_info =
502 user_info.unwrap_or_else(|| auth::userinfo_by_name(Some(username.to_string())));
503
504 self.session.set_user_info(user_info);
505
506 true
507 }
508
509 async fn on_prepare<'a>(
510 &'a mut self,
511 raw_query: &'a str,
512 w: StatementMetaWriter<'a, W>,
513 ) -> Result<()> {
514 let query_ctx = self.new_query_context();
515 let stmt_id = self.prepared_stmts_counter.fetch_add(1, Ordering::Relaxed);
516 let stmt_key = uuid::Uuid::from_u128(stmt_id as u128).to_string();
517 let (params, columns) = match self
518 .do_prepare(raw_query, query_ctx.clone(), stmt_key)
519 .await
520 {
521 Ok(x) => x,
522 Err(e) => {
523 let (kind, msg) = handle_err(e, query_ctx.clone());
524 w.error(kind, msg.as_bytes()).await?;
525 return Ok(());
526 }
527 };
528 debug!("on_prepare: Params: {:?}, Columns: {:?}", params, columns);
529 w.reply(stmt_id, ¶ms, &columns).await?;
530 crate::metrics::METRIC_MYSQL_PREPARED_COUNT
531 .with_label_values(&[query_ctx.get_db_string().as_str()])
532 .inc();
533 return Ok(());
534 }
535
536 async fn on_execute<'a>(
537 &'a mut self,
538 stmt_id: u32,
539 p: ParamParser<'a>,
540 w: QueryResultWriter<'a, W>,
541 ) -> Result<()> {
542 self.session.clear_warnings();
543
544 let query_ctx = self.new_query_context();
545 let db = query_ctx.get_db_string();
546 let _timer = crate::metrics::METRIC_MYSQL_QUERY_TIMER
547 .with_label_values(&[crate::metrics::METRIC_MYSQL_BINQUERY, db.as_str()])
548 .start_timer();
549
550 let params: Vec<ParamValue> = p.into_iter().collect();
551 let stmt_key = uuid::Uuid::from_u128(stmt_id as u128).to_string();
552
553 let outputs = match self
554 .do_execute(query_ctx.clone(), stmt_key, Params::ProtocolParams(params))
555 .await
556 {
557 Ok(outputs) => outputs,
558 Err(e) => {
559 let (kind, err) = handle_err(e, query_ctx);
560 debug!(
561 "Failed to execute prepared statement, kind: {:?}, err: {}",
562 kind, err
563 );
564 w.error(kind, err.as_bytes()).await?;
565 return Ok(());
566 }
567 };
568
569 writer::write_output(w, query_ctx, self.session.clone(), outputs).await?;
570
571 Ok(())
572 }
573
574 async fn on_close<'a>(&'a mut self, stmt_id: u32)
575 where
576 W: 'async_trait,
577 {
578 let stmt_key = uuid::Uuid::from_u128(stmt_id as u128).to_string();
579 self.do_close(stmt_key);
580 }
581
582 #[tracing::instrument(skip_all, fields(protocol = "mysql"))]
583 async fn on_query<'a>(
584 &'a mut self,
585 query: &'a str,
586 writer: QueryResultWriter<'a, W>,
587 ) -> Result<()> {
588 let query_ctx = self.new_query_context();
589 let db = query_ctx.get_db_string();
590 let _timer = crate::metrics::METRIC_MYSQL_QUERY_TIMER
591 .with_label_values(&[crate::metrics::METRIC_MYSQL_TEXTQUERY, db.as_str()])
592 .start_timer();
593
594 let query_upcase = query.to_uppercase();
596 if !query_upcase.starts_with("SHOW WARNINGS") {
597 self.session.clear_warnings();
598 }
599
600 if query_upcase.starts_with("PREPARE ") {
601 match ParserContext::parse_mysql_prepare_stmt(query, query_ctx.sql_dialect()) {
602 Ok((stmt_name, stmt)) => {
603 let prepare_results =
604 self.do_prepare(&stmt, query_ctx.clone(), stmt_name).await;
605 match prepare_results {
606 Ok(_) => {
607 let outputs = vec![Ok(Output::new_with_affected_rows(0))];
608 writer::write_output(writer, query_ctx, self.session.clone(), outputs)
609 .await?;
610 return Ok(());
611 }
612 Err(e) => {
613 writer
614 .error(ErrorKind::ER_SP_BADSTATEMENT, e.output_msg().as_bytes())
615 .await?;
616 return Ok(());
617 }
618 }
619 }
620 Err(e) => {
621 writer
622 .error(ErrorKind::ER_PARSE_ERROR, e.output_msg().as_bytes())
623 .await?;
624 return Ok(());
625 }
626 }
627 } else if query_upcase.starts_with("EXECUTE ") {
628 match ParserContext::parse_mysql_execute_stmt(query, query_ctx.sql_dialect()) {
629 Ok((stmt_name, params)) => {
630 let outputs = match self
631 .do_execute(query_ctx.clone(), stmt_name, Params::CliParams(params))
632 .await
633 {
634 Ok(outputs) => outputs,
635 Err(e) => {
636 let (kind, err) = handle_err(e, query_ctx);
637 debug!(
638 "Failed to execute prepared statement, kind: {:?}, err: {}",
639 kind, err
640 );
641 writer.error(kind, err.as_bytes()).await?;
642 return Ok(());
643 }
644 };
645 writer::write_output(writer, query_ctx, self.session.clone(), outputs).await?;
646
647 return Ok(());
648 }
649 Err(e) => {
650 writer
651 .error(ErrorKind::ER_PARSE_ERROR, e.output_msg().as_bytes())
652 .await?;
653 return Ok(());
654 }
655 }
656 } else if query_upcase.starts_with("DEALLOCATE ") {
657 match ParserContext::parse_mysql_deallocate_stmt(query, query_ctx.sql_dialect()) {
658 Ok(stmt_name) => {
659 self.do_close(stmt_name);
660 let outputs = vec![Ok(Output::new_with_affected_rows(0))];
661 writer::write_output(writer, query_ctx, self.session.clone(), outputs).await?;
662 return Ok(());
663 }
664 Err(e) => {
665 writer
666 .error(ErrorKind::ER_PARSE_ERROR, e.output_msg().as_bytes())
667 .await?;
668 return Ok(());
669 }
670 }
671 }
672
673 let outputs = self.do_query(query, query_ctx.clone()).await;
674 writer::write_output(writer, query_ctx, self.session.clone(), outputs).await?;
675
676 Ok(())
677 }
678
679 async fn on_init<'a>(&'a mut self, database: &'a str, w: InitWriter<'a, W>) -> Result<()> {
680 let (catalog_from_db, schema) = parse_optional_catalog_and_schema_from_db_string(database);
681 let catalog = if let Some(catalog) = &catalog_from_db {
682 catalog.clone()
683 } else {
684 self.session.catalog()
685 };
686
687 if !self
688 .query_handler
689 .is_valid_schema(&catalog, &schema)
690 .await?
691 {
692 return w
693 .error(
694 ErrorKind::ER_WRONG_DB_NAME,
695 format!("Unknown database '{}'", database).as_bytes(),
696 )
697 .await
698 .map_err(|e| e.into());
699 }
700
701 let user_info = &self.session.user_info();
702
703 if let Some(schema_validator) = &self.user_provider
704 && let Err(e) = schema_validator
705 .authorize(&catalog, &schema, user_info)
706 .await
707 {
708 METRIC_AUTH_FAILURE
709 .with_label_values(&[e.status_code().as_ref()])
710 .inc();
711 return w
712 .error(
713 ErrorKind::ER_DBACCESS_DENIED_ERROR,
714 e.output_msg().as_bytes(),
715 )
716 .await
717 .map_err(|e| e.into());
718 }
719
720 if catalog_from_db.is_some() {
721 self.session.set_catalog(catalog)
722 }
723 self.session.set_schema(schema);
724
725 w.ok().await.map_err(|e| e.into())
726 }
727}
728
729fn convert_param_value_to_string(param: &ParamValue) -> String {
730 match param.value.into_inner() {
731 ValueInner::Int(u) => u.to_string(),
732 ValueInner::UInt(u) => u.to_string(),
733 ValueInner::Double(u) => u.to_string(),
734 ValueInner::NULL => "NULL".to_string(),
735 ValueInner::Bytes(b) => MysqlValue::Bytes(b.to_vec()).as_sql(false),
740 ValueInner::Date(_) => format!("'{}'", NaiveDate::from(param.value)),
741 ValueInner::Datetime(_) => format!("'{}'", NaiveDateTime::from(param.value)),
742 ValueInner::Time(_) => format_duration(Duration::from(param.value)),
743 }
744}
745
746fn replace_params(params: Vec<String>, stmt: Statement, mut query: String) -> Result<String> {
747 let spans = helper::placeholder_spans(stmt);
748 ensure!(
749 spans.len() == params.len(),
750 error::InternalSnafu {
751 err_msg: format!(
752 "Prepared statement expected {} parameters but got {}",
753 spans.len(),
754 params.len()
755 )
756 }
757 );
758
759 let mut replacements = Vec::with_capacity(spans.len());
760 for span in spans {
761 let start = location_to_byte_offset(&query, span.start_line, span.start_column)
762 .ok_or_else(|| {
763 error::InternalSnafu {
764 err_msg: format!(
765 "Invalid placeholder start span: line {}, column {}",
766 span.start_line, span.start_column
767 ),
768 }
769 .build()
770 })?;
771 let end =
772 location_to_byte_offset(&query, span.end_line, span.end_column).ok_or_else(|| {
773 error::InternalSnafu {
774 err_msg: format!(
775 "Invalid placeholder end span: line {}, column {}",
776 span.end_line, span.end_column
777 ),
778 }
779 .build()
780 })?;
781 let param = span
782 .index
783 .checked_sub(1)
784 .and_then(|idx| params.get(idx))
785 .ok_or_else(|| {
786 error::InternalSnafu {
787 err_msg: format!("Missing prepared statement parameter {}", span.index),
788 }
789 .build()
790 })?;
791
792 ensure!(
793 start < end && end <= query.len(),
794 error::InternalSnafu {
795 err_msg: format!(
796 "Invalid placeholder byte span: {}..{} for query length {}",
797 start,
798 end,
799 query.len()
800 )
801 }
802 );
803 ensure!(
804 query.get(start..end) == Some("?"),
805 error::InternalSnafu {
806 err_msg: format!(
807 "Prepared statement placeholder span maps to {:?} instead of '?'",
808 query.get(start..end)
809 )
810 }
811 );
812
813 replacements.push((start, end, param.clone()));
814 }
815
816 replacements.sort_unstable_by_key(|(start, _, _)| *start);
817 for windows in replacements.windows(2) {
818 ensure!(
819 windows[0].1 <= windows[1].0,
820 error::InternalSnafu {
821 err_msg: "Overlapping placeholder spans in prepared statement".to_string()
822 }
823 );
824 }
825
826 for (start, end, param) in replacements.into_iter().rev() {
830 query.replace_range(start..end, ¶m);
831 }
832
833 Ok(query)
834}
835
836fn location_to_byte_offset(query: &str, line: u64, column: u64) -> Option<usize> {
837 if line == 0 || column == 0 {
841 return None;
842 }
843
844 let mut current_line = 1;
845 let mut current_column = 1;
846 for (index, ch) in query.char_indices() {
847 if current_line == line && current_column == column {
848 return Some(index);
849 }
850
851 if ch == '\n' {
852 current_line += 1;
853 current_column = 1;
854 } else {
855 current_column += 1;
856 }
857 }
858
859 (current_line == line && current_column == column).then_some(query.len())
862}
863
864fn format_duration(duration: Duration) -> String {
865 let seconds = duration.as_secs() % 60;
866 let minutes = (duration.as_secs() / 60) % 60;
867 let hours = (duration.as_secs() / 60) / 60;
868 format!("'{}:{}:{}'", hours, minutes, seconds)
869}
870
871fn replace_params_with_values(
872 plan: &LogicalPlan,
873 param_types: HashMap<String, Option<ConcreteDataType>>,
874 params: &[ParamValue],
875 timezone: &Timezone,
876) -> Result<LogicalPlan> {
877 debug_assert_eq!(param_types.len(), params.len());
878
879 debug!(
880 "replace_params_with_values(param_types: {:#?}, params: {:#?}, plan: {:#?})",
881 param_types,
882 params
883 .iter()
884 .map(|x| format!("({:?}, {:?})", x.value, x.coltype))
885 .join(", "),
886 plan
887 );
888
889 let mut values = Vec::with_capacity(params.len());
890
891 for (i, param) in params.iter().enumerate() {
892 if let Some(Some(t)) = param_types.get(&format_placeholder(i + 1)) {
893 let value = helper::convert_value(param, t, timezone)?;
894
895 values.push(value.into());
896 }
897 }
898
899 plan.clone()
900 .replace_params_with_values(&ParamValues::List(values.clone()))
901 .context(DataFrameSnafu)
902}
903
904fn replace_params_with_exprs(
905 plan: &LogicalPlan,
906 param_types: HashMap<String, Option<ConcreteDataType>>,
907 params: &[sql::ast::Expr],
908 timezone: &Timezone,
909) -> Result<LogicalPlan> {
910 debug_assert_eq!(param_types.len(), params.len());
911
912 debug!(
913 "replace_params_with_exprs(param_types: {:#?}, params: {:#?}, plan: {:#?})",
914 param_types,
915 params.iter().map(|x| format!("({:?})", x)).join(", "),
916 plan
917 );
918
919 let mut values = Vec::with_capacity(params.len());
920
921 for (i, param) in params.iter().enumerate() {
922 if let Some(Some(t)) = param_types.get(&format_placeholder(i + 1)) {
923 let value = helper::convert_expr_to_scalar_value(param, t, timezone)?;
924
925 values.push(value.into());
926 }
927 }
928
929 plan.clone()
930 .replace_params_with_values(&ParamValues::List(values.clone()))
931 .context(DataFrameSnafu)
932}
933
934async fn validate_query(query: &str) -> Result<Statement> {
935 let statement =
936 ParserContext::create_with_dialect(query, &MySqlDialect {}, ParseOptions::default());
937 let mut statement = statement.map_err(|e| {
938 InvalidPrepareStatementSnafu {
939 err_msg: e.output_msg(),
940 }
941 .build()
942 })?;
943
944 ensure!(
945 statement.len() == 1,
946 InvalidPrepareStatementSnafu {
947 err_msg: "prepare statement only support single statement".to_string(),
948 }
949 );
950
951 let statement = statement.remove(0);
952
953 Ok(statement)
954}
955
956fn dummy_params(index: usize) -> Result<Vec<Column>> {
957 let mut params = Vec::with_capacity(index - 1);
958
959 for _ in 1..index {
960 params.push(create_mysql_column(&ConcreteDataType::null_datatype(), "")?);
961 }
962
963 Ok(params)
964}
965
966fn prepared_params(
968 param_types: &HashMap<String, Option<ConcreteDataType>>,
969 param_num: usize,
970) -> Result<Vec<Column>> {
971 let mut params = Vec::with_capacity(param_num - 1);
972
973 for i in 1..param_num {
975 let column = if let Some(Some(t)) = param_types.get(&format_placeholder(i)) {
976 create_mysql_column(t, "")?
977 } else {
978 create_mysql_column(&ConcreteDataType::null_datatype(), "")?
979 };
980 params.push(column);
981 }
982
983 Ok(params)
984}
985
986fn all_params_have_types(
987 param_types: &HashMap<String, Option<ConcreteDataType>>,
988 param_num: usize,
989) -> bool {
990 param_types.len() == param_num - 1
991 && (1..param_num).all(|i| matches!(param_types.get(&format_placeholder(i)), Some(Some(_))))
992}
993
994#[cfg(test)]
995mod tests {
996 use std::sync::Arc;
997
998 use async_trait::async_trait;
999 use common_query::Output;
1000 use datafusion_expr::LogicalPlan;
1001 use query::parser::PromQuery;
1002 use query::query_engine::DescribeResult;
1003 use session::context::QueryContext;
1004 use sql::statements::statement::Statement;
1005
1006 use super::*;
1007 use crate::error::Result;
1008 use crate::query_handler::sql::SqlQueryHandler;
1009
1010 struct DummyQueryHandler;
1011
1012 #[async_trait]
1013 impl SqlQueryHandler for DummyQueryHandler {
1014 async fn do_query(&self, _: &str, _: QueryContextRef) -> Vec<Result<Output>> {
1015 unimplemented!()
1016 }
1017
1018 async fn do_analyze_stream_query(&self, _: &str, _: QueryContextRef) -> Result<Output> {
1019 unimplemented!()
1020 }
1021
1022 async fn do_promql_query(&self, _: &PromQuery, _: QueryContextRef) -> Vec<Result<Output>> {
1023 unimplemented!()
1024 }
1025
1026 async fn do_exec_plan(
1027 &self,
1028 _: LogicalPlan,
1029 _: Option<Statement>,
1030 _: QueryContextRef,
1031 ) -> Result<Output> {
1032 unimplemented!()
1033 }
1034
1035 async fn do_describe(
1036 &self,
1037 _: Statement,
1038 _: QueryContextRef,
1039 ) -> Result<Option<DescribeResult>> {
1040 unimplemented!()
1041 }
1042
1043 async fn is_valid_schema(&self, _: &str, _: &str) -> Result<bool> {
1044 Ok(true)
1045 }
1046 }
1047
1048 fn create_shim() -> MysqlInstanceShim {
1049 MysqlInstanceShim::create(
1050 Arc::new(DummyQueryHandler),
1051 None,
1052 "127.0.0.1:3306".parse().unwrap(),
1053 1,
1054 1024,
1055 )
1056 }
1057
1058 #[test]
1059 fn test_prepared_statement_cache_metrics() {
1060 use common_frontend::metrics::{CACHE_HIT, CACHE_MISS};
1061
1062 let hits = CACHE_HIT.with_label_values(&["mysql_prepared_stmt"]);
1063 let misses = CACHE_MISS.with_label_values(&["mysql_prepared_stmt"]);
1064 let before = (hits.get(), misses.get());
1065 let mut shim = create_shim();
1066 assert!(shim.plan("stmt").is_none());
1067 shim.save_plan(SqlPlan::Shortcut("SELECT 1".into()), "stmt".into())
1068 .unwrap();
1069 assert!(shim.plan("stmt").is_some());
1070 shim.do_close("stmt".into());
1071 assert!(shim.plan("stmt").is_none());
1072 assert_eq!((hits.get(), misses.get()), (before.0 + 1, before.1 + 2));
1073 }
1074
1075 #[test]
1076 fn test_batching_context() {
1077 for enabled in [false, true] {
1078 let shim = create_shim().with_batching_enabled(enabled);
1079 let ctx = shim.new_query_context();
1080 assert_eq!(ctx.batching_enabled(), enabled);
1081 assert!(!ctx.logical_batching_enabled());
1082 assert_eq!(ctx.channel(), Channel::Mysql);
1083 }
1084 }
1085
1086 fn statement_with_transformed_placeholders(query: &str) -> Statement {
1087 let mut statements =
1088 ParserContext::create_with_dialect(query, &MySqlDialect {}, ParseOptions::default())
1089 .unwrap();
1090 assert_eq!(statements.len(), 1);
1091 transform_placeholders_with_count(statements.remove(0)).0
1092 }
1093
1094 #[test]
1095 fn test_prepared_params_keep_unknown_type_placeholders() {
1096 let mut param_types = HashMap::new();
1097 param_types.insert(format_placeholder(1), None);
1098 param_types.insert(
1099 format_placeholder(2),
1100 Some(ConcreteDataType::int32_datatype()),
1101 );
1102
1103 let params = prepared_params(¶m_types, 3).unwrap();
1104 assert_eq!(params.len(), 2);
1105 assert!(!all_params_have_types(¶m_types, 3));
1106 }
1107
1108 #[test]
1109 fn test_replace_params_by_placeholder_span() {
1110 let query = "SELECT ?, ?".to_string();
1111 let stmt = statement_with_transformed_placeholders(&query);
1112 let params = vec!["'$2 should stay'".to_string(), "'value'".to_string()];
1113
1114 assert_eq!(
1115 "SELECT '$2 should stay', 'value'",
1116 replace_params(params, stmt, query).unwrap()
1117 );
1118
1119 let query = "SELECT ?, ?, ?".to_string();
1120 let stmt = statement_with_transformed_placeholders(&query);
1121 let params = vec![
1122 "'much longer than a placeholder'".to_string(),
1123 "0".to_string(),
1124 "'also much longer than a placeholder'".to_string(),
1125 ];
1126
1127 assert_eq!(
1128 "SELECT 'much longer than a placeholder', 0, 'also much longer than a placeholder'",
1129 replace_params(params, stmt, query).unwrap()
1130 );
1131
1132 let query = "SELECT '$1', \"$2\", `$3`, ?, ?".to_string();
1133 let stmt = statement_with_transformed_placeholders(&query);
1134 let params = vec!["'1'".to_string(), "'2'".to_string()];
1135
1136 assert_eq!(
1137 "SELECT '$1', \"$2\", `$3`, '1', '2'",
1138 replace_params(params, stmt, query).unwrap()
1139 );
1140
1141 let query = "SELECT /* ? */ ? -- ?\n, ?".to_string();
1142 let stmt = statement_with_transformed_placeholders(&query);
1143 let params = vec!["'first'".to_string(), "'second'".to_string()];
1144
1145 assert_eq!(
1146 "SELECT /* ? */ 'first' -- ?\n, 'second'",
1147 replace_params(params, stmt, query).unwrap()
1148 );
1149
1150 let query = "SELECT '中文', ?".to_string();
1151 let stmt = statement_with_transformed_placeholders(&query);
1152 let params = vec!["'value'".to_string()];
1153
1154 assert_eq!(
1155 "SELECT '中文', 'value'",
1156 replace_params(params, stmt, query).unwrap()
1157 );
1158
1159 let query = "SELECT '中文',\n ?".to_string();
1160 let stmt = statement_with_transformed_placeholders(&query);
1161 let params = vec!["'value'".to_string()];
1162
1163 assert_eq!(
1164 "SELECT '中文',\n 'value'",
1165 replace_params(params, stmt, query).unwrap()
1166 );
1167
1168 let query = "SELECT 'x'\r\n, ?".to_string();
1169 let stmt = statement_with_transformed_placeholders(&query);
1170 let params = vec!["'crlf'".to_string()];
1171
1172 assert_eq!(
1173 "SELECT 'x'\r\n, 'crlf'",
1174 replace_params(params, stmt, query).unwrap()
1175 );
1176
1177 let query = "SELECT\t?".to_string();
1178 let stmt = statement_with_transformed_placeholders(&query);
1179 let params = vec!["NULL".to_string()];
1180
1181 assert_eq!("SELECT\tNULL", replace_params(params, stmt, query).unwrap());
1182
1183 let query = "SELECT CAST(? AS INT64), ? + (SELECT ?)".to_string();
1184 let stmt = statement_with_transformed_placeholders(&query);
1185 let params = vec!["1".to_string(), "2".to_string(), "3".to_string()];
1186
1187 assert_eq!(
1188 "SELECT CAST(1 AS INT64), 2 + (SELECT 3)",
1189 replace_params(params, stmt, query).unwrap()
1190 );
1191
1192 let query = "SET time_zone = ?".to_string();
1193 let stmt = statement_with_transformed_placeholders(&query);
1194 let params = vec!["'UTC'".to_string()];
1195
1196 assert_eq!(
1197 "SET time_zone = 'UTC'",
1198 replace_params(params, stmt, query).unwrap()
1199 );
1200 }
1201
1202 #[tokio::test]
1203 async fn test_prepare_federated_query() {
1204 let mut shim = create_shim();
1205 let query_ctx = QueryContext::arc();
1206 let stmt_key = "test_federated".to_string();
1207
1208 let (params, columns) = shim
1209 .do_prepare(
1210 "SELECT @@version_comment",
1211 query_ctx.clone(),
1212 stmt_key.clone(),
1213 )
1214 .await
1215 .unwrap();
1216
1217 assert!(params.is_empty());
1218 assert!(columns.is_empty());
1219
1220 let plan = shim.plan(&stmt_key).unwrap();
1221 assert!(matches!(plan, SqlPlan::Shortcut(q) if q == "SELECT @@version_comment"));
1222 }
1223
1224 #[tokio::test]
1225 async fn test_execute_federated_shortcut() {
1226 let mut shim = create_shim();
1227 let query_ctx = QueryContext::arc();
1228 let stmt_key = "test_federated_exec".to_string();
1229
1230 shim.do_prepare(
1231 "SELECT @@version_comment",
1232 query_ctx.clone(),
1233 stmt_key.clone(),
1234 )
1235 .await
1236 .unwrap();
1237
1238 let outputs = shim
1239 .do_execute(query_ctx.clone(), stmt_key, Params::CliParams(vec![]))
1240 .await
1241 .unwrap();
1242
1243 assert_eq!(outputs.len(), 1);
1244 let output = outputs.into_iter().next().unwrap().unwrap();
1245 let pretty = output.data.pretty_print().await;
1246 assert!(pretty.contains("GreptimeDB"));
1247 }
1248
1249 #[tokio::test]
1250 async fn test_prepare_non_federated_query_not_shortcut() {
1251 let mut shim = create_shim();
1252 let query_ctx = QueryContext::arc();
1253 let stmt_key = "test_non_federated".to_string();
1254
1255 let result = shim
1256 .do_prepare("SET NAMES utf8", query_ctx.clone(), stmt_key.clone())
1257 .await;
1258
1259 assert!(result.is_ok());
1260 let plan = shim.plan(&stmt_key).unwrap();
1261 assert!(matches!(plan, SqlPlan::Shortcut(_)));
1262 }
1263
1264 #[tokio::test]
1265 async fn test_execute_set_shortcut() {
1266 let mut shim = create_shim();
1267 let query_ctx = QueryContext::arc();
1268 let stmt_key = "test_set_shortcut".to_string();
1269
1270 shim.do_prepare("SET NAMES utf8", query_ctx.clone(), stmt_key.clone())
1271 .await
1272 .unwrap();
1273
1274 let outputs = shim
1275 .do_execute(query_ctx.clone(), stmt_key, Params::CliParams(vec![]))
1276 .await
1277 .unwrap();
1278
1279 assert_eq!(outputs.len(), 1);
1280 let output = outputs.into_iter().next().unwrap().unwrap();
1281 match output.data {
1282 common_query::OutputData::RecordBatches(batches) => {
1283 let total_rows: usize = batches.iter().map(|b| b.num_rows()).sum();
1284 assert_eq!(total_rows, 0);
1285 }
1286 other => panic!("Expected RecordBatches, got {:?}", other),
1287 }
1288 }
1289}