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