mz_adapter/coord/sequencer/inner/peek.rs
1// Copyright Materialize, Inc. and contributors. All rights reserved.
2//
3// Use of this software is governed by the Business Source License
4// included in the LICENSE file.
5//
6// As of the Change Date specified in that file, in accordance with
7// the Business Source License, use of this software will be governed
8// by the Apache License, Version 2.0.
9
10use std::collections::BTreeSet;
11
12use mz_controller_types::ClusterId;
13use mz_ore::instrument;
14use mz_repr::{GlobalId, Timestamp};
15use mz_sql::plan::QueryWhen;
16use mz_sql::session::metadata::SessionMetadata;
17
18use crate::coord::Coordinator;
19use crate::coord::id_bundle::CollectionIdBundle;
20use crate::coord::timeline::{TimelineContext, timedomain_for};
21use crate::coord::timestamp_selection::{TimestampContext, TimestampDetermination};
22use crate::error::AdapterError;
23use crate::session::{RequireLinearization, Session, TransactionOps, TransactionStatus};
24
25impl Coordinator {
26 /// Determines the query timestamp and acquires read holds on dependent sources
27 /// if necessary.
28 #[instrument]
29 pub(super) fn sequence_peek_timestamp(
30 &mut self,
31 session: &mut Session,
32 when: &QueryWhen,
33 cluster_id: ClusterId,
34 timeline_context: TimelineContext,
35 oracle_read_ts: Option<Timestamp>,
36 source_bundle: &CollectionIdBundle,
37 source_ids: &BTreeSet<GlobalId>,
38 real_time_recency_ts: Option<Timestamp>,
39 requires_linearization: RequireLinearization,
40 ) -> Result<TimestampDetermination, AdapterError> {
41 let in_immediate_multi_stmt_txn = session.transaction().in_immediate_multi_stmt_txn(when);
42 let timedomain_bundle;
43
44 // Fetch or generate a timestamp for this query and what the read holds would be if we need to set
45 // them.
46 let (determination, read_holds) = match session.get_transaction_timestamp_determination() {
47 // Use the transaction's timestamp if it exists and this isn't an AS OF query.
48 Some(
49 determination @ TimestampDetermination {
50 timestamp_context: TimestampContext::TimelineTimestamp { .. },
51 ..
52 },
53 ) if in_immediate_multi_stmt_txn => (determination, None),
54 _ => {
55 let determine_bundle = if in_immediate_multi_stmt_txn {
56 // In a transaction, determine a timestamp that will be valid for anything in
57 // any schema referenced by the first query.
58 timedomain_bundle = timedomain_for(
59 self.catalog(),
60 &self.index_oracle(cluster_id),
61 source_ids,
62 &timeline_context,
63 session.conn_id(),
64 cluster_id,
65 )?;
66
67 &timedomain_bundle
68 } else {
69 // If not in a transaction, use the source.
70 source_bundle
71 };
72 let (determination, read_holds) = self.determine_timestamp(
73 session,
74 determine_bundle,
75 when,
76 cluster_id,
77 &timeline_context,
78 oracle_read_ts,
79 real_time_recency_ts,
80 )?;
81 // We only need read holds if the read depends on a timestamp.
82 let read_holds = match determination.timestamp_context.timestamp() {
83 Some(_ts) => Some(read_holds),
84 None => {
85 // We don't need the read holds and shouldn't add them
86 // to the txn.
87 //
88 // TODO: Handle this within determine_timestamp.
89 drop(read_holds);
90 None
91 }
92 };
93 (determination, read_holds)
94 }
95 };
96
97 // Always either verify the current statement ids are within the existing
98 // transaction's read hold set (timedomain), or create the read holds if this is the
99 // first statement in a transaction (or this is a single statement transaction).
100 // This must happen even if this is an `AS OF` query as well. There are steps after
101 // this that happen off thread, so no matter the kind of statement or transaction,
102 // we must acquire read holds here so they are held until the off-thread work
103 // returns to the coordinator.
104
105 if let Some(txn_reads) = self.txn_read_holds.get(session.conn_id()) {
106 // Find referenced ids not in the read hold. A reference could be caused by a
107 // user specifying an object in a different schema than the first query. An
108 // index could be caused by a CREATE INDEX after the transaction started.
109 let allowed_id_bundle = txn_reads.id_bundle();
110
111 // We don't need the read holds that determine_timestamp acquired
112 // for us.
113 drop(read_holds);
114
115 let outside = source_bundle.difference(&allowed_id_bundle);
116 // Queries without a timestamp and timeline can belong to any existing timedomain.
117 if determination.timestamp_context.contains_timestamp() && !outside.is_empty() {
118 let valid_names =
119 allowed_id_bundle.resolve_names(self.catalog(), session.conn_id());
120 let invalid_names = outside.resolve_names(self.catalog(), session.conn_id());
121 return Err(AdapterError::RelationOutsideTimeDomain {
122 relations: invalid_names,
123 names: valid_names,
124 });
125 }
126 } else if let Some(read_holds) = read_holds {
127 self.store_transaction_read_holds(session.conn_id().clone(), read_holds);
128 }
129
130 // TODO: Checking for only `InTransaction` and not `Implied` (also `Started`?) seems
131 // arbitrary and we don't recall why we did it (possibly an error!). Change this to always
132 // set the transaction ops. Decide and document what our policy should be on AS OF queries.
133 // Maybe they shouldn't be allowed in transactions at all because it's hard to explain
134 // what's going on there. This should probably get a small design document.
135
136 // We only track the peeks in the session if the query doesn't use AS
137 // OF or we're inside an explicit transaction. The latter case is
138 // necessary to support PG's `BEGIN` semantics, whose behavior can
139 // depend on whether or not reads have occurred in the txn.
140 let mut transaction_determination = determination.clone();
141 if when.is_transactional() {
142 session.add_transaction_ops(TransactionOps::Peeks {
143 determination: transaction_determination,
144 cluster_id,
145 requires_linearization,
146 })?;
147 } else if matches!(session.transaction(), &TransactionStatus::InTransaction(_)) {
148 // If the query uses AS OF, then ignore the timestamp.
149 transaction_determination.timestamp_context = TimestampContext::NoTimestamp;
150 session.add_transaction_ops(TransactionOps::Peeks {
151 determination: transaction_determination,
152 cluster_id,
153 requires_linearization,
154 })?;
155 };
156
157 Ok(determination)
158 }
159}