Skip to content

Commit 64beec8

Browse files
authored
Merge pull request #20423 from mjibson/error-codes
environmentd: add errors codes to http/ws endpoints
2 parents 56596ea + 332fd5b commit 64beec8

File tree

6 files changed

+149
-142
lines changed

6 files changed

+149
-142
lines changed

src/adapter/src/error.rs

+99
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ use mz_storage_client::controller::StorageError;
2929
use mz_transform::TransformError;
3030
use smallvec::SmallVec;
3131
use tokio::sync::oneshot;
32+
use tokio_postgres::error::SqlState;
3233

3334
use crate::{catalog, rbac};
3435

@@ -345,6 +346,104 @@ impl AdapterError {
345346
}
346347
}
347348

349+
pub fn code(&self) -> SqlState {
350+
// TODO(benesch): we should only use `SqlState::INTERNAL_ERROR` for
351+
// those errors that are truly internal errors. At the moment we have
352+
// a various classes of uncategorized errors that use this error code
353+
// inappropriately.
354+
match self {
355+
// DATA_EXCEPTION to match what Postgres returns for degenerate
356+
// range bounds
357+
AdapterError::AbsurdSubscribeBounds { .. } => SqlState::DATA_EXCEPTION,
358+
AdapterError::AmbiguousSystemColumnReference => SqlState::FEATURE_NOT_SUPPORTED,
359+
AdapterError::BadItemInStorageCluster { .. } => SqlState::FEATURE_NOT_SUPPORTED,
360+
AdapterError::Catalog(_) => SqlState::INTERNAL_ERROR,
361+
AdapterError::ChangedPlan => SqlState::FEATURE_NOT_SUPPORTED,
362+
AdapterError::DuplicateCursor(_) => SqlState::DUPLICATE_CURSOR,
363+
AdapterError::Eval(EvalError::CharacterNotValidForEncoding(_)) => {
364+
SqlState::PROGRAM_LIMIT_EXCEEDED
365+
}
366+
AdapterError::Eval(EvalError::CharacterTooLargeForEncoding(_)) => {
367+
SqlState::PROGRAM_LIMIT_EXCEEDED
368+
}
369+
AdapterError::Eval(EvalError::LengthTooLarge) => SqlState::PROGRAM_LIMIT_EXCEEDED,
370+
AdapterError::Eval(EvalError::NullCharacterNotPermitted) => {
371+
SqlState::PROGRAM_LIMIT_EXCEEDED
372+
}
373+
AdapterError::Eval(_) => SqlState::INTERNAL_ERROR,
374+
AdapterError::Explain(_) => SqlState::INTERNAL_ERROR,
375+
AdapterError::IdExhaustionError => SqlState::INTERNAL_ERROR,
376+
AdapterError::Internal(_) => SqlState::INTERNAL_ERROR,
377+
AdapterError::IntrospectionDisabled { .. } => SqlState::FEATURE_NOT_SUPPORTED,
378+
AdapterError::InvalidLogDependency { .. } => SqlState::FEATURE_NOT_SUPPORTED,
379+
AdapterError::InvalidClusterReplicaAz { .. } => SqlState::FEATURE_NOT_SUPPORTED,
380+
AdapterError::InvalidClusterReplicaSize { .. } => SqlState::FEATURE_NOT_SUPPORTED,
381+
AdapterError::InvalidSetIsolationLevel => SqlState::ACTIVE_SQL_TRANSACTION,
382+
AdapterError::InvalidStorageClusterSize { .. } => SqlState::FEATURE_NOT_SUPPORTED,
383+
AdapterError::SourceOrSinkSizeRequired { .. } => SqlState::FEATURE_NOT_SUPPORTED,
384+
AdapterError::InvalidTableMutationSelection => SqlState::INVALID_TRANSACTION_STATE,
385+
AdapterError::ConstraintViolation(NotNullViolation(_)) => SqlState::NOT_NULL_VIOLATION,
386+
AdapterError::NoClusterReplicasAvailable(_) => SqlState::FEATURE_NOT_SUPPORTED,
387+
AdapterError::OperationProhibitsTransaction(_) => SqlState::ACTIVE_SQL_TRANSACTION,
388+
AdapterError::OperationRequiresTransaction(_) => SqlState::NO_ACTIVE_SQL_TRANSACTION,
389+
AdapterError::ParseError(_) => SqlState::SYNTAX_ERROR,
390+
AdapterError::PlanError(PlanError::InvalidSchemaName) => SqlState::INVALID_SCHEMA_NAME,
391+
AdapterError::PlanError(_) => SqlState::INTERNAL_ERROR,
392+
AdapterError::PreparedStatementExists(_) => SqlState::DUPLICATE_PSTATEMENT,
393+
AdapterError::ReadOnlyTransaction => SqlState::READ_ONLY_SQL_TRANSACTION,
394+
AdapterError::ReadWriteUnavailable => SqlState::INVALID_TRANSACTION_STATE,
395+
AdapterError::StatementTimeout => SqlState::QUERY_CANCELED,
396+
AdapterError::Canceled => SqlState::QUERY_CANCELED,
397+
AdapterError::IdleInTransactionSessionTimeout => {
398+
SqlState::IDLE_IN_TRANSACTION_SESSION_TIMEOUT
399+
}
400+
AdapterError::RecursionLimit(_) => SqlState::INTERNAL_ERROR,
401+
AdapterError::RelationOutsideTimeDomain { .. } => SqlState::INVALID_TRANSACTION_STATE,
402+
AdapterError::ResourceExhaustion { .. } => SqlState::INSUFFICIENT_RESOURCES,
403+
AdapterError::ResultSize(_) => SqlState::OUT_OF_MEMORY,
404+
AdapterError::SafeModeViolation(_) => SqlState::INTERNAL_ERROR,
405+
AdapterError::SqlCatalog(_) => SqlState::INTERNAL_ERROR,
406+
AdapterError::SubscribeOnlyTransaction => SqlState::INVALID_TRANSACTION_STATE,
407+
AdapterError::Transform(_) => SqlState::INTERNAL_ERROR,
408+
AdapterError::UnallowedOnCluster { .. } => {
409+
SqlState::S_R_E_PROHIBITED_SQL_STATEMENT_ATTEMPTED
410+
}
411+
AdapterError::Unauthorized(_) => SqlState::INSUFFICIENT_PRIVILEGE,
412+
AdapterError::UncallableFunction { .. } => SqlState::FEATURE_NOT_SUPPORTED,
413+
AdapterError::UnknownCursor(_) => SqlState::INVALID_CURSOR_NAME,
414+
AdapterError::UnknownPreparedStatement(_) => SqlState::UNDEFINED_PSTATEMENT,
415+
AdapterError::UnknownLoginRole(_) => SqlState::INVALID_AUTHORIZATION_SPECIFICATION,
416+
AdapterError::UnknownClusterReplica { .. } => SqlState::UNDEFINED_OBJECT,
417+
AdapterError::UnmaterializableFunction(_) => SqlState::FEATURE_NOT_SUPPORTED,
418+
AdapterError::UnrecognizedConfigurationParam(_) => SqlState::UNDEFINED_OBJECT,
419+
AdapterError::UnstableDependency { .. } => SqlState::FEATURE_NOT_SUPPORTED,
420+
AdapterError::Unsupported(..) => SqlState::FEATURE_NOT_SUPPORTED,
421+
AdapterError::Unstructured(_) => SqlState::INTERNAL_ERROR,
422+
AdapterError::UntargetedLogRead { .. } => SqlState::FEATURE_NOT_SUPPORTED,
423+
// It's not immediately clear which error code to use here because a
424+
// "write-only transaction" and "single table write transaction" are
425+
// not things in Postgres. This error code is the generic "bad txn thing"
426+
// code, so it's probably the best choice.
427+
AdapterError::WriteOnlyTransaction => SqlState::INVALID_TRANSACTION_STATE,
428+
AdapterError::MultiTableWriteTransaction => SqlState::INVALID_TRANSACTION_STATE,
429+
AdapterError::Storage(_) | AdapterError::Compute(_) | AdapterError::Orchestrator(_) => {
430+
SqlState::INTERNAL_ERROR
431+
}
432+
AdapterError::ConcurrentRoleDrop(_) => SqlState::UNDEFINED_OBJECT,
433+
AdapterError::DependentObject(_) => SqlState::DEPENDENT_OBJECTS_STILL_EXIST,
434+
AdapterError::VarError(e) => match e {
435+
VarError::ConstrainedParameter { .. } => SqlState::INVALID_PARAMETER_VALUE,
436+
VarError::FixedValueParameter(_) => SqlState::INVALID_PARAMETER_VALUE,
437+
VarError::InvalidParameterType(_) => SqlState::INVALID_PARAMETER_VALUE,
438+
VarError::InvalidParameterValue { .. } => SqlState::INVALID_PARAMETER_VALUE,
439+
VarError::ReadOnlyParameter(_) => SqlState::CANT_CHANGE_RUNTIME_PARAM,
440+
VarError::UnknownParameter(_) => SqlState::UNDEFINED_OBJECT,
441+
VarError::RequiresUnsafeMode { .. } => SqlState::CANT_CHANGE_RUNTIME_PARAM,
442+
VarError::RequiresFeatureFlag { .. } => SqlState::CANT_CHANGE_RUNTIME_PARAM,
443+
},
444+
}
445+
}
446+
348447
pub fn internal<E: std::fmt::Display>(context: &str, e: E) -> AdapterError {
349448
AdapterError::Internal(format!("{context}: {e}"))
350449
}

src/adapter/src/notice.rs

+40
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ use mz_sql::ast::NoticeSeverity;
1919
use mz_sql::catalog::ErrorMessageObjectDescription;
2020
use mz_sql::plan::PlanNotice;
2121
use mz_sql::session::vars::IsolationLevel;
22+
use tokio_postgres::error::SqlState;
2223

2324
/// Notices that can occur in the adapter layer.
2425
///
@@ -138,6 +139,45 @@ impl AdapterNotice {
138139
_ => None
139140
}
140141
}
142+
143+
/// Reports the error code.
144+
pub fn code(&self) -> SqlState {
145+
match self {
146+
AdapterNotice::DatabaseAlreadyExists { .. } => SqlState::DUPLICATE_DATABASE,
147+
AdapterNotice::SchemaAlreadyExists { .. } => SqlState::DUPLICATE_SCHEMA,
148+
AdapterNotice::TableAlreadyExists { .. } => SqlState::DUPLICATE_TABLE,
149+
AdapterNotice::ObjectAlreadyExists { .. } => SqlState::DUPLICATE_OBJECT,
150+
AdapterNotice::DatabaseDoesNotExist { .. } => SqlState::WARNING,
151+
AdapterNotice::ClusterDoesNotExist { .. } => SqlState::WARNING,
152+
AdapterNotice::NoResolvableSearchPathSchema { .. } => SqlState::WARNING,
153+
AdapterNotice::ExistingTransactionInProgress => SqlState::ACTIVE_SQL_TRANSACTION,
154+
AdapterNotice::ExplicitTransactionControlInImplicitTransaction => {
155+
SqlState::NO_ACTIVE_SQL_TRANSACTION
156+
}
157+
AdapterNotice::UserRequested { .. } => SqlState::WARNING,
158+
AdapterNotice::ClusterReplicaStatusChanged { .. } => SqlState::WARNING,
159+
AdapterNotice::DroppedActiveDatabase { .. } => SqlState::WARNING,
160+
AdapterNotice::DroppedActiveCluster { .. } => SqlState::WARNING,
161+
AdapterNotice::QueryTimestamp { .. } => SqlState::WARNING,
162+
AdapterNotice::EqualSubscribeBounds { .. } => SqlState::WARNING,
163+
AdapterNotice::QueryTrace { .. } => SqlState::WARNING,
164+
AdapterNotice::UnimplementedIsolationLevel { .. } => SqlState::WARNING,
165+
AdapterNotice::DroppedSubscribe { .. } => SqlState::WARNING,
166+
AdapterNotice::BadStartupSetting { .. } => SqlState::WARNING,
167+
AdapterNotice::RbacSystemDisabled => SqlState::WARNING,
168+
AdapterNotice::RbacUserDisabled => SqlState::WARNING,
169+
AdapterNotice::RoleMembershipAlreadyExists { .. } => SqlState::WARNING,
170+
AdapterNotice::RoleMembershipDoesNotExists { .. } => SqlState::WARNING,
171+
AdapterNotice::AutoRunOnIntrospectionCluster => SqlState::WARNING,
172+
AdapterNotice::AlterIndexOwner { .. } => SqlState::WARNING,
173+
AdapterNotice::CannotRevoke { .. } => SqlState::WARNING,
174+
AdapterNotice::NonApplicablePrivilegeTypes { .. } => SqlState::WARNING,
175+
AdapterNotice::PlanNotice(plan) => match plan {
176+
PlanNotice::ObjectDoesNotExist { .. } => SqlState::UNDEFINED_OBJECT,
177+
PlanNotice::UpsertSinkKeyNotEnforced { .. } => SqlState::WARNING,
178+
},
179+
}
180+
}
141181
}
142182

143183
impl fmt::Display for AdapterNotice {

src/environmentd/src/http/sql.rs

+1-2
Original file line numberDiff line numberDiff line change
@@ -371,8 +371,7 @@ impl From<AdapterError> for SqlError {
371371
fn from(err: AdapterError) -> Self {
372372
SqlError {
373373
message: err.to_string(),
374-
// TODO: Move codes out of pgwire so they can be shared here.
375-
code: SqlState::INTERNAL_ERROR.code().to_string(),
374+
code: err.code().code().to_string(),
376375
detail: err.detail(),
377376
hint: err.hint(),
378377
}

src/environmentd/tests/testdata/http/post

+2-2
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ http
5454
{"query":"create view v1 as select 1; create view v2 as select 1"}
5555
----
5656
200 OK
57-
{"results":[{"error":{"message":"CREATE VIEW v1 AS SELECT 1 cannot be run inside a transaction block","code":"XX000"},"notices":[]}]}
57+
{"results":[{"error":{"message":"CREATE VIEW v1 AS SELECT 1 cannot be run inside a transaction block","code":"25001"},"notices":[]}]}
5858

5959
# Syntax errors fail the request.
6060
http
@@ -425,4 +425,4 @@ http
425425
{"query":"CREATE MATERIALIZED VIEW _now AS SELECT now()"}
426426
----
427427
200 OK
428-
{"results":[{"error":{"message":"cannot materialize call to current_timestamp","code":"XX000","detail":"See: https://materialize.com/docs/sql/functions/now_and_mz_now/","hint":"Try using `mz_now()` here instead."},"notices":[]}]}
428+
{"results":[{"error":{"message":"cannot materialize call to current_timestamp","code":"0A000","detail":"See: https://materialize.com/docs/sql/functions/now_and_mz_now/","hint":"Try using `mz_now()` here instead."},"notices":[]}]}

src/environmentd/tests/testdata/http/ws

+2-2
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ ws-text
174174
{"query": "CREATE VIEW v AS SELECT 1"}
175175
----
176176
{"type":"CommandStarting","payload":{"has_rows":false,"is_streaming":false}}
177-
{"type":"Error","payload":{"message":"CREATE VIEW v AS SELECT 1 cannot be run inside a transaction block","code":"XX000"}}
177+
{"type":"Error","payload":{"message":"CREATE VIEW v AS SELECT 1 cannot be run inside a transaction block","code":"25001"}}
178178
{"type":"ReadyForQuery","payload":"E"}
179179

180180
ws-text
@@ -276,7 +276,7 @@ ws-text
276276
{"type":"Row","payload":["18446744073709551615",1,1]}
277277
{"type":"CommandComplete","payload":"SUBSCRIBE"}
278278
{"type":"CommandStarting","payload":{"has_rows":false,"is_streaming":false}}
279-
{"type":"Error","payload":{"message":"SUBSCRIBE in transactions must be the only read statement","code":"XX000"}}
279+
{"type":"Error","payload":{"message":"SUBSCRIBE in transactions must be the only read statement","code":"25000"}}
280280
{"type":"ReadyForQuery","payload":"I"}
281281

282282
ws-text rows=2 fixtimestamp=true

0 commit comments

Comments
 (0)