1use std::collections::BTreeMap;
11use std::sync::Arc;
12
13use itertools::Itertools;
14use mz_catalog::memory::objects::{CatalogItem, Source, Table};
15use mz_repr::{CatalogItemId, GlobalId};
16use mz_sql::ast::{Raw, Statement};
17use mz_sql::names::{Aug, QualifiedItemName, ResolvedIds};
18use mz_sql::plan::{Params, Plan, PlanNotice};
19use mz_sql::pure::{
20 PurifiedStatement, SourcePreparation, SourcePreparationRequest, SourcePreparationResponse,
21 SourcePreparationStatus,
22};
23use mz_sql::session::metadata::SessionMetadata;
24use tokio::sync::mpsc;
25
26use super::source::{SourceObject, SourcePlanning, plan_create_source};
27use crate::catalog::{CatalogState, ConnCatalog, Op};
28use crate::session::SessionMeta;
29use crate::{AdapterError, AdapterNotice};
30
31#[derive(Clone, Debug)]
32pub(crate) struct SourceStatement {
33 statement: Arc<Statement<Raw>>,
34 params: Params,
35 session: Arc<SessionMeta>,
36 preparation: SourcePreparation,
37 ids: BTreeMap<SourceObject, (CatalogItemId, GlobalId)>,
38}
39
40#[derive(Debug)]
41pub(crate) enum SourceResolution {
42 Prepare(SourcePreparationRequest),
43 Allocate(Vec<SourceObject>),
44 Ready(Box<SourceCompilation>),
45}
46
47#[derive(Debug)]
48pub(crate) struct SourceCompilation {
49 pub plan: Plan,
50 pub(in crate::catalog) ops: Vec<Op>,
51 pub(in crate::catalog) if_not_exists: BTreeMap<CatalogItemId, QualifiedItemName>,
52 pub(in crate::catalog) notices: Vec<PlanNotice>,
53}
54
55impl SourceStatement {
56 pub(crate) fn new(
57 statement: Arc<Statement<Raw>>,
58 params: Params,
59 session: SessionMeta,
60 now: u64,
61 ) -> Self {
62 Self {
63 statement,
64 params,
65 session: Arc::new(session),
66 preparation: SourcePreparation::new(now),
67 ids: BTreeMap::new(),
68 }
69 }
70
71 pub(in crate::catalog) fn connection_id(&self) -> &mz_adapter_types::connection::ConnectionId {
72 self.session.conn_id()
73 }
74
75 pub(crate) fn record_ids(
76 &mut self,
77 keys: Vec<SourceObject>,
78 ids: Vec<(CatalogItemId, GlobalId)>,
79 ) {
80 for (key, id) in keys.into_iter().zip_eq(ids) {
81 self.ids.entry(key).or_insert(id);
82 }
83 }
84
85 pub(crate) fn resolve(&self, state: &CatalogState) -> Result<SourceResolution, AdapterError> {
86 let (tx, rx) = mpsc::unbounded_channel();
87 let (catalog, statement) = self.input(state, tx)?;
88 let status = self.preparation.resolve(&catalog, statement)?;
89 self.compile(&catalog, status, rx)
90 }
91
92 pub(crate) fn finish(
93 &mut self,
94 state: &CatalogState,
95 response: SourcePreparationResponse,
96 ) -> Result<SourceResolution, AdapterError> {
97 let (tx, rx) = mpsc::unbounded_channel();
98 let (catalog, statement) = self.input(state, tx)?;
99 let status = self.preparation.finish(&catalog, statement, response)?;
100 self.compile(&catalog, status, rx)
101 }
102
103 fn input<'a>(
104 &self,
105 state: &'a CatalogState,
106 notices: mpsc::UnboundedSender<AdapterNotice>,
107 ) -> Result<(ConnCatalog<'a>, Statement<Aug>), AdapterError> {
108 let catalog = state.for_session_metadata(self.session.as_ref(), notices);
109 let (statement, mut resolved_ids) =
110 mz_sql::names::resolve(&catalog, (*self.statement).clone())?;
111 if matches!(&statement, Statement::AlterSource(stmt)
112 if matches!(stmt.action, mz_sql::ast::AlterSourceAction::AddSubsources { .. }))
113 {
114 if let Some(item) = mz_sql::pure::statement_source(&catalog, &statement) {
115 if let Some(source) = state.get_entry(&item.id()).source() {
116 resolved_ids.extend_from(&source.resolved_ids);
117 }
118 }
119 }
120 mz_sql::rbac::check_purification(
121 &catalog,
122 self.session.as_ref(),
123 mz_sql::pure::statement_source(&catalog, &statement),
124 &resolved_ids,
125 )?;
126 Ok((catalog, statement))
127 }
128
129 fn compile(
130 &self,
131 catalog: &ConnCatalog<'_>,
132 status: SourcePreparationStatus,
133 mut notices_rx: mpsc::UnboundedReceiver<AdapterNotice>,
134 ) -> Result<SourceResolution, AdapterError> {
135 let purified = match status {
136 SourcePreparationStatus::Pending(request) => {
137 return Ok(SourceResolution::Prepare(request));
138 }
139 SourcePreparationStatus::Ready(statement) => *statement,
140 };
141 let (plan, mut resolved_ids, sql_impl_ids) = match purified {
142 source @ PurifiedStatement::PurifiedCreateSource { .. } => {
143 match plan_create_source(
144 catalog,
145 self.session.pcx(),
146 &self.params,
147 source,
148 &self.ids,
149 )? {
150 SourcePlanning::NeedsIds(keys) => {
151 return Ok(SourceResolution::Allocate(keys));
152 }
153 SourcePlanning::Ready(plans) => (
154 Plan::CreateSources(plans),
155 ResolvedIds::empty(),
156 ResolvedIds::empty(),
157 ),
158 }
159 }
160 PurifiedStatement::PurifiedCreateTableFromSource { stmt } => {
161 let statement = Statement::CreateTableFromSource(stmt);
162 let resolved_ids = mz_sql::names::visit_dependencies(catalog, &statement);
163 let (plan, sql_impl_ids) = mz_sql::plan::plan(
164 Some(self.session.pcx()),
165 catalog,
166 statement,
167 &self.params,
168 &resolved_ids,
169 )?;
170 (plan, resolved_ids, sql_impl_ids)
171 }
172 source @ PurifiedStatement::PurifiedAlterSourceAddSubsources { .. } => {
173 match super::source::plan_alter_source_add_subsources(
174 catalog,
175 self.session.pcx(),
176 &self.params,
177 source,
178 &self.ids,
179 )? {
180 SourcePlanning::NeedsIds(keys) => return Ok(SourceResolution::Allocate(keys)),
181 SourcePlanning::Ready(plan) => {
182 (plan, ResolvedIds::empty(), ResolvedIds::empty())
183 }
184 }
185 }
186 PurifiedStatement::PurifiedAlterSourceRefreshReferences {
187 source_name,
188 available_source_references,
189 } => (
190 super::source::plan_alter_source_refresh_references(
191 catalog,
192 source_name,
193 available_source_references,
194 )?,
195 ResolvedIds::empty(),
196 ResolvedIds::empty(),
197 ),
198 PurifiedStatement::PurifiedAlterSource { alter_source_stmt } => {
199 let statement = Statement::AlterSource(alter_source_stmt);
200 let resolved_ids = mz_sql::names::visit_dependencies(catalog, &statement);
201 let (plan, sql_impl_ids) = mz_sql::plan::plan(
202 Some(self.session.pcx()),
203 catalog,
204 statement,
205 &self.params,
206 &resolved_ids,
207 )?;
208 (plan, resolved_ids, sql_impl_ids)
209 }
210 _ => return Err(AdapterError::Unsupported("unsupported source command")),
211 };
212 let mut altered_source = match &plan {
213 Plan::AlterSource(mz_sql::plan::AlterSourcePlan {
214 item_id,
215 action:
216 mz_sql::plan::AlterSourceAction::AddSubsourceExports {
217 options,
218 subsources,
219 },
220 ..
221 }) => {
222 let source =
223 super::source::alter::rebuild_source(catalog, *item_id, options.clone())?;
224 resolved_ids.extend_from(&source.resolved_ids);
225 for subsource in subsources {
226 resolved_ids.extend_from(&subsource.resolved_ids);
227 }
228 Some(source)
229 }
230 _ => None,
231 };
232 mz_sql::rbac::check_plan(
233 catalog,
234 None,
235 self.session.as_ref(),
236 &plan,
237 None,
238 &resolved_ids,
239 &sql_impl_ids,
240 )?;
241 let owner_id = self.session.role_metadata().current_role;
242 let mut ops = Vec::new();
243 let mut if_not_exists = BTreeMap::new();
244 let creations: &[mz_sql::plan::CreateSourcePlanBundle] = match &plan {
245 Plan::CreateSources(plans) => plans,
246 Plan::AlterSource(mz_sql::plan::AlterSourcePlan {
247 item_id, action, ..
248 }) => match action {
249 mz_sql::plan::AlterSourceAction::AddSubsourceExports { subsources, .. } => {
250 ops.push(Op::UpdateItem {
251 id: *item_id,
252 name: catalog.state().get_entry(item_id).name().clone(),
253 to_item: CatalogItem::Source(
254 altered_source.take().expect("rebuilt source"),
255 ),
256 });
257 subsources
258 }
259 mz_sql::plan::AlterSourceAction::RefreshReferences { references } => {
260 ops.push(Op::UpdateSourceReferences {
261 source_id: *item_id,
262 references: references.clone().into(),
263 });
264 &[]
265 }
266 },
267 Plan::AlterNoop(_) => &[],
268
269 Plan::CreateTable(plan) => {
270 if plan.table.temporary {
271 return Err(AdapterError::Unsupported("temporary source table command"));
272 }
273 let Some(&(id, global_id)) = self.ids.get(&SourceObject::Primary) else {
274 return Ok(SourceResolution::Allocate(vec![SourceObject::Primary]));
275 };
276 if plan.if_not_exists {
277 if_not_exists.insert(id, plan.name.clone());
278 }
279 ops.push(Op::CreateItem {
280 id,
281 name: plan.name.clone(),
282 item: CatalogItem::Table(Table::from_plan(
283 plan.table.clone(),
284 global_id,
285 self.session.conn_id().clone(),
286 resolved_ids,
287 )),
288 owner_id,
289 });
290 &[]
291 }
292 _ => return Err(AdapterError::Unsupported("unsupported source plan")),
293 };
294 for bundle in creations {
295 let mz_sql::plan::CreateSourcePlanBundle {
296 item_id,
297 global_id,
298 plan,
299 resolved_ids,
300 available_source_references,
301 } = bundle;
302 if matches!(
303 &plan.source.data_source,
304 mz_sql::plan::DataSourceDesc::Webhook { .. }
305 ) {
306 return Err(AdapterError::Unsupported("webhook source command"));
307 }
308 if plan.if_not_exists {
309 if_not_exists.insert(*item_id, plan.name.clone());
310 }
311 ops.push(Op::CreateItem {
312 id: *item_id,
313 name: plan.name.clone(),
314 item: CatalogItem::Source(Source::new(
315 plan.clone(),
316 *global_id,
317 resolved_ids.clone(),
318 None,
319 false,
320 )),
321 owner_id,
322 });
323 if let Some(references) = available_source_references {
324 ops.push(Op::UpdateSourceReferences {
325 source_id: *item_id,
326 references: references.clone().into(),
327 });
328 }
329 }
330 let mut notices = Vec::new();
331 while let Ok(notice) = notices_rx.try_recv() {
332 match notice {
333 AdapterNotice::PlanNotice(notice) => notices.push(notice),
334 _ => unreachable!("planning only emits plan notices"),
335 }
336 }
337 catalog
339 .state()
340 .validate_resource_limits(&ops, self.connection_id())?;
341 Ok(SourceResolution::Ready(Box::new(SourceCompilation {
342 plan,
343 ops,
344 if_not_exists,
345 notices,
346 })))
347 }
348}
349
350#[derive(Clone, Debug, PartialEq)]
351pub(crate) struct SourceOutcome {
352 skipped_name: Option<String>,
353 notices: Vec<PlanNotice>,
354}
355
356#[derive(Clone, Debug)]
357pub struct SourceCommand {
358 pub(in crate::catalog) statement: SourceStatement,
359 pub(in crate::catalog) outcome: SourceOutcome,
360 pub(in crate::catalog) observed: bool,
361}
362
363impl SourceCompilation {
364 pub(in crate::catalog) fn classify_result<T>(
365 &self,
366 result: Result<T, AdapterError>,
367 ) -> Result<(Option<T>, SourceOutcome), AdapterError> {
368 match result {
369 Ok(value) => Ok((Some(value), self.created())),
370 Err(error) => match self.skipped(&error) {
371 Some(outcome) => Ok((None, outcome)),
372 None => Err(error),
373 },
374 }
375 }
376
377 pub(in crate::catalog) fn created(&self) -> SourceOutcome {
378 SourceOutcome {
379 skipped_name: None,
380 notices: self.notices.clone(),
381 }
382 }
383
384 pub(in crate::catalog) fn skipped(&self, error: &AdapterError) -> Option<SourceOutcome> {
385 let AdapterError::Catalog(mz_catalog::memory::error::Error {
386 kind:
387 mz_catalog::memory::error::ErrorKind::Sql(
388 mz_sql::catalog::CatalogError::ItemAlreadyExists(id, _),
389 ),
390 }) = error
391 else {
392 return None;
393 };
394 self.if_not_exists.get(id).map(|name| SourceOutcome {
395 skipped_name: Some(name.item.clone()),
396 notices: self.notices.clone(),
397 })
398 }
399}
400
401impl SourceCommand {
402 pub(in crate::catalog) fn needs_collision_probe(&self, compiled: &SourceCompilation) -> bool {
403 self.skipped_name().is_some() || (!self.observed && !compiled.if_not_exists.is_empty())
404 }
405
406 pub(crate) fn unobserved(statement: SourceStatement, compiled: SourceCompilation) -> Self {
408 Self {
409 statement,
410 outcome: compiled.created(),
411 observed: false,
412 }
413 }
414
415 pub(crate) fn item_ids(&self) -> impl Iterator<Item = CatalogItemId> + '_ {
416 self.statement.ids.values().map(|(id, _)| *id)
417 }
418
419 pub(crate) fn skipped_notice(&self) -> Option<AdapterNotice> {
420 self.skipped_name()
421 .map(|name| AdapterNotice::ObjectAlreadyExists {
422 name: name.to_owned(),
423 ty: match &*self.statement.statement {
424 Statement::CreateSource(_) => "source",
425 Statement::CreateTableFromSource(_) => "table",
426 _ => unreachable!("source command statement"),
427 },
428 })
429 }
430
431 pub(crate) fn record_ids(
432 &mut self,
433 keys: Vec<SourceObject>,
434 ids: Vec<(CatalogItemId, GlobalId)>,
435 ) {
436 self.statement.record_ids(keys, ids);
437 }
438
439 pub(crate) fn notices(&self) -> &[PlanNotice] {
440 &self.outcome.notices
441 }
442
443 pub(crate) fn skipped_name(&self) -> Option<&str> {
444 self.outcome.skipped_name.as_deref()
445 }
446
447 pub(in crate::catalog) fn check_outcome(
448 &mut self,
449 outcome: SourceOutcome,
450 ) -> Result<(), AdapterError> {
451 if self.observed && self.outcome != outcome {
452 return Err(AdapterError::DDLTransactionOutcomeChanged);
453 }
454 self.outcome = outcome;
455 Ok(())
456 }
457}
458
459#[derive(Debug)]
460pub(crate) struct StagedSourceStatement {
461 pub command: SourceCommand,
462 pub state: CatalogState,
463 pub snapshot: mz_catalog::durable::Snapshot,
464}