1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
//! Session-parameter handling split out of `lib.rs` (lib.rs split 16):
//! `set_session_param` records a `SET <name> = <value>` (folding the
//! MySQL/PG FK-check + string-dialect toggles into engine state),
//! `session_param` reads one back (the FTS dispatcher consults
//! `default_text_search_config`), and `ev_ctx` builds an `EvalContext`
//! pre-chained with that config. Whole `impl Engine` methods; the
//! execute dispatcher drives `set_session_param`, `select.rs` drives
//! `ev_ctx`, and `dml.rs` / `plpgsql.rs` read via `session_param`.
use alloc::string::String;
use spg_storage::ColumnSchema;
use crate::Engine;
use crate::eval::EvalContext;
impl Engine {
/// v7.12.1 — record a `SET <name> = <value>` parameter. Names
/// are case-folded to lowercase to match PG; values keep their
/// caller-supplied form so observability paths see what was
/// requested. Only `default_text_search_config` is consulted by
/// the engine today.
pub(crate) fn set_session_param(&mut self, name: String, value: spg_sql::ast::SetValue) {
let normalised = match value {
spg_sql::ast::SetValue::String(s) => s,
spg_sql::ast::SetValue::Ident(s) => s,
spg_sql::ast::SetValue::Number(s) => s,
spg_sql::ast::SetValue::Default => String::new(),
};
let key = name.to_ascii_lowercase();
// v7.14.0 — mysqldump preamble emits
// `SET FOREIGN_KEY_CHECKS=0` so it can CREATE TABLE in any
// order despite cross-table FK references; the closing
// section emits `SET FOREIGN_KEY_CHECKS=1` (or
// `=@OLD_FOREIGN_KEY_CHECKS` which resolves to "ON" in our
// session-variable-aware path). Match both shapes.
// Also accept PG's `session_replication_role = 'replica'`
// which suppresses trigger + FK enforcement during a
// logical replication apply (pg_dump preserves this for
// schema-only mode but it shows up in some restores).
let value_off = matches!(
normalised.to_ascii_lowercase().as_str(),
"0" | "off" | "false"
);
let value_on = matches!(
normalised.to_ascii_lowercase().as_str(),
"1" | "on" | "true"
);
if key == "foreign_key_checks"
|| key == "session_replication_role" && normalised.eq_ignore_ascii_case("replica")
{
if value_off || key == "session_replication_role" {
self.foreign_key_checks = false;
} else if value_on
|| (key == "session_replication_role" && normalised.eq_ignore_ascii_case("origin"))
{
self.foreign_key_checks = true;
// Drain pending FK queue against the now-complete
// catalog. Errors here surface as the SET reply —
// caller knows enabling checks revealed orphans.
let _ = self.drain_pending_foreign_keys();
}
}
// v7.22 (round-13 T3) — string-literal dialect signals.
// `SET sql_mode = …` is something only MySQL clients and
// mysqldump preambles emit → MySQL escape semantics.
// `SET standard_conforming_strings = on|off` is PG's own
// switch for exactly this behaviour (every pg_dump preamble
// sets it to on). The same SQL text lexes differently per
// dialect, so a flip invalidates the plan cache.
let new_escapes = if key == "sql_mode" {
Some(true)
} else if key == "standard_conforming_strings" {
Some(value_off)
} else {
None
};
if let Some(flag) = new_escapes
&& flag != self.backslash_escapes
{
self.backslash_escapes = flag;
self.plan_cache.clear();
}
self.session_params.insert(key, normalised);
}
/// v7.12.1 — read a session parameter set via `SET`. Used by
/// the FTS function dispatcher to resolve the default config
/// for `to_tsvector(text)` / `plainto_tsquery(text)` etc.
#[must_use]
pub fn session_param(&self, name: &str) -> Option<&str> {
self.session_params
.get(&name.to_ascii_lowercase())
.map(String::as_str)
}
/// v7.12.1 — build an `EvalContext` chained with the session's
/// `default_text_search_config`. Engine-internal callers use
/// this instead of `EvalContext::new` so the FTS function
/// dispatcher sees the SET configuration.
pub(crate) fn ev_ctx<'a>(
&'a self,
columns: &'a [ColumnSchema],
alias: Option<&'a str>,
) -> EvalContext<'a> {
EvalContext::new(columns, alias)
.with_default_text_search_config(self.session_param("default_text_search_config"))
}
}