Skip to content

Commit c2cc48d

Browse files
committed
sql: switch all errors to PlanError
Most of the functions still produce only PlanError::Unstructured, but this will allow us to incrementally move each error to a structured error type. This allows the error structure to be propagated upwards to the coordinator when it exists, which will allow the structured SQL errors to return hints and long form descriptions. In particular, after this change, the error messages under construction in MaterializeInc#13445 will be able to properly emit hints.
1 parent bca844c commit c2cc48d

File tree

20 files changed

+615
-559
lines changed

20 files changed

+615
-559
lines changed

src/adapter/src/error.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,7 @@ impl AdapterError {
180180
"The query references the following log sources:\n {}",
181181
log_names.join("\n "),
182182
)),
183+
AdapterError::PlanError(e) => e.detail(),
183184
_ => None,
184185
}
185186
}
@@ -224,6 +225,7 @@ impl AdapterError {
224225
selection, use `RESET cluster_replica`."
225226
.into(),
226227
),
228+
AdapterError::PlanError(e) => e.hint(),
227229
_ => None,
228230
}
229231
}

src/sql/src/func.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1050,7 +1050,7 @@ where
10501050
}
10511051
(FuncSpec::Op(_), [..]) => unreachable!("non-unary non-binary operator"),
10521052
};
1053-
PlanError::Unstructured(format!("{}: {}", context, e))
1053+
sql_err!("{}: {}", context, e)
10541054
})
10551055
}
10561056

@@ -2857,7 +2857,7 @@ pub static MZ_CATALOG_BUILTINS: Lazy<HashMap<&'static str, Func>> = Lazy::new(||
28572857
params!(String, String) => Operation::binary(move |_ecx, regex, haystack| {
28582858
let regex = match regex.into_literal_string() {
28592859
None => sql_bail!("regex_extract requires a string literal as its first argument"),
2860-
Some(regex) => mz_expr::AnalyzedRegex::new(&regex).map_err(|e| PlanError::Unstructured(format!("analyzing regex: {}", e)))?,
2860+
Some(regex) => mz_expr::AnalyzedRegex::new(&regex).map_err(|e| sql_err!("analyzing regex: {}", e))?,
28612861
};
28622862
let column_names = regex
28632863
.capture_groups_iter()

src/sql/src/kafka_util.rs

Lines changed: 37 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ use std::collections::BTreeMap;
1313
use std::convert::{self, TryInto};
1414
use std::sync::{Arc, Mutex};
1515

16-
use anyhow::bail;
1716
use rdkafka::client::ClientContext;
1817
use rdkafka::consumer::{BaseConsumer, Consumer, ConsumerContext};
1918
use rdkafka::{Offset, TopicPartitionList};
@@ -29,6 +28,7 @@ use mz_storage::client::connections::{
2928
};
3029

3130
use crate::normalize::SqlValueOrSecret;
31+
use crate::plan::PlanError;
3232

3333
enum ValType {
3434
String { transform: fn(String) -> String },
@@ -96,7 +96,7 @@ impl Config {
9696
fn extract(
9797
input: &mut BTreeMap<String, SqlValueOrSecret>,
9898
configs: &[Config],
99-
) -> Result<BTreeMap<String, StringOrSecret>, anyhow::Error> {
99+
) -> Result<BTreeMap<String, StringOrSecret>, PlanError> {
100100
let mut out = BTreeMap::new();
101101
for config in configs {
102102
// Look for config.name
@@ -109,7 +109,7 @@ fn extract(
109109
Ok(parsed_n) if *lower <= parsed_n && parsed_n <= *upper => {
110110
StringOrSecret::String(n.to_string())
111111
}
112-
_ => bail!("must be a number between {} and {}", lower, upper),
112+
_ => sql_bail!("must be a number between {} and {}", lower, upper),
113113
}
114114
}
115115
(Some(SqlValueOrSecret::Value(Value::String(s))), ValType::String { transform }) => {
@@ -128,14 +128,14 @@ fn extract(
128128
None => continue,
129129
},
130130
(Some(SqlValueOrSecret::Value(v)), _) => {
131-
bail!(
131+
sql_bail!(
132132
"Invalid WITH option {}={}: unexpected value type",
133133
config.name,
134134
v
135135
);
136136
}
137137
(Some(SqlValueOrSecret::Secret(_)), _) => {
138-
bail!(
138+
sql_bail!(
139139
"WITH option {} does not accept secret references",
140140
config.name
141141
);
@@ -158,7 +158,7 @@ fn extract(
158158
/// `sql_parser::ast::Value::String`.
159159
pub fn extract_config(
160160
with_options: &mut BTreeMap<String, SqlValueOrSecret>,
161-
) -> anyhow::Result<BTreeMap<String, StringOrSecret>> {
161+
) -> Result<BTreeMap<String, StringOrSecret>, PlanError> {
162162
extract(
163163
with_options,
164164
&[
@@ -222,7 +222,7 @@ pub async fn create_consumer(
222222
options: &BTreeMap<String, StringOrSecret>,
223223
librdkafka_log_level: tracing::Level,
224224
secrets_reader: &SecretsReader,
225-
) -> Result<Arc<BaseConsumer<KafkaErrCheckContext>>, anyhow::Error> {
225+
) -> Result<Arc<BaseConsumer<KafkaErrCheckContext>>, PlanError> {
226226
let mut config = create_new_client_config(librdkafka_log_level);
227227
mz_storage::client::connections::populate_client_config(
228228
kafka_connection.clone(),
@@ -238,8 +238,11 @@ pub async fn create_consumer(
238238
.get("bootstrap.servers")
239239
.expect("callers must have already set bootstrap.servers");
240240

241-
let consumer: Arc<BaseConsumer<KafkaErrCheckContext>> =
242-
Arc::new(config.create_with_context(KafkaErrCheckContext::default())?);
241+
let consumer: Arc<BaseConsumer<KafkaErrCheckContext>> = Arc::new(
242+
config
243+
.create_with_context(KafkaErrCheckContext::default())
244+
.map_err(|e| sql_err!("{}", e))?,
245+
);
243246
let context = Arc::clone(&consumer.context());
244247
let owned_topic = String::from(topic);
245248
// Wait for a metadata request for up to one second. This greatly
@@ -253,10 +256,11 @@ pub async fn create_consumer(
253256
let _ = consumer.fetch_metadata(Some(&owned_topic), Duration::from_secs(1));
254257
}
255258
})
256-
.await?;
259+
.await
260+
.map_err(|e| sql_err!("{}", e))?;
257261
let error = context.error.lock().expect("lock poisoned");
258262
if let Some(error) = &*error {
259-
bail!("librdkafka: {}", error)
263+
sql_bail!("librdkafka: {}", error)
260264
}
261265
Ok(consumer)
262266
}
@@ -281,11 +285,11 @@ pub async fn lookup_start_offsets(
281285
topic: &str,
282286
with_options: &BTreeMap<String, SqlValueOrSecret>,
283287
now: u64,
284-
) -> Result<Option<Vec<i64>>, anyhow::Error> {
288+
) -> Result<Option<Vec<i64>>, PlanError> {
285289
let time_offset = match with_options.get("kafka_time_offset").cloned() {
286290
None => return Ok(None),
287291
Some(_) if with_options.contains_key("start_offset") => {
288-
bail!("`start_offset` and `kafka_time_offset` cannot be set at the same time.")
292+
sql_bail!("`start_offset` and `kafka_time_offset` cannot be set at the same time.")
289293
}
290294
Some(offset) => offset,
291295
};
@@ -298,15 +302,15 @@ pub async fn lookup_start_offsets(
298302
let now: i64 = now.try_into()?;
299303
let ts = now - ts.abs();
300304
if ts <= 0 {
301-
bail!("Relative `kafka_time_offset` must be smaller than current system timestamp")
305+
sql_bail!("Relative `kafka_time_offset` must be smaller than current system timestamp")
302306
}
303307
ts
304308
}
305309
// Timestamp in millis (e.g. 1622659034343)
306310
Ok(ts) => ts,
307-
_ => bail!("`kafka_time_offset` must be a number"),
311+
_ => sql_bail!("`kafka_time_offset` must be a number"),
308312
},
309-
_ => bail!("`kafka_time_offset` must be a number"),
313+
_ => sql_bail!("`kafka_time_offset` must be a number"),
310314
};
311315

312316
// Lookup offsets
@@ -319,14 +323,18 @@ pub async fn lookup_start_offsets(
319323
consumer.as_ref().client(),
320324
&topic,
321325
Duration::from_secs(10),
322-
)?
326+
)
327+
.map_err(|e| sql_err!("{}", e))?
323328
.len();
324329

325330
let mut tpl = TopicPartitionList::with_capacity(1);
326331
tpl.add_partition_range(&topic, 0, num_partitions as i32 - 1);
327-
tpl.set_all_offsets(Offset::Offset(time_offset))?;
332+
tpl.set_all_offsets(Offset::Offset(time_offset))
333+
.map_err(|e| sql_err!("{}", e))?;
328334

329-
let offsets_for_times = consumer.offsets_for_times(tpl, Duration::from_secs(10))?;
335+
let offsets_for_times = consumer
336+
.offsets_for_times(tpl, Duration::from_secs(10))
337+
.map_err(|e| sql_err!("{}", e))?;
330338

331339
// Translate to `start_offsets`
332340
let start_offsets = offsets_for_times
@@ -335,7 +343,7 @@ pub async fn lookup_start_offsets(
335343
.map(|elem| match elem.offset() {
336344
Offset::Offset(offset) => Ok(offset),
337345
Offset::End => fetch_end_offset(&consumer, &topic, elem.partition()),
338-
_ => bail!(
346+
_ => sql_bail!(
339347
"Unexpected offset {:?} for partition {}",
340348
elem.offset(),
341349
elem.partition()
@@ -344,7 +352,7 @@ pub async fn lookup_start_offsets(
344352
.collect::<Result<Vec<_>, _>>()?;
345353

346354
if start_offsets.len() != num_partitions {
347-
bail!(
355+
sql_bail!(
348356
"Expected offsets for {} partitions, but received {}",
349357
num_partitions,
350358
start_offsets.len(),
@@ -354,7 +362,8 @@ pub async fn lookup_start_offsets(
354362
Ok(Some(start_offsets))
355363
}
356364
})
357-
.await?
365+
.await
366+
.map_err(|e| sql_err!("{}", e))?
358367
}
359368

360369
// Kafka supports bulk lookup of watermarks, but it is not exposed in rdkafka.
@@ -365,8 +374,10 @@ fn fetch_end_offset(
365374
consumer: &BaseConsumer<KafkaErrCheckContext>,
366375
topic: &str,
367376
pid: i32,
368-
) -> Result<i64, anyhow::Error> {
369-
let (_low, high) = consumer.fetch_watermarks(topic, pid, Duration::from_secs(10))?;
377+
) -> Result<i64, PlanError> {
378+
let (_low, high) = consumer
379+
.fetch_watermarks(topic, pid, Duration::from_secs(10))
380+
.map_err(|e| sql_err!("{}", e))?;
370381
Ok(high)
371382
}
372383

@@ -410,7 +421,7 @@ impl ClientContext for KafkaErrCheckContext {
410421
pub fn generate_ccsr_connection(
411422
url: Url,
412423
ccsr_options: &mut BTreeMap<String, SqlValueOrSecret>,
413-
) -> Result<CsrConnection, anyhow::Error> {
424+
) -> Result<CsrConnection, PlanError> {
414425
let mut ccsr_options = extract(
415426
ccsr_options,
416427
&[
@@ -435,7 +446,7 @@ pub fn generate_ccsr_connection(
435446
let key = key.unwrap_secret();
436447
Some(CsrConnectionTlsIdentity { cert, key })
437448
}
438-
_ => bail!(
449+
_ => sql_bail!(
439450
"Reading from SSL-auth Confluent Schema Registry \
440451
requires both ssl.key.pem and ssl.certificate.pem"
441452
),

src/sql/src/lib.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,10 +82,15 @@ macro_rules! bail_unsupported {
8282
};
8383
}
8484

85-
// TODO(benesch): delete this once we use structured errors everywhere.
85+
// TODO(benesch): delete these macros once we use structured errors everywhere.
8686
macro_rules! sql_bail {
8787
($($e:expr),* $(,)?) => {
88-
return Err(crate::plan::error::PlanError::Unstructured(format!($($e),*)))
88+
return Err(sql_err!($($e),*))
89+
}
90+
}
91+
macro_rules! sql_err {
92+
($($e:expr),* $(,)?) => {
93+
crate::plan::error::PlanError::Unstructured(format!($($e),*))
8994
}
9095
}
9196

src/sql/src/names.rs

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99

1010
//! Structured name types for SQL objects.
1111
12-
use anyhow::Error;
1312
use std::collections::{HashMap, HashSet};
1413
use std::fmt;
1514
use std::str::FromStr;
@@ -616,7 +615,7 @@ impl fmt::Display for SchemaId {
616615
}
617616

618617
impl FromStr for SchemaId {
619-
type Err = Error;
618+
type Err = PlanError;
620619

621620
fn from_str(s: &str) -> Result<Self, Self::Err> {
622621
let val: u64 = s.parse()?;
@@ -643,7 +642,7 @@ impl fmt::Display for DatabaseId {
643642
}
644643

645644
impl FromStr for DatabaseId {
646-
type Err = Error;
645+
type Err = PlanError;
647646

648647
fn from_str(s: &str) -> Result<Self, Self::Err> {
649648
let val: u64 = s.parse()?;
@@ -769,10 +768,10 @@ impl<'a> Fold<Raw, Aug> for NameResolver<'a> {
769768
let cte_name = normalize::ident(cte.alias.name.clone());
770769

771770
if used_names.contains(&cte_name) {
772-
self.status = Err(PlanError::Unstructured(format!(
771+
self.status = Err(sql_err!(
773772
"WITH query name \"{}\" specified more than once",
774773
cte_name
775-
)));
774+
));
776775
}
777776
used_names.insert(cte_name.clone());
778777

@@ -879,8 +878,7 @@ impl<'a> Fold<Raw, Aug> for NameResolver<'a> {
879878
Some(item) => item,
880879
None => {
881880
if self.status.is_ok() {
882-
self.status =
883-
Err(PlanError::Unstructured(format!("invalid id {}", &gid)));
881+
self.status = Err(sql_err!("invalid id {}", &gid));
884882
}
885883
return ResolvedObjectName::Error;
886884
}
@@ -891,7 +889,7 @@ impl<'a> Fold<Raw, Aug> for NameResolver<'a> {
891889
Ok(full_name) => full_name,
892890
Err(e) => {
893891
if self.status.is_ok() {
894-
self.status = Err(e.into());
892+
self.status = Err(e);
895893
}
896894
return ResolvedObjectName::Error;
897895
}

0 commit comments

Comments
 (0)