Skip to main content

session/
context.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::fmt::{Display, Formatter};
17use std::net::SocketAddr;
18use std::str::FromStr;
19use std::sync::{Arc, RwLock};
20use std::time::Duration;
21
22use api::v1::ExplainOptions;
23use api::v1::region::RegionRequestHeader;
24use arc_swap::ArcSwap;
25use auth::UserInfoRef;
26pub use common_base::protocol::Channel;
27use common_catalog::consts::{DEFAULT_CATALOG_NAME, DEFAULT_SCHEMA_NAME};
28use common_catalog::{build_db_string, parse_catalog_and_schema_from_db_string};
29use common_recordbatch::cursor::RecordBatchStreamCursor;
30use common_telemetry::warn;
31use common_time::Timezone;
32use common_time::timezone::parse_timezone;
33use datafusion_common::config::ConfigOptions;
34use derive_builder::Builder;
35use sql::dialect::{Dialect, GenericDialect, GreptimeDbDialect, MySqlDialect, PostgreSqlDialect};
36
37pub use crate::hints::{
38    LIVE_ANALYZE_METRICS_EXTENSION_KEY, READ_PREFERENCE_EXTENSION_KEY,
39    REMOTE_QUERY_ID_EXTENSION_KEY, SUPPORT_FLIGHT_METRICS_BEFORE_BATCH_EXTENSION_KEY,
40};
41use crate::protocol_ctx::ProtocolCtx;
42use crate::query_id::QueryId;
43use crate::session_config::{
44    InvalidConfigValueSnafu, PGByteaOutputValue, PGDateOrder, PGDateTimeStyle, PGIntervalStyle,
45};
46use crate::{MutableInner, ReadPreference};
47
48pub type QueryContextRef = Arc<QueryContext>;
49pub type ConnInfoRef = Arc<ConnInfo>;
50
51pub const FLIGHT_METRICS_HEARTBEAT_INTERVAL: Duration = Duration::from_secs(1);
52
53const CURSOR_COUNT_WARNING_LIMIT: usize = 10;
54
55pub fn generate_remote_query_id() -> String {
56    generate_remote_query_id_value().to_string()
57}
58
59pub fn generate_remote_query_id_value() -> QueryId {
60    QueryId::new()
61}
62
63#[derive(Debug, Builder, Clone)]
64#[builder(pattern = "owned")]
65#[builder(build_fn(skip))]
66pub struct QueryContext {
67    current_catalog: String,
68    /// mapping of RegionId to SequenceNumber, for snapshot read, meaning that the read should only
69    /// container data that was committed before(and include) the given sequence number
70    snapshot_seqs: Arc<RwLock<HashMap<u64, u64>>>,
71    /// Mappings of the RegionId to the minimal sequence of SST file to scan.
72    sst_min_sequences: Arc<RwLock<HashMap<u64, u64>>>,
73    // we use Arc<RwLock>> for modifiable fields
74    #[builder(default)]
75    mutable_session_data: Arc<RwLock<MutableInner>>,
76    #[builder(default)]
77    mutable_query_context_data: Arc<RwLock<QueryContextMutableFields>>,
78    sql_dialect: Arc<dyn Dialect + Send + Sync>,
79    #[builder(default)]
80    extensions: HashMap<String, String>,
81    #[builder(setter(skip))]
82    query_options_snapshotted: bool,
83    /// The configuration parameter are used to store the parameters that are set by the user
84    #[builder(default)]
85    configuration_parameter: Arc<ConfigurationVariables>,
86    /// Local-only write batching selection; never transported in protobuf extensions.
87    #[builder(default)]
88    batching_enabled: bool,
89    /// Local-only opt-in for logical metric writes, independent of ordinary-table batching.
90    #[builder(default)]
91    logical_batching_enabled: bool,
92    /// Track which protocol the query comes from.
93    #[builder(default)]
94    channel: Channel,
95    /// Process-local admission for one database in this request. Never sent over the wire.
96    #[builder(setter(skip))]
97    admitted_write: Option<(String, String)>,
98    /// Process id for managing on-going queries
99    #[builder(default)]
100    process_id: u32,
101    /// Connection information
102    #[builder(default)]
103    conn_info: ConnInfo,
104    /// Protocol specific context
105    #[builder(default)]
106    protocol_ctx: ProtocolCtx,
107}
108
109/// This fields hold data that is only valid to current query context
110#[derive(Debug, Builder, Clone, Default)]
111pub struct QueryContextMutableFields {
112    warning: Option<String>,
113    // TODO: remove this when format is supported in datafusion
114    explain_format: Option<String>,
115    /// Explain options to control the verbose analyze output.
116    explain_options: Option<ExplainOptions>,
117}
118
119impl Display for QueryContext {
120    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
121        write!(
122            f,
123            "QueryContext{{catalog: {}, schema: {}}}",
124            self.current_catalog(),
125            self.current_schema()
126        )
127    }
128}
129
130impl QueryContextBuilder {
131    pub fn current_schema(mut self, schema: String) -> Self {
132        if self.mutable_session_data.is_none() {
133            self.mutable_session_data = Some(Arc::new(RwLock::new(MutableInner::default())));
134        }
135
136        // safe for unwrap because previous none check
137        self.mutable_session_data
138            .as_mut()
139            .unwrap()
140            .write()
141            .unwrap()
142            .schema = schema;
143        self
144    }
145
146    pub fn timezone(mut self, timezone: Timezone) -> Self {
147        if self.mutable_session_data.is_none() {
148            self.mutable_session_data = Some(Arc::new(RwLock::new(MutableInner::default())));
149        }
150
151        self.mutable_session_data
152            .as_mut()
153            .unwrap()
154            .write()
155            .unwrap()
156            .timezone = timezone;
157        self
158    }
159
160    pub fn explain_options(mut self, explain_options: Option<ExplainOptions>) -> Self {
161        self.mutable_query_context_data
162            .get_or_insert_default()
163            .write()
164            .unwrap()
165            .explain_options = explain_options;
166        self
167    }
168
169    pub fn skip_wal(mut self, skip_wal: bool) -> Self {
170        self.mutable_session_data
171            .get_or_insert_default()
172            .write()
173            .unwrap()
174            .skip_wal = skip_wal;
175        self
176    }
177
178    pub fn read_preference(mut self, read_preference: ReadPreference) -> Self {
179        self.mutable_session_data
180            .get_or_insert_default()
181            .write()
182            .unwrap()
183            .read_preference = read_preference;
184        self
185    }
186}
187
188impl TryFrom<&RegionRequestHeader> for QueryContext {
189    type Error = crate::session_config::Error;
190
191    fn try_from(value: &RegionRequestHeader) -> Result<Self, Self::Error> {
192        if let Some(ctx) = &value.query_context {
193            ctx.clone().try_into()
194        } else {
195            Ok(QueryContextBuilder::default()
196                .set_extension(
197                    REMOTE_QUERY_ID_EXTENSION_KEY.to_string(),
198                    generate_remote_query_id(),
199                )
200                .build())
201        }
202    }
203}
204
205impl TryFrom<api::v1::QueryContext> for QueryContext {
206    type Error = crate::session_config::Error;
207
208    fn try_from(mut ctx: api::v1::QueryContext) -> Result<Self, Self::Error> {
209        let read_preference = ctx.extensions.remove(READ_PREFERENCE_EXTENSION_KEY);
210        let read_preference = match read_preference {
211            Some(value) => ReadPreference::from_str(&value).map_err(|_| {
212                InvalidConfigValueSnafu {
213                    name: "read_preference",
214                    value,
215                    hint: "Expected a supported read preference",
216                }
217                .build()
218            })?,
219            None => ReadPreference::default(),
220        };
221        let sequences = ctx.snapshot_seqs.as_ref();
222        Ok(QueryContextBuilder::default()
223            .current_catalog(ctx.current_catalog)
224            .current_schema(ctx.current_schema)
225            .timezone(parse_timezone(Some(&ctx.timezone)))
226            .extensions(ctx.extensions)
227            .channel(ctx.channel.into())
228            .snapshot_seqs(Arc::new(RwLock::new(
229                sequences
230                    .map(|x| x.snapshot_seqs.clone())
231                    .unwrap_or_default(),
232            )))
233            .sst_min_sequences(Arc::new(RwLock::new(
234                sequences
235                    .map(|x| x.sst_min_sequences.clone())
236                    .unwrap_or_default(),
237            )))
238            .explain_options(ctx.explain)
239            .read_preference(read_preference)
240            .build())
241    }
242}
243
244impl From<QueryContext> for api::v1::QueryContext {
245    fn from(
246        QueryContext {
247            current_catalog,
248            mutable_session_data: mutable_inner,
249            extensions,
250            channel,
251            snapshot_seqs,
252            sst_min_sequences,
253            mutable_query_context_data,
254            ..
255        }: QueryContext,
256    ) -> Self {
257        let explain = mutable_query_context_data.read().unwrap().explain_options;
258        let mutable_inner = mutable_inner.read().unwrap();
259        let mut extensions = extensions;
260        extensions.remove(READ_PREFERENCE_EXTENSION_KEY);
261        if mutable_inner.read_preference != ReadPreference::Leader {
262            extensions.insert(
263                READ_PREFERENCE_EXTENSION_KEY.to_string(),
264                mutable_inner
265                    .read_preference
266                    .to_string()
267                    .to_ascii_lowercase(),
268            );
269        }
270        api::v1::QueryContext {
271            current_catalog,
272            current_schema: mutable_inner.schema.clone(),
273            timezone: mutable_inner.timezone.to_string(),
274            extensions,
275            channel: channel as u32,
276            snapshot_seqs: Some(api::v1::SnapshotSequences {
277                snapshot_seqs: snapshot_seqs.read().unwrap().clone(),
278                sst_min_sequences: sst_min_sequences.read().unwrap().clone(),
279            }),
280            explain,
281        }
282    }
283}
284
285impl From<&QueryContext> for api::v1::QueryContext {
286    fn from(ctx: &QueryContext) -> Self {
287        ctx.clone().into()
288    }
289}
290
291impl QueryContext {
292    /// Forks this context with an independent snapshot of mutable session data.
293    ///
294    /// Unlike [`Clone`], changes to the schema, user, timezone, and other fields
295    /// held in mutable session data do not affect this context.
296    pub fn fork(&self) -> Self {
297        let mut fork = self.clone();
298        fork.mutable_session_data = Arc::new(RwLock::new(
299            self.mutable_session_data.read().unwrap().clone(),
300        ));
301        fork
302    }
303
304    /// Forks a context for internal writes after admitting the complete request.
305    /// Call only after write admission succeeds for the current database.
306    pub fn with_write_admission(&self) -> Self {
307        let mut ctx = self.fork();
308        ctx.admitted_write = Some((ctx.current_catalog().to_string(), ctx.current_schema()));
309        ctx
310    }
311
312    /// Returns zero for writes covered by this request's database admission.
313    /// Usage accounting and the original protocol channel remain unchanged.
314    pub fn write_rows_to_admit(&self, catalog: &str, schema: &str, rows: u64) -> u64 {
315        if self
316            .admitted_write
317            .as_ref()
318            .is_some_and(|(c, s)| c == catalog && s == schema)
319        {
320            0
321        } else {
322            rows
323        }
324    }
325
326    pub fn arc() -> QueryContextRef {
327        Arc::new(
328            QueryContextBuilder::default()
329                .set_extension(
330                    REMOTE_QUERY_ID_EXTENSION_KEY.to_string(),
331                    generate_remote_query_id(),
332                )
333                .build(),
334        )
335    }
336
337    /// Create a new  datafusion's ConfigOptions instance based on the current QueryContext.
338    pub fn create_config_options(&self) -> ConfigOptions {
339        let mut config = ConfigOptions::default();
340        config.execution.time_zone = Some(self.timezone().to_string());
341        config
342    }
343
344    pub fn with(catalog: &str, schema: &str) -> QueryContext {
345        QueryContextBuilder::default()
346            .current_catalog(catalog.to_string())
347            .current_schema(schema.to_string())
348            .set_extension(
349                REMOTE_QUERY_ID_EXTENSION_KEY.to_string(),
350                generate_remote_query_id(),
351            )
352            .build()
353    }
354
355    pub fn with_channel(catalog: &str, schema: &str, channel: Channel) -> QueryContext {
356        QueryContextBuilder::default()
357            .current_catalog(catalog.to_string())
358            .current_schema(schema.to_string())
359            .channel(channel)
360            .set_extension(
361                REMOTE_QUERY_ID_EXTENSION_KEY.to_string(),
362                generate_remote_query_id(),
363            )
364            .build()
365    }
366
367    pub fn with_db_name(db_name: Option<&str>) -> QueryContext {
368        let (catalog, schema) = db_name
369            .map(|db| {
370                let (catalog, schema) = parse_catalog_and_schema_from_db_string(db);
371                (catalog, schema)
372            })
373            .unwrap_or_else(|| {
374                (
375                    DEFAULT_CATALOG_NAME.to_string(),
376                    DEFAULT_SCHEMA_NAME.to_string(),
377                )
378            });
379        QueryContextBuilder::default()
380            .current_catalog(catalog)
381            .current_schema(schema.clone())
382            .set_extension(
383                REMOTE_QUERY_ID_EXTENSION_KEY.to_string(),
384                generate_remote_query_id(),
385            )
386            .build()
387    }
388
389    pub fn current_schema(&self) -> String {
390        self.mutable_session_data.read().unwrap().schema.clone()
391    }
392
393    pub fn set_current_schema(&self, new_schema: &str) {
394        self.mutable_session_data.write().unwrap().schema = new_schema.to_string();
395    }
396
397    pub fn current_catalog(&self) -> &str {
398        &self.current_catalog
399    }
400
401    pub fn set_current_catalog(&mut self, new_catalog: &str) {
402        self.current_catalog = new_catalog.to_string();
403    }
404
405    pub fn sql_dialect(&self) -> &(dyn Dialect + Send + Sync) {
406        &*self.sql_dialect
407    }
408
409    pub fn get_db_string(&self) -> String {
410        let catalog = self.current_catalog();
411        let schema = self.current_schema();
412        build_db_string(catalog, &schema)
413    }
414
415    pub fn timezone(&self) -> Timezone {
416        self.mutable_session_data.read().unwrap().timezone.clone()
417    }
418
419    pub fn set_timezone(&self, timezone: Timezone) {
420        self.mutable_session_data.write().unwrap().timezone = timezone;
421    }
422
423    /// Returns whether ordinary inserts in this request should skip WAL.
424    pub fn skip_wal(&self) -> bool {
425        self.mutable_session_data.read().unwrap().skip_wal
426    }
427
428    pub fn set_skip_wal(&self, skip_wal: bool) {
429        self.mutable_session_data.write().unwrap().skip_wal = skip_wal;
430    }
431
432    pub fn read_preference(&self) -> ReadPreference {
433        self.mutable_session_data.read().unwrap().read_preference
434    }
435
436    pub fn set_read_preference(&self, read_preference: ReadPreference) {
437        self.mutable_session_data.write().unwrap().read_preference = read_preference;
438    }
439
440    /// Returns the latest procedure ID recorded in this Session.
441    pub fn last_procedure_id(&self) -> Option<String> {
442        self.mutable_session_data
443            .read()
444            .unwrap()
445            .last_procedure_id
446            .clone()
447    }
448
449    /// Records a procedure ID after successful statement execution and
450    /// construction of its ID result.
451    ///
452    /// Recording precedes `post_execute` hooks and protocol transmission, which
453    /// may still fail. It does not confirm that the client received the ID.
454    pub fn set_last_procedure_id(&self, procedure_id: String) {
455        self.mutable_session_data.write().unwrap().last_procedure_id = Some(procedure_id);
456    }
457
458    pub fn current_user(&self) -> UserInfoRef {
459        self.mutable_session_data.read().unwrap().user_info.clone()
460    }
461
462    pub fn set_current_user(&self, user: UserInfoRef) {
463        self.mutable_session_data.write().unwrap().user_info = user;
464    }
465
466    pub fn set_extension<S1: Into<String>, S2: Into<String>>(&mut self, key: S1, value: S2) {
467        self.extensions.insert(key.into(), value.into());
468    }
469
470    #[cfg(feature = "enterprise")]
471    pub fn remove_extension(&mut self, key: &str) {
472        self.extensions.remove(key);
473    }
474
475    pub fn extension<S: AsRef<str>>(&self, key: S) -> Option<&str> {
476        self.extensions.get(key.as_ref()).map(|v| v.as_str())
477    }
478
479    pub fn remote_query_id(&self) -> Option<&str> {
480        self.extension(REMOTE_QUERY_ID_EXTENSION_KEY)
481    }
482
483    pub fn remote_query_id_value(&self) -> Option<QueryId> {
484        self.remote_query_id()
485            .and_then(|query_id| query_id.parse().ok())
486    }
487
488    pub fn enable_live_analyze_metrics(&mut self) {
489        if let Some(remote_query_id) = self.remote_query_id().map(str::to_string) {
490            self.set_extension(LIVE_ANALYZE_METRICS_EXTENSION_KEY, remote_query_id);
491        }
492    }
493
494    pub fn live_analyze_metrics_enabled(&self) -> bool {
495        self.remote_query_id()
496            .zip(self.extension(LIVE_ANALYZE_METRICS_EXTENSION_KEY))
497            .is_some_and(|(remote_query_id, value)| value == remote_query_id)
498    }
499
500    pub fn extensions(&self) -> HashMap<String, String> {
501        self.extensions.clone()
502    }
503
504    /// Returns normalized query options from the request over session defaults.
505    pub fn effective_query_options(
506        &self,
507    ) -> Result<HashMap<String, String>, crate::session_config::Error> {
508        let mut request_options = HashMap::new();
509        for (key, value) in &self.extensions {
510            if crate::hints::is_reserved_extension_key(key) {
511                continue;
512            }
513            // Older Frontends and Flownodes forward `query_parallelism=0` (their
514            // "use the server default" encoding) untouched inside the wire
515            // query context, so accept it as unset instead of failing the
516            // query on the Datanode side. The canonical
517            // `query.parallelism = 0` stays rejected.
518            if key.eq_ignore_ascii_case("query_parallelism") && value == "0" {
519                continue;
520            }
521            // Wire decoding preserves legacy frontend nonzero values; public entry points enforce the limit.
522            if let Some((canonical, normalized)) =
523                crate::query_options::parse_query_option(key, value, u16::MAX)?
524            {
525                if request_options
526                    .get(&canonical)
527                    .is_some_and(|previous| previous != &normalized)
528                {
529                    return crate::session_config::InvalidConfigValueSnafu {
530                        name: key,
531                        value,
532                        hint: "Conflicting aliases for the same query option",
533                    }
534                    .fail();
535                }
536                request_options.insert(canonical, normalized);
537            }
538        }
539        let mut effective = if self.query_options_snapshotted {
540            HashMap::new()
541        } else {
542            self.configuration_parameter.query_options()
543        };
544        effective.extend(request_options);
545        Ok(effective)
546    }
547
548    /// Creates a per-query immutable option snapshot while retaining shared mutable session data.
549    pub fn query_option_snapshot(&self) -> Result<Self, crate::session_config::Error> {
550        let mut snapshot = self.clone();
551        let options = snapshot.effective_query_options()?;
552        snapshot
553            .extensions
554            .retain(|key, _| crate::query_options::canonical_query_option_name(key).is_none());
555        // Older Datanodes only understand the legacy `query_parallelism` hint key, so
556        // mirror the canonical parallelism override for the wire format while keeping
557        // the canonical key for in-process consumers.
558        let legacy_parallelism = options
559            .get("query.parallelism")
560            .map(|parallelism| ("query_parallelism".to_string(), parallelism.clone()));
561        snapshot.extensions.extend(options);
562        if let Some((key, value)) = legacy_parallelism {
563            snapshot.extensions.entry(key).or_insert(value);
564        }
565        snapshot.query_options_snapshotted = true;
566        Ok(snapshot)
567    }
568
569    /// Default to double quote and fallback to back quote
570    pub fn quote_style(&self) -> char {
571        if self.sql_dialect().is_delimited_identifier_start('"') {
572            '"'
573        } else if self.sql_dialect().is_delimited_identifier_start('\'') {
574            '\''
575        } else {
576            '`'
577        }
578    }
579
580    pub fn configuration_parameter(&self) -> &ConfigurationVariables {
581        &self.configuration_parameter
582    }
583
584    /// Whether the local HTTP entry point selected logical-table batching.
585    pub fn logical_batching_enabled(&self) -> bool {
586        self.logical_batching_enabled
587    }
588
589    /// Sets local logical-table batching selection without adding a wire-visible extension.
590    pub fn set_logical_batching_enabled(&mut self, enabled: bool) {
591        self.logical_batching_enabled = enabled;
592    }
593
594    /// Whether the local protocol entry point selected ordinary-table batching.
595    pub fn batching_enabled(&self) -> bool {
596        self.batching_enabled
597    }
598
599    /// Sets local write batching selection without adding a wire-visible extension.
600    pub fn set_batching_enabled(&mut self, enabled: bool) {
601        self.batching_enabled = enabled;
602    }
603
604    pub fn channel(&self) -> Channel {
605        self.channel
606    }
607
608    pub fn set_channel(&mut self, channel: Channel) {
609        self.channel = channel;
610    }
611
612    pub fn warning(&self) -> Option<String> {
613        self.mutable_query_context_data
614            .read()
615            .unwrap()
616            .warning
617            .clone()
618    }
619
620    pub fn set_warning(&self, msg: String) {
621        self.mutable_query_context_data.write().unwrap().warning = Some(msg);
622    }
623
624    pub fn explain_format(&self) -> Option<String> {
625        self.mutable_query_context_data
626            .read()
627            .unwrap()
628            .explain_format
629            .clone()
630    }
631
632    pub fn set_explain_format(&self, format: String) {
633        self.mutable_query_context_data
634            .write()
635            .unwrap()
636            .explain_format = Some(format);
637    }
638
639    pub fn explain_verbose(&self) -> bool {
640        self.mutable_query_context_data
641            .read()
642            .unwrap()
643            .explain_options
644            .map(|opts| opts.verbose)
645            .unwrap_or(false)
646    }
647
648    pub fn set_explain_verbose(&self, verbose: bool) {
649        self.mutable_query_context_data
650            .write()
651            .unwrap()
652            .explain_options
653            .get_or_insert_default()
654            .verbose = verbose;
655    }
656
657    pub fn query_timeout(&self) -> Option<Duration> {
658        self.mutable_session_data.read().unwrap().query_timeout
659    }
660
661    pub fn query_timeout_as_millis(&self) -> u128 {
662        let timeout = self.mutable_session_data.read().unwrap().query_timeout;
663        if let Some(t) = timeout {
664            return t.as_millis();
665        }
666        0
667    }
668
669    pub fn set_query_timeout(&self, timeout: Duration) {
670        self.mutable_session_data.write().unwrap().query_timeout = Some(timeout);
671    }
672
673    pub fn insert_cursor(&self, name: String, rb: RecordBatchStreamCursor) {
674        let mut guard = self.mutable_session_data.write().unwrap();
675        guard.cursors.insert(name, Arc::new(rb));
676
677        let cursor_count = guard.cursors.len();
678        if cursor_count > CURSOR_COUNT_WARNING_LIMIT {
679            warn!("Current connection has {} open cursors", cursor_count);
680        }
681    }
682
683    pub fn remove_cursor(&self, name: &str) {
684        let mut guard = self.mutable_session_data.write().unwrap();
685        guard.cursors.remove(name);
686    }
687
688    pub fn get_cursor(&self, name: &str) -> Option<Arc<RecordBatchStreamCursor>> {
689        let guard = self.mutable_session_data.read().unwrap();
690        let rb = guard.cursors.get(name);
691        rb.cloned()
692    }
693
694    pub fn snapshots(&self) -> HashMap<u64, u64> {
695        self.snapshot_seqs.read().unwrap().clone()
696    }
697
698    pub fn sst_min_sequences(&self) -> HashMap<u64, u64> {
699        self.sst_min_sequences.read().unwrap().clone()
700    }
701
702    pub fn get_snapshot(&self, region_id: u64) -> Option<u64> {
703        self.snapshot_seqs.read().unwrap().get(&region_id).cloned()
704    }
705
706    pub fn set_snapshot(&self, region_id: u64, sequence: u64) {
707        self.snapshot_seqs
708            .write()
709            .unwrap()
710            .insert(region_id, sequence);
711    }
712
713    /// Returns `true` if the session can cast strings to numbers in MySQL style.
714    pub fn auto_string_to_numeric(&self) -> bool {
715        matches!(self.channel, Channel::Mysql)
716    }
717
718    /// Finds the minimal sequence of SST files to scan of a Region.
719    pub fn sst_min_sequence(&self, region_id: u64) -> Option<u64> {
720        self.sst_min_sequences
721            .read()
722            .unwrap()
723            .get(&region_id)
724            .copied()
725    }
726
727    pub fn process_id(&self) -> u32 {
728        self.process_id
729    }
730
731    /// Get client information
732    pub fn conn_info(&self) -> &ConnInfo {
733        &self.conn_info
734    }
735
736    pub fn protocol_ctx(&self) -> &ProtocolCtx {
737        &self.protocol_ctx
738    }
739
740    pub fn set_protocol_ctx(&mut self, protocol_ctx: ProtocolCtx) {
741        self.protocol_ctx = protocol_ctx;
742    }
743}
744
745impl QueryContextBuilder {
746    pub fn build(self) -> QueryContext {
747        let channel = self.channel.unwrap_or_default();
748        let mut extensions = self.extensions.unwrap_or_default();
749        extensions
750            .entry(REMOTE_QUERY_ID_EXTENSION_KEY.to_string())
751            .or_insert_with(generate_remote_query_id);
752        QueryContext {
753            current_catalog: self
754                .current_catalog
755                .unwrap_or_else(|| DEFAULT_CATALOG_NAME.to_string()),
756            snapshot_seqs: self.snapshot_seqs.unwrap_or_default(),
757            sst_min_sequences: self.sst_min_sequences.unwrap_or_default(),
758            mutable_session_data: self.mutable_session_data.unwrap_or_default(),
759            mutable_query_context_data: self.mutable_query_context_data.unwrap_or_default(),
760            sql_dialect: self
761                .sql_dialect
762                .unwrap_or_else(|| Arc::new(GreptimeDbDialect {})),
763            extensions,
764            query_options_snapshotted: false,
765            configuration_parameter: self
766                .configuration_parameter
767                .unwrap_or_else(|| Arc::new(ConfigurationVariables::default())),
768            channel,
769            batching_enabled: self.batching_enabled.unwrap_or_default(),
770            admitted_write: None,
771            logical_batching_enabled: self.logical_batching_enabled.unwrap_or_default(),
772            process_id: self.process_id.unwrap_or_default(),
773            conn_info: self.conn_info.unwrap_or_default(),
774            protocol_ctx: self.protocol_ctx.unwrap_or_default(),
775        }
776    }
777
778    pub fn set_extension(mut self, key: String, value: String) -> Self {
779        self.extensions
780            .get_or_insert_with(HashMap::new)
781            .insert(key, value);
782        self
783    }
784}
785
786#[derive(Debug, Clone, Default)]
787pub struct ConnInfo {
788    pub client_addr: Option<SocketAddr>,
789    pub channel: Channel,
790}
791
792impl Display for ConnInfo {
793    fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
794        write!(
795            f,
796            "{}[{}]",
797            self.channel,
798            self.client_addr
799                .map(|addr| addr.to_string())
800                .as_deref()
801                .unwrap_or("unknown client addr")
802        )
803    }
804}
805
806impl ConnInfo {
807    pub fn new(client_addr: Option<SocketAddr>, channel: Channel) -> Self {
808        Self {
809            client_addr,
810            channel,
811        }
812    }
813}
814
815/// Returns the SQL dialect for the given query channel.
816pub fn dialect_for_channel(channel: Channel) -> Arc<dyn Dialect + Send + Sync> {
817    match channel {
818        Channel::Mysql => Arc::new(MySqlDialect {}),
819        Channel::Postgres => Arc::new(PostgreSqlDialect {}),
820        _ => Arc::new(GenericDialect {}),
821    }
822}
823
824#[derive(Default, Debug)]
825pub struct ConfigurationVariables {
826    postgres_bytea_output: ArcSwap<PGByteaOutputValue>,
827    pg_datestyle_format: ArcSwap<(PGDateTimeStyle, PGDateOrder)>,
828    pg_intervalstyle_format: ArcSwap<PGIntervalStyle>,
829    query_options: RwLock<HashMap<String, String>>,
830}
831
832impl Clone for ConfigurationVariables {
833    fn clone(&self) -> Self {
834        Self {
835            postgres_bytea_output: ArcSwap::new(self.postgres_bytea_output.load().clone()),
836            pg_datestyle_format: ArcSwap::new(self.pg_datestyle_format.load().clone()),
837            pg_intervalstyle_format: ArcSwap::new(self.pg_intervalstyle_format.load().clone()),
838            query_options: RwLock::new(self.query_options.read().unwrap().clone()),
839        }
840    }
841}
842
843impl ConfigurationVariables {
844    pub fn new() -> Self {
845        Self::default()
846    }
847
848    pub fn set_postgres_bytea_output(&self, value: PGByteaOutputValue) {
849        let _ = self.postgres_bytea_output.swap(Arc::new(value));
850    }
851
852    pub fn postgres_bytea_output(&self) -> Arc<PGByteaOutputValue> {
853        self.postgres_bytea_output.load().clone()
854    }
855
856    pub fn pg_datetime_style(&self) -> Arc<(PGDateTimeStyle, PGDateOrder)> {
857        self.pg_datestyle_format.load().clone()
858    }
859
860    pub fn set_pg_datetime_style(&self, style: PGDateTimeStyle, order: PGDateOrder) {
861        self.pg_datestyle_format.swap(Arc::new((style, order)));
862    }
863
864    pub fn pg_intervalstyle_format(&self) -> Arc<PGIntervalStyle> {
865        self.pg_intervalstyle_format.load().clone()
866    }
867
868    pub fn set_pg_intervalstyle_format(&self, value: PGIntervalStyle) {
869        self.pg_intervalstyle_format.swap(Arc::new(value));
870    }
871
872    pub fn allow_query_fallback(&self) -> bool {
873        self.query_options().get("query.allow_query_fallback") == Some(&"true".to_string())
874    }
875
876    pub fn set_allow_query_fallback(&self, allow: bool) {
877        self.query_options
878            .write()
879            .unwrap()
880            .insert("query.allow_query_fallback".to_string(), allow.to_string());
881    }
882
883    /// Validates and stores one canonicalized per-session query option.
884    pub fn set_query_option(
885        &self,
886        key: &str,
887        value: &str,
888    ) -> Result<(), crate::session_config::Error> {
889        let (canonical, normalized) = crate::query_options::parse_query_option(
890            key,
891            value,
892            crate::query_options::MAX_PUBLIC_PARALLELISM,
893        )?
894        .ok_or_else(|| crate::session_config::Error::InvalidConfigValue {
895            name: key.to_string(),
896            value: value.to_string(),
897            hint: "Not a supported query option".to_string(),
898            location: snafu::Location::new(file!(), line!(), column!()),
899        })?;
900        self.query_options
901            .write()
902            .unwrap()
903            .insert(canonical, normalized);
904        Ok(())
905    }
906
907    /// Returns the session's canonical query option overrides.
908    pub fn query_options(&self) -> HashMap<String, String> {
909        self.query_options.read().unwrap().clone()
910    }
911}
912
913#[cfg(test)]
914mod test {
915    use std::collections::HashMap;
916
917    use common_catalog::consts::DEFAULT_CATALOG_NAME;
918
919    use crate::Session;
920    use crate::context::{Channel, *};
921
922    #[test]
923    fn test_query_parallelism_wire_accepts_legacy_value() {
924        let mut context = QueryContext::with(DEFAULT_CATALOG_NAME, "public");
925        context.set_extension("query_parallelism", "2048");
926
927        assert_eq!(
928            context
929                .effective_query_options()
930                .unwrap()
931                .get("query.parallelism"),
932            Some(&"2048".to_string())
933        );
934    }
935
936    #[test]
937    fn test_set_query_parallelism_public_limit() {
938        let context = QueryContext::with(DEFAULT_CATALOG_NAME, "public");
939        assert!(
940            context
941                .configuration_parameter()
942                .set_query_option("query.parallelism", "1025")
943                .is_err()
944        );
945        assert!(
946            context
947                .configuration_parameter()
948                .set_query_option("query.parallelism", "1024")
949                .is_ok()
950        );
951    }
952
953    #[test]
954    fn test_session() {
955        let session = Session::new(
956            Some("127.0.0.1:9000".parse().unwrap()),
957            Channel::Mysql,
958            Default::default(),
959            100,
960        );
961        // test user_info
962        assert_eq!(session.user_info().username(), "greptime");
963
964        // test channel
965        assert_eq!(session.conn_info().channel, Channel::Mysql);
966        let client_addr = session.conn_info().client_addr.as_ref().unwrap();
967        assert_eq!(client_addr.ip().to_string(), "127.0.0.1");
968        assert_eq!(client_addr.port(), 9000);
969
970        assert_eq!("mysql[127.0.0.1:9000]", session.conn_info().to_string());
971        assert_eq!(100, session.process_id());
972
973        let query_ctx = session.new_query_context();
974        assert!(query_ctx.remote_query_id().is_some());
975    }
976
977    #[test]
978    fn test_context_db_string() {
979        let context = QueryContext::with("a0b1c2d3", "test");
980        assert_eq!("a0b1c2d3-test", context.get_db_string());
981
982        let context = QueryContext::with(DEFAULT_CATALOG_NAME, "test");
983        assert_eq!("test", context.get_db_string());
984    }
985
986    #[test]
987    fn test_fork_has_independent_mutable_session_data() {
988        let context = QueryContext::with(DEFAULT_CATALOG_NAME, "public");
989        let fork = context.fork();
990
991        fork.set_current_schema("private");
992
993        assert_eq!(context.current_schema(), "public");
994        assert_eq!(fork.current_schema(), "private");
995    }
996
997    #[test]
998    fn test_skip_wal_default_builder_and_fork() {
999        let default_context = QueryContext::with(DEFAULT_CATALOG_NAME, "public");
1000        assert!(!default_context.skip_wal());
1001        assert!(!QueryContextBuilder::default().build().skip_wal());
1002        let context = QueryContextBuilder::default().skip_wal(true).build();
1003        assert!(context.skip_wal());
1004        let fork = context.fork();
1005        assert!(fork.skip_wal());
1006        fork.set_skip_wal(false);
1007        assert!(context.skip_wal());
1008        assert!(!fork.skip_wal());
1009        context.set_skip_wal(false);
1010        fork.set_skip_wal(true);
1011        assert!(!context.skip_wal());
1012        assert!(fork.skip_wal());
1013    }
1014
1015    #[test]
1016    fn test_write_admission_is_local_and_database_scoped() {
1017        let ctx = QueryContext::with_channel("greptime", "public", Channel::Otlp);
1018        let admitted = ctx.with_write_admission();
1019        assert_eq!(ctx.write_rows_to_admit("greptime", "public", 100), 100);
1020        assert_eq!(admitted.write_rows_to_admit("greptime", "public", 100), 0);
1021        assert_eq!(admitted.write_rows_to_admit("greptime", "other", 100), 100);
1022        assert_eq!(admitted.write_rows_to_admit("other", "public", 100), 100);
1023        assert_eq!(admitted.channel(), Channel::Otlp);
1024        assert_eq!(
1025            admitted
1026                .fork()
1027                .write_rows_to_admit("greptime", "public", 100),
1028            0
1029        );
1030        let wire: api::v1::QueryContext = admitted.into();
1031        let restored = QueryContext::try_from(wire).unwrap();
1032        assert_eq!(restored.write_rows_to_admit("greptime", "public", 100), 100);
1033    }
1034
1035    #[test]
1036    fn test_skip_wal_is_not_serialized_in_query_context() {
1037        let context = QueryContextBuilder::default().skip_wal(true).build();
1038        let api_context: api::v1::QueryContext = context.into();
1039        assert!(
1040            !api_context
1041                .extensions
1042                .contains_key(crate::hints::INSERT_SKIP_WAL_HINT)
1043        );
1044        let restored: QueryContext = api_context.try_into().unwrap();
1045        assert!(!restored.skip_wal());
1046    }
1047
1048    #[test]
1049    fn test_api_query_context_roundtrip_with_sequences() {
1050        let api_ctx = api::v1::QueryContext {
1051            current_catalog: "c1".to_string(),
1052            current_schema: "s1".to_string(),
1053            timezone: "UTC".to_string(),
1054            extensions: HashMap::from([("flow.return_region_seq".to_string(), "true".to_string())]),
1055            channel: Channel::Internal as u32,
1056            snapshot_seqs: Some(api::v1::SnapshotSequences {
1057                snapshot_seqs: HashMap::from([(1, 100)]),
1058                sst_min_sequences: HashMap::from([(1, 90)]),
1059            }),
1060            explain: None,
1061        };
1062
1063        let session_ctx: QueryContext = api_ctx.clone().try_into().unwrap();
1064        let roundtrip_api: api::v1::QueryContext = session_ctx.into();
1065
1066        assert_eq!(roundtrip_api.current_catalog, api_ctx.current_catalog);
1067        assert_eq!(roundtrip_api.current_schema, api_ctx.current_schema);
1068        assert_eq!(roundtrip_api.timezone, api_ctx.timezone);
1069        assert_eq!(
1070            roundtrip_api.extensions.get("flow.return_region_seq"),
1071            Some(&"true".to_string())
1072        );
1073        assert!(
1074            roundtrip_api
1075                .extensions
1076                .contains_key(REMOTE_QUERY_ID_EXTENSION_KEY)
1077        );
1078        assert_eq!(roundtrip_api.channel, api_ctx.channel);
1079        assert_eq!(roundtrip_api.snapshot_seqs, api_ctx.snapshot_seqs);
1080    }
1081
1082    #[test]
1083    fn test_query_context_remote_query_id_round_trip() {
1084        let query_id = "0195f4fd-c503-7c54-8b8f-7dfb8f6f9c4a";
1085        let ctx = QueryContextBuilder::default()
1086            .current_catalog(DEFAULT_CATALOG_NAME.to_string())
1087            .current_schema("public".to_string())
1088            .set_extension(
1089                REMOTE_QUERY_ID_EXTENSION_KEY.to_string(),
1090                query_id.to_string(),
1091            )
1092            .build();
1093
1094        assert_eq!(ctx.remote_query_id(), Some(query_id));
1095        assert_eq!(ctx.remote_query_id_value().unwrap().to_string(), query_id);
1096
1097        let proto: api::v1::QueryContext = (&ctx).into();
1098        let restored = QueryContext::try_from(proto).unwrap();
1099        assert_eq!(restored.remote_query_id(), Some(query_id));
1100        assert_eq!(
1101            restored.remote_query_id_value().unwrap().to_string(),
1102            query_id
1103        );
1104    }
1105
1106    #[test]
1107    fn test_query_context_read_preference_wire_values() {
1108        let wire = |value: Option<&str>| api::v1::QueryContext {
1109            current_catalog: DEFAULT_CATALOG_NAME.to_string(),
1110            current_schema: DEFAULT_SCHEMA_NAME.to_string(),
1111            timezone: "UTC".to_string(),
1112            extensions: value
1113                .map(|value| {
1114                    HashMap::from([(READ_PREFERENCE_EXTENSION_KEY.to_string(), value.to_string())])
1115                })
1116                .unwrap_or_default(),
1117            channel: Channel::Internal as u32,
1118            snapshot_seqs: None,
1119            explain: None,
1120        };
1121
1122        let header = RegionRequestHeader::default();
1123        let header_context = QueryContext::try_from(&header).unwrap();
1124        assert_eq!(header_context.read_preference(), ReadPreference::Leader);
1125
1126        for value in [None, Some("leader"), Some("LEADER")] {
1127            let ctx = QueryContext::try_from(wire(value)).unwrap();
1128            assert_eq!(ctx.read_preference(), ReadPreference::Leader);
1129            assert!(!ctx.extensions().contains_key(READ_PREFERENCE_EXTENSION_KEY));
1130        }
1131        for value in ["", "unknown"] {
1132            let error = QueryContext::try_from(wire(Some(value))).unwrap_err();
1133            assert!(matches!(
1134                error,
1135                crate::session_config::Error::InvalidConfigValue {
1136                    name,
1137                    value: actual,
1138                    ..
1139                } if name == "read_preference" && actual == value
1140            ));
1141        }
1142    }
1143
1144    #[test]
1145    fn test_legacy_query_context_wire_defaults_to_leader() {
1146        use prost::Message;
1147
1148        let legacy = api::v1::QueryContext::decode(
1149            include_bytes!(
1150                "../../../tests/compatibility/wire/read_preference/legacy_query_context.pb"
1151            )
1152            .as_slice(),
1153        )
1154        .unwrap();
1155        assert_eq!(legacy.current_catalog, "c1");
1156        assert_eq!(legacy.current_schema, "s1");
1157        assert_eq!(legacy.timezone, "UTC");
1158        assert_eq!(legacy.channel, Channel::Internal as u32);
1159        assert_eq!(
1160            legacy
1161                .extensions
1162                .get("flow.return_region_seq")
1163                .map(String::as_str),
1164            Some("true")
1165        );
1166        assert!(
1167            !legacy
1168                .extensions
1169                .contains_key(READ_PREFERENCE_EXTENSION_KEY)
1170        );
1171
1172        let restored = QueryContext::try_from(legacy).unwrap();
1173        assert_eq!(restored.read_preference(), ReadPreference::Leader);
1174        assert_eq!(restored.current_catalog(), "c1");
1175        assert_eq!(restored.current_schema(), "s1");
1176        assert_eq!(restored.timezone().to_string(), "UTC");
1177        assert_eq!(restored.channel(), Channel::Internal);
1178        assert_eq!(restored.extension("flow.return_region_seq"), Some("true"));
1179
1180        let outbound: api::v1::QueryContext = restored.into();
1181        let roundtrip = api::v1::QueryContext::decode(outbound.encode_to_vec().as_slice()).unwrap();
1182        assert_eq!(roundtrip.current_catalog, "c1");
1183        assert_eq!(roundtrip.current_schema, "s1");
1184        assert_eq!(roundtrip.timezone, "UTC");
1185        assert_eq!(roundtrip.channel, Channel::Internal as u32);
1186        assert_eq!(
1187            roundtrip
1188                .extensions
1189                .get("flow.return_region_seq")
1190                .map(String::as_str),
1191            Some("true")
1192        );
1193        assert!(
1194            !roundtrip
1195                .extensions
1196                .contains_key(READ_PREFERENCE_EXTENSION_KEY)
1197        );
1198    }
1199
1200    #[test]
1201    fn test_outbound_read_preference_clears_stale_extension() {
1202        let context = QueryContextBuilder::default()
1203            .set_extension(
1204                READ_PREFERENCE_EXTENSION_KEY.to_string(),
1205                "stale".to_string(),
1206            )
1207            .set_extension("unrelated".to_string(), "preserved".to_string())
1208            .build();
1209        let wire: api::v1::QueryContext = context.into();
1210        assert!(!wire.extensions.contains_key(READ_PREFERENCE_EXTENSION_KEY));
1211        assert_eq!(
1212            wire.extensions.get("unrelated").map(String::as_str),
1213            Some("preserved")
1214        );
1215    }
1216
1217    #[test]
1218    fn test_live_analyze_metrics_requires_matching_remote_query_id() {
1219        let mut ctx = QueryContext::arc().as_ref().clone();
1220        assert!(!ctx.live_analyze_metrics_enabled());
1221
1222        ctx.enable_live_analyze_metrics();
1223        assert!(ctx.live_analyze_metrics_enabled());
1224
1225        ctx.set_extension(LIVE_ANALYZE_METRICS_EXTENSION_KEY, "true");
1226        assert!(!ctx.live_analyze_metrics_enabled());
1227
1228        ctx.set_extension(LIVE_ANALYZE_METRICS_EXTENSION_KEY, "another-query-id");
1229        assert!(!ctx.live_analyze_metrics_enabled());
1230    }
1231    #[test]
1232    fn test_batching_selection_is_local_only() {
1233        let mut ctx = QueryContextBuilder::default().build();
1234        assert!(!ctx.batching_enabled());
1235        assert!(!ctx.logical_batching_enabled());
1236        ctx.set_logical_batching_enabled(true);
1237        assert!(ctx.clone().logical_batching_enabled());
1238        assert!(ctx.fork().logical_batching_enabled());
1239        ctx.set_batching_enabled(true);
1240        assert!(ctx.clone().batching_enabled());
1241        assert!(ctx.fork().batching_enabled());
1242        let wire: api::v1::QueryContext = ctx.into();
1243        let restored = QueryContext::try_from(wire).unwrap();
1244        assert!(!restored.batching_enabled());
1245        assert!(!restored.logical_batching_enabled());
1246        let ctx = QueryContextBuilder::default()
1247            .set_extension("batching_enabled".to_string(), "true".to_string())
1248            .build();
1249        assert!(!ctx.batching_enabled());
1250    }
1251}