Skip to content

Commit 5fcb756

Browse files
committed
adapter: expose Materialize version in mz_version parameter
This commit exposes the Materialize-specific information in a new `mz_version` parameter, as in: materialize=> show mz_version; mz_version ------------------------- v0.39.0-dev (73f6bed) (1 row) The `mz_version` parameter is additionally added to the parameter set that is automatically sent to the client during part of startup. This approach comes from CockroachDB, and allows clients to easily detect whether they're talking to Materialize or PostgreSQL without incurring an additional roundtrip. I already have a PR out to sqlx [0] that uses this feature to automatically disable PostgreSQL-specific features that Materialize does not support. The version string matches the output of the `mz_version()` function. The implementation is somewhat irritating, as it requires plumbing the `BuildInfo` from the adapter into each session. Doesn't turn out too complicated, though, now that it's all written out. [0]: launchbadge/sqlx#2282
1 parent e490ebd commit 5fcb756

File tree

9 files changed

+106
-43
lines changed

9 files changed

+106
-43
lines changed

src/adapter/src/client.rs

+20-3
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ use tokio::sync::{mpsc, oneshot, watch};
1818
use tracing::error;
1919
use uuid::Uuid;
2020

21+
use mz_build_info::BuildInfo;
2122
use mz_ore::collections::CollectionExt;
2223
use mz_ore::id_gen::IdAllocator;
2324
use mz_ore::task::{AbortOnDropHandle, JoinHandleExt};
@@ -29,7 +30,7 @@ use crate::catalog::INTROSPECTION_USER;
2930
use crate::command::{Canceled, Command, ExecuteResponse, Response, StartupResponse};
3031
use crate::error::AdapterError;
3132
use crate::metrics::Metrics;
32-
use crate::session::{EndTransactionAction, PreparedStatement, Session, TransactionId};
33+
use crate::session::{EndTransactionAction, PreparedStatement, Session, TransactionId, User};
3334
use crate::PeekResponseUnary;
3435

3536
/// An abstraction allowing us to name different connections.
@@ -71,14 +72,20 @@ impl Handle {
7172
/// outstanding clients have dropped.
7273
#[derive(Debug, Clone)]
7374
pub struct Client {
75+
build_info: &'static BuildInfo,
7476
inner_cmd_tx: mpsc::UnboundedSender<Command>,
7577
id_alloc: Arc<IdAllocator<ConnectionId>>,
7678
metrics: Metrics,
7779
}
7880

7981
impl Client {
80-
pub(crate) fn new(cmd_tx: mpsc::UnboundedSender<Command>, metrics: Metrics) -> Client {
82+
pub(crate) fn new(
83+
build_info: &'static BuildInfo,
84+
cmd_tx: mpsc::UnboundedSender<Command>,
85+
metrics: Metrics,
86+
) -> Client {
8187
Client {
88+
build_info,
8289
inner_cmd_tx: cmd_tx,
8390
id_alloc: Arc::new(IdAllocator::new(1, 1 << 16)),
8491
metrics,
@@ -88,6 +95,7 @@ impl Client {
8895
/// Allocates a client for an incoming connection.
8996
pub fn new_conn(&self) -> Result<ConnClient, AdapterError> {
9097
Ok(ConnClient {
98+
build_info: self.build_info,
9199
conn_id: self
92100
.id_alloc
93101
.alloc()
@@ -101,7 +109,7 @@ impl Client {
101109
pub async fn introspection_execute_one(&self, sql: &str) -> Result<Vec<Row>, anyhow::Error> {
102110
// Connect to the coordinator.
103111
let conn_client = self.new_conn()?;
104-
let session = Session::new(conn_client.conn_id(), INTROSPECTION_USER.clone());
112+
let session = conn_client.new_session(INTROSPECTION_USER.clone());
105113
let (mut session_client, _) = conn_client.startup(session, false).await?;
106114

107115
// Parse the SQL statement.
@@ -146,11 +154,20 @@ impl Client {
146154
/// See also [`Client`].
147155
#[derive(Debug)]
148156
pub struct ConnClient {
157+
build_info: &'static BuildInfo,
149158
conn_id: ConnectionId,
150159
inner: Client,
151160
}
152161

153162
impl ConnClient {
163+
/// Creates a new session associated with this connection for the given
164+
/// user.
165+
///
166+
/// It is the caller's responsibility to have authenticated the user.
167+
pub fn new_session(&self, user: User) -> Session {
168+
Session::new(self.build_info, self.conn_id, user)
169+
}
170+
154171
/// Returns the ID of the connection associated with this client.
155172
pub fn conn_id(&self) -> ConnectionId {
156173
self.conn_id

src/adapter/src/config/backend.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use mz_repr::Row;
1111
use mz_sql::ast::{Ident, Raw, ShowStatement, ShowVariableStatement, Statement};
1212

1313
use crate::catalog::SYSTEM_USER;
14-
use crate::session::{EndTransactionAction, Session};
14+
use crate::session::EndTransactionAction;
1515
use crate::{AdapterError, Client, ExecuteResponse, PeekResponseUnary, SessionClient};
1616

1717
use super::SynchronizedParameters;
@@ -27,7 +27,7 @@ pub struct SystemParameterBackend {
2727
impl SystemParameterBackend {
2828
pub async fn new(client: Client) -> Result<Self, AdapterError> {
2929
let conn_client = client.new_conn()?;
30-
let session = Session::new(conn_client.conn_id(), SYSTEM_USER.clone());
30+
let session = conn_client.new_session(SYSTEM_USER.clone());
3131
let (session_client, _) = conn_client.startup(session, true).await?;
3232
Ok(Self { session_client })
3333
}

src/adapter/src/coord.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1251,7 +1251,7 @@ pub async fn serve<S: Append + 'static>(
12511251
start_instant,
12521252
_thread: thread.join_on_drop(),
12531253
};
1254-
let client = Client::new(cmd_tx.clone(), metrics_clone);
1254+
let client = Client::new(build_info, cmd_tx.clone(), metrics_clone);
12551255
Ok((handle, client))
12561256
}
12571257
Err(e) => Err(e),

src/adapter/src/session.rs

+16-7
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ use tokio::sync::mpsc::{self, UnboundedReceiver, UnboundedSender};
2222
use tokio::sync::OwnedMutexGuard;
2323
use uuid::Uuid;
2424

25+
use mz_build_info::{BuildInfo, DUMMY_BUILD_INFO};
2526
use mz_pgrepr::Format;
2627
use mz_repr::{Datum, Diff, GlobalId, Row, ScalarType, TimestampManipulation};
2728
use mz_sql::ast::{Raw, Statement, TransactionAccessMode};
@@ -95,25 +96,33 @@ pub struct Session<T = mz_repr::Timestamp> {
9596

9697
impl<T: TimestampManipulation> Session<T> {
9798
/// Creates a new session for the specified connection ID.
98-
pub fn new(conn_id: ConnectionId, user: User) -> Session<T> {
99+
pub(crate) fn new(
100+
build_info: &'static BuildInfo,
101+
conn_id: ConnectionId,
102+
user: User,
103+
) -> Session<T> {
99104
assert_ne!(conn_id, DUMMY_CONNECTION_ID);
100-
Self::new_internal(conn_id, user)
105+
Self::new_internal(build_info, conn_id, user)
101106
}
102107

103108
/// Creates a new dummy session.
104109
///
105110
/// Dummy sessions are intended for use when executing queries on behalf of
106111
/// the system itself, rather than on behalf of a user.
107112
pub fn dummy() -> Session<T> {
108-
Self::new_internal(DUMMY_CONNECTION_ID, SYSTEM_USER.clone())
113+
Self::new_internal(&DUMMY_BUILD_INFO, DUMMY_CONNECTION_ID, SYSTEM_USER.clone())
109114
}
110115

111-
fn new_internal(conn_id: ConnectionId, user: User) -> Session<T> {
116+
fn new_internal(
117+
build_info: &'static BuildInfo,
118+
conn_id: ConnectionId,
119+
user: User,
120+
) -> Session<T> {
112121
let (notices_tx, notices_rx) = mpsc::unbounded_channel();
113122
let vars = if INTERNAL_USER_NAMES.contains(&user.name) {
114-
SessionVars::for_cluster(&user.name)
123+
SessionVars::for_cluster(build_info, &user.name)
115124
} else {
116-
SessionVars::default()
125+
SessionVars::new(build_info)
117126
};
118127
Session {
119128
conn_id,
@@ -593,7 +602,7 @@ impl<T: TimestampManipulation> Session<T> {
593602
pub fn reset(&mut self) {
594603
let _ = self.clear_transaction();
595604
self.prepared_statements.clear();
596-
self.vars = SessionVars::default();
605+
self.vars = SessionVars::new(self.vars.build_info());
597606
}
598607

599608
/// Returns the user who owns this session.

src/adapter/src/session/vars.rs

+57-13
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ use once_cell::sync::Lazy;
1616
use serde::Serialize;
1717
use uncased::UncasedStr;
1818

19+
use mz_build_info::BuildInfo;
1920
use mz_ore::cast;
2021
use mz_sql::ast::{Ident, SetVariableValue, Value as AstValue};
2122
use mz_sql::DEFAULT_SCHEMA;
@@ -125,6 +126,8 @@ const INTERVAL_STYLE: ServerVar<str> = ServerVar {
125126
internal: false,
126127
};
127128

129+
const MZ_VERSION_NAME: &UncasedStr = UncasedStr::new("mz_version");
130+
128131
const QGM_OPTIMIZATIONS: ServerVar<bool> = ServerVar {
129132
name: UncasedStr::new("qgm_optimizations_experimental"),
130133
value: &false,
@@ -399,6 +402,7 @@ static EMIT_TRACE_ID_NOTICE: ServerVar<bool> = ServerVar {
399402
#[derive(Debug)]
400403
pub struct SessionVars {
401404
application_name: SessionVar<str>,
405+
build_info: &'static BuildInfo,
402406
client_encoding: ServerVar<str>,
403407
client_min_messages: SessionVar<ClientSeverity>,
404408
cluster: SessionVar<str>,
@@ -424,10 +428,12 @@ pub struct SessionVars {
424428
emit_trace_id_notice: SessionVar<bool>,
425429
}
426430

427-
impl Default for SessionVars {
428-
fn default() -> SessionVars {
431+
impl SessionVars {
432+
/// Creates a new [`SessionVars`].
433+
pub fn new(build_info: &'static BuildInfo) -> SessionVars {
429434
SessionVars {
430435
application_name: SessionVar::new(&APPLICATION_NAME),
436+
build_info,
431437
client_encoding: CLIENT_ENCODING,
432438
client_min_messages: SessionVar::new(&CLIENT_MIN_MESSAGES),
433439
cluster: SessionVar::new(&CLUSTER),
@@ -455,24 +461,20 @@ impl Default for SessionVars {
455461
emit_trace_id_notice: SessionVar::new(&EMIT_TRACE_ID_NOTICE),
456462
}
457463
}
458-
}
459464

460-
impl SessionVars {
461465
/// Returns a new SessionVars with the cluster variable set to `cluster`.
462-
pub fn for_cluster(cluster_name: &str) -> Self {
463-
let mut cluster = SessionVar::new(&CLUSTER);
464-
cluster.session_value = Some(cluster_name.into());
465-
Self {
466-
cluster,
467-
..Default::default()
468-
}
466+
pub fn for_cluster(build_info: &'static BuildInfo, cluster_name: &str) -> Self {
467+
let mut vars = SessionVars::new(build_info);
468+
vars.cluster.session_value = Some(cluster_name.into());
469+
vars
469470
}
470471

471472
/// Returns an iterator over the configuration parameters and their current
472473
/// values for this session.
473474
pub fn iter(&self) -> impl Iterator<Item = &dyn Var> {
474-
let vars: [&dyn Var; 24] = [
475+
let vars: [&dyn Var; 25] = [
475476
&self.application_name,
477+
self.build_info,
476478
&self.client_encoding,
477479
&self.client_min_messages,
478480
&self.cluster,
@@ -504,7 +506,7 @@ impl SessionVars {
504506
/// values for this session) that are expected to be sent to the client when
505507
/// a new connection is established or when their value changes.
506508
pub fn notify_set(&self) -> impl Iterator<Item = &dyn Var> {
507-
let vars: [&dyn Var; 8] = [
509+
let vars: [&dyn Var; 9] = [
508510
&self.application_name,
509511
&self.client_encoding,
510512
&self.date_style,
@@ -513,6 +515,13 @@ impl SessionVars {
513515
&self.standard_conforming_strings,
514516
&self.timezone,
515517
&self.interval_style,
518+
// Including `mz_version` in the notify set is a Materialize
519+
// extension. Doing so allows applications to detect whether they
520+
// are talking to Materialize or PostgreSQL without an additional
521+
// network roundtrip. This is known to be safe because CockroachDB
522+
// has an analogous extension [0].
523+
// [0]: https://github.com/cockroachdb/cockroach/blob/369c4057a/pkg/sql/pgwire/conn.go#L1840
524+
self.build_info,
516525
];
517526
vars.into_iter()
518527
}
@@ -550,6 +559,8 @@ impl SessionVars {
550559
Ok(&self.integer_datetimes)
551560
} else if name == INTERVAL_STYLE.name {
552561
Ok(&self.interval_style)
562+
} else if name == MZ_VERSION_NAME {
563+
Ok(self.build_info)
553564
} else if name == QGM_OPTIMIZATIONS.name {
554565
Ok(&self.qgm_optimizations)
555566
} else if name == SEARCH_PATH.name {
@@ -789,6 +800,7 @@ impl SessionVars {
789800
// call to `end_transaction` below.
790801
let SessionVars {
791802
application_name,
803+
build_info: _,
792804
client_encoding: _,
793805
client_min_messages,
794806
cluster,
@@ -836,6 +848,11 @@ impl SessionVars {
836848
self.application_name.value()
837849
}
838850

851+
/// Returns the build info.
852+
pub fn build_info(&self) -> &'static BuildInfo {
853+
self.build_info
854+
}
855+
839856
/// Returns the value of the `client_encoding` configuration parameter.
840857
pub fn client_encoding(&self) -> &'static str {
841858
self.client_encoding.value
@@ -881,6 +898,11 @@ impl SessionVars {
881898
self.interval_style.value
882899
}
883900

901+
/// Returns the value of the `mz_version` configuration parameter.
902+
pub fn mz_version(&self) -> String {
903+
self.build_info.value()
904+
}
905+
884906
/// Returns the value of the `qgm_optimizations` configuration parameter.
885907
pub fn qgm_optimizations(&self) -> bool {
886908
*self.qgm_optimizations.value()
@@ -1589,6 +1611,28 @@ where
15891611
}
15901612
}
15911613

1614+
impl Var for BuildInfo {
1615+
fn name(&self) -> &'static str {
1616+
"mz_version"
1617+
}
1618+
1619+
fn value(&self) -> String {
1620+
self.human_version()
1621+
}
1622+
1623+
fn description(&self) -> &'static str {
1624+
"Shows the Materialize server version (Materialize)."
1625+
}
1626+
1627+
fn type_name(&self) -> &'static str {
1628+
str::TYPE_NAME
1629+
}
1630+
1631+
fn visible(&self, _: &User) -> bool {
1632+
true
1633+
}
1634+
}
1635+
15921636
/// A value that can be stored in a session or server variable.
15931637
pub trait Value: ToOwned + Send + Sync {
15941638
/// The name of the value type.

src/environmentd/src/http.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ use tower_http::cors::{AllowOrigin, Any, CorsLayer};
4444
use tracing::{error, warn};
4545

4646
use mz_adapter::catalog::{HTTP_DEFAULT_USER, SYSTEM_USER};
47-
use mz_adapter::session::{ExternalUserMetadata, Session, User};
47+
use mz_adapter::session::{ExternalUserMetadata, User};
4848
use mz_adapter::{AdapterError, Client, SessionClient};
4949
use mz_frontegg_auth::{FronteggAuthentication, FronteggError};
5050
use mz_ore::metrics::MetricsRegistry;
@@ -282,7 +282,7 @@ impl AuthedClient {
282282
create_if_not_exists,
283283
} = user;
284284
let adapter_client = adapter_client.new_conn()?;
285-
let session = Session::new(adapter_client.conn_id(), user);
285+
let session = adapter_client.new_session(user);
286286
let (adapter_client, _) = adapter_client
287287
.startup(session, create_if_not_exists)
288288
.await?;

src/pgwire/src/codec.rs

-5
Original file line numberDiff line numberDiff line change
@@ -81,11 +81,6 @@ where
8181
}
8282
}
8383

84-
/// Returns the ID of this connection.
85-
pub fn id(&self) -> u32 {
86-
self.conn_id
87-
}
88-
8984
/// Reads and decodes one frontend message from the client.
9085
///
9186
/// Blocks until the client sends a complete message. If the client

src/pgwire/src/protocol.rs

+5-8
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ use mz_adapter::catalog::INTERNAL_USER_NAMES;
2929
use mz_adapter::session::User;
3030
use mz_adapter::session::{
3131
EndTransactionAction, ExternalUserMetadata, InProgressRows, Portal, PortalState,
32-
RowBatchStream, Session, TransactionStatus,
32+
RowBatchStream, TransactionStatus,
3333
};
3434
use mz_adapter::{ExecuteResponse, PeekResponseUnary, RowsFuture};
3535
use mz_frontegg_auth::FronteggAuthentication;
@@ -224,13 +224,10 @@ where
224224
};
225225

226226
// Construct session.
227-
let mut session = Session::new(
228-
conn.id(),
229-
User {
230-
name: user,
231-
external_metadata,
232-
},
233-
);
227+
let mut session = adapter_client.new_session(User {
228+
name: user,
229+
external_metadata,
230+
});
234231
for (name, value) in params {
235232
let local = false;
236233
let _ = session.vars_mut().set(&name, &value, local);

0 commit comments

Comments
 (0)