Skip to main content

servers/mysql/
handler.rs

1// Copyright 2023 Greptime Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use 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
62/// Parameters for the prepared statement
63enum Params<'a> {
64    /// Parameters passed through protocol
65    ProtocolParams(Vec<ParamValue<'a>>),
66    /// Parameters passed through cli
67    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
79// An intermediate shim for executing MySQL queries.
80pub 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        // init a random salt
101        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    /// Enables ordinary-table batching for this connection.
132    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    /// Describe the statement
155    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    /// Save query and logical plan with a given statement key
164    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    /// Retrieve the query and logical plan by a given statement key
185    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    /// Save the prepared statement and return the parameters and result columns
192    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        // We have to transform the placeholder, because DataFusion only parses placeholders
211        // in the form of "$i", it can't process "?" right now.
212        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(&param_types, param_num)?,
229                all_params_have_types(&param_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, &params, &timezone)
310                    }
311                    Params::CliParams(params) => {
312                        replace_params_with_exprs(&plan, param_types, &params, &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    /// Remove the prepared statement by a given statement key
359    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                // This hook cannot return an error. Keep the default challenge;
398                // authentication still validates the credentials separately.
399                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        // if not specified then **greptime** will be used
436        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                        // The raw bytes received could be represented in C-like string, ended in '\0'.
477                        // We must "trim" it to get the real password string.
478                        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, &params, &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        // Clear warnings for non SHOW WARNINGS queries
595        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        // MySQL prepared fallback emits SQL text. Delegate bytes/string literal
736        // escaping to mysql_common. `false` means normal MySQL backslash escapes;
737        // if NO_BACKSLASH_ESCAPES is supported in this path later, wire the
738        // session SQL mode here.
739        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    // All spans are computed against the original query. Apply replacements
827    // from right to left so changing one parameter's string length never shifts
828    // the byte offsets of placeholders that have not been replaced yet.
829    for (start, end, param) in replacements.into_iter().rev() {
830        query.replace_range(start..end, &param);
831    }
832
833    Ok(query)
834}
835
836fn location_to_byte_offset(query: &str, line: u64, column: u64) -> Option<usize> {
837    // sqlparser spans are 1-based line/column locations, and columns advance by
838    // Rust `char`s rather than bytes. Convert them to byte offsets before using
839    // `String::replace_range` on the original SQL text.
840    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    // The exclusive end location of a trailing placeholder points just past
860    // the last character, for example the end span of `SELECT ?`.
861    (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
966/// Parameters that the client must provide when executing the prepared statement.
967fn 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    // Placeholder index starts from 1
974    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(&param_types, 3).unwrap();
1104        assert_eq!(params.len(), 2);
1105        assert!(!all_params_have_types(&param_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}