sql_utils/common/errors.h (117 lines of code) (raw):
/*
* Copyright 2023 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef THIRD_PARTY_PY_BIGQUERY_ML_UTILS_SQL_UTILS_COMMON_ERRORS_H_
#define THIRD_PARTY_PY_BIGQUERY_ML_UTILS_SQL_UTILS_COMMON_ERRORS_H_
#include <optional>
#include <string>
#include <string_view>
#include <vector>
#include "google/protobuf/repeated_field.h"
#include "sql_utils/proto/internal_error_location.pb.h"
#include "sql_utils/public/deprecation_warning.pb.h"
#include "sql_utils/public/error_helpers.h"
#include "sql_utils/public/error_location.pb.h"
#include "sql_utils/public/options.pb.h"
#include "sql_utils/public/parse_location.h"
#include "absl/base/optimization.h"
#include "absl/status/statusor.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "absl/types/optional.h"
#include "sql_utils/base/source_location.h"
#include "sql_utils/base/status.h"
#include "sql_utils/base/status_builder.h"
namespace bigquery_ml_utils {
// Make an ErrorSource from <status> and <text>. If <text> is non-empty and
// <mode> is ErrorMessageMode::ERROR_MESSAGE_MULTI_LINE_WITH_CARET, then <text>
// must be related to the ErrorLocation in <status> and a caret string is
// constructed from <text> and <status> ErrorLocation. Otherwise no caret string
// is constructed.
// The returned ErrorSource gets its ErrorLocation from <status>.
// Requires non-OK status.
ErrorSource MakeErrorSource(const absl::Status& status, std::string_view text,
ErrorMessageMode mode);
// Creates a StatusBuilder for SQL errors using the INVALID_ARGUMENT
// error code. Note: if you enable logging on the StatusBuilder, the logged
// file/line location will not be useful.
inline ::bigquery_ml_utils_base::StatusBuilder MakeSqlError() {
return ::bigquery_ml_utils_base::InvalidArgumentErrorBuilder();
}
// Returns MakeSqlError() annotated with <point> as the error location.
inline ::bigquery_ml_utils_base::StatusBuilder MakeSqlErrorAtPoint(ParseLocationPoint point) {
return MakeSqlError().Attach(point.ToInternalErrorLocation());
}
// Returns an UnimplementedError with a payload corresponding to the given error
// location. This is similar to MakeSqlErrorAtPoint(), except for the error
// code.
inline ::bigquery_ml_utils_base::StatusBuilder MakeUnimplementedErrorAtPoint(
ParseLocationPoint point) {
return bigquery_ml_utils_base::UnimplementedErrorBuilder().Attach(
point.ToInternalErrorLocation());
}
// Returns a StatusBuilder for SQL evaluation errors, using the OUT_OF_RANGE
// error code. Note: if you enable logging on the StatusBuilder, the logged
// file/line location will not be useful.
inline ::bigquery_ml_utils_base::StatusBuilder MakeEvalError() {
return ::bigquery_ml_utils_base::OutOfRangeErrorBuilder();
}
// Same, but uses <error_location> as the error location.
absl::Status StatusWithInternalErrorLocation(
const absl::Status& status, const ParseLocationPoint& error_location);
// If <status> is OK or if it does not have a InternalErrorLocation payload,
// returns <status>. Otherwise, replaces the InternalErrorLocation payload by an
// ErrorLocation payload. 'query' should be the query that was parsed; it is
// used to convert the error location from line/column to byte offset or vice
// versa. This function must be called on all errors before returning them to
// the client. An InternalErrorLocation contained in 'status' must be valid for
// 'query'. If it is not, then this function returns an internal error.
absl::Status ConvertInternalErrorLocationToExternal(absl::Status status,
absl::string_view query);
inline std::string ExtractingNotSupportedDatePart(
absl::string_view from_type, absl::string_view date_part_name) {
return absl::StrCat("EXTRACT from ", from_type, " does not support the ",
date_part_name, " date part");
}
// Returns ErrorSources from <status>, if present.
std::optional<::google::protobuf::RepeatedPtrField<ErrorSource>> GetErrorSources(
const absl::Status& status);
// Sets ErrorSources on <error_location_in> from <status>, including
// a new ErrorSource built from <status> (using <input_text_for_status> to
// build a caret string for it), along with other ErrorSources from inside
// <status> (if any). If <input_text_for_status> is empty, then the new
// ErrorSource based on <status> will not include a caret string.
// The 'ErrorLocationType' should be either an ErrorLocation or an
// InternalErrorLocation.
template <typename ErrorLocationType>
ErrorLocationType SetErrorSourcesFromStatus(
const ErrorLocationType& error_location_in, const absl::Status& status,
ErrorMessageMode mode, std::string_view input_text_for_status = "") {
if (status.ok()) {
// An OK status should not have any payload, so just return the
// InternalErrorLocation.
return error_location_in;
}
ErrorLocationType error_location =
SetErrorSourcesFromStatusWithoutOutermostError(error_location_in, status);
const ErrorSource additional_error_source =
MakeErrorSource(status, input_text_for_status, mode);
*error_location.add_error_source() = additional_error_source;
return error_location;
}
// If <status> has ErrorSources, copies them into <error_location_in>.
// Otherwise returns <error_location_in>.
// Note: This does not copy the outermost error from <status>. This is
// intended for cases where the caller will replace the outermost error
// message and location, but wants to preserve its original recursive
// sources. The 'ErrorLocationType' should be either an ErrorLocation or an
// InternalErrorLocation.
template <typename ErrorLocationType>
ErrorLocationType SetErrorSourcesFromStatusWithoutOutermostError(
const ErrorLocationType& error_location_in, const absl::Status& status) {
if (status.ok()) {
// An OK status should not have any payload, so just return the
// error location.
return error_location_in;
}
ErrorLocationType error_location = error_location_in;
std::optional<const ::google::protobuf::RepeatedPtrField<ErrorSource>> error_sources =
GetErrorSources(status);
if (error_sources.has_value()) {
*error_location.mutable_error_source() = *error_sources;
}
return error_location;
}
// Returns <warnings> as a string suitable for debug output.
std::string DeprecationWarningsToDebugString(
const std::vector<FreestandingDeprecationWarning>& warnings);
// Converts <warning> to a absl::Status.
inline absl::Status DeprecationWarningToStatus(
const FreestandingDeprecationWarning& warning) {
return MakeSqlError()
.Attach(warning.error_location())
.Attach(warning.deprecation_warning())
<< warning.message();
}
// Converts <from_status> to a FreestandingDeprecationWarning. Returns an error
// if <from_status> does not represent a valid deprecation warning. In
// particular, 'from_status' must have a DeprecationWarning extension and an
// ErrorLocation (and cannot have an InternalErrorLocation or any other
// payload).
absl::StatusOr<FreestandingDeprecationWarning> StatusToDeprecationWarning(
const absl::Status& from_status, absl::string_view sql);
// Same as above, but for a vector of absl::Statuses.
absl::StatusOr<std::vector<FreestandingDeprecationWarning>>
StatusesToDeprecationWarnings(const std::vector<absl::Status>& from_statuses,
absl::string_view sql);
// This function potentially performs two actions:
// 1) Converts a SQL 'internal' Status (with InternalErrorLocation)
// into a Status that can be returned from the SQL library (that
// has ErrorLocation in place of InternalErrorLocation). If <status>
// does not have an InternalErrorLocation (including OK status) then
// this step is a no-op.
// 2) Updates the Status error message and/or removes the location payload,
// as determined by <mode>.
//
// If <status> has an InternalErrorLocation (as byte-offset) payload,
// then converts it to an ErrorLocation (line/column) payload instead
// (the line/column is determined from the byte-offset relative to
// <input_string>).
//
// If <mode> is ERROR_MESSAGE_WITH_PAYLOAD, then returns the (possibly
// updated) Status (with ErrorLocation if applicable). For other modes,
// updates the Status error string to include the external ErrorLocation
// info (line/offset), then clears the ErrorLocation payload from the
// Status and returns that Status.
inline absl::Status ConvertInternalErrorLocationAndAdjustErrorString(
ErrorMessageMode mode, absl::string_view input_string,
const absl::Status& status) {
if (status.ok()) return status;
const absl::Status new_status =
ConvertInternalErrorLocationToExternal(status, input_string);
if (mode == ERROR_MESSAGE_WITH_PAYLOAD) {
return new_status;
}
return MaybeUpdateErrorFromPayload(mode, input_string, new_status);
}
// Same as above, but for a vector of absl::Statuses.
inline std::vector<absl::Status>
ConvertInternalErrorLocationsAndAdjustErrorStrings(
ErrorMessageMode mode, absl::string_view input_string,
const std::vector<absl::Status>& statuses) {
if (statuses.empty()) return statuses;
std::vector<absl::Status> new_statuses;
new_statuses.reserve(statuses.size());
for (const absl::Status& status : statuses) {
new_statuses.push_back(ConvertInternalErrorLocationAndAdjustErrorString(
mode, input_string, status));
}
return new_statuses;
}
} // namespace bigquery_ml_utils
#endif // THIRD_PARTY_PY_BIGQUERY_ML_UTILS_SQL_UTILS_COMMON_ERRORS_H_