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