-
Notifications
You must be signed in to change notification settings - Fork 62
ErrorHandling
The PartiQL library provides a robust error reporting mechanism, and this usage guide aims to show how you can leverage the exposed APIs.
This usage guide is aimed at developers who use any one of PartiQL's components for their
application. If you are looking for how to change how errors are reported in the CLI, please run: partiql --help
.
To elaborate on why this usage guide may be useful to you, the developer, let us assume that your company provides a CLI to enable your customers to execute PartiQL queries. When a user is typing a query and references a table that doesn't exist, your CLI might want to highlight that error and halt processing of the query to save on computational costs. Or, your CLI might want to highlight the error but continue processing the query to accumulate errors to better enable the developer to see all of their mistakes at once. In any case, the PartiQL library allows developers to register their own error listeners to take control over their customers' experience.
Each major component (parser, planner, compiler) of the PartiQL Library allows for the registration of an ErrorListener
that will receive every warning/error that the particular component emits. The default error listener aborts the
component's execution, by throwing an PErrorListenerException
, upon encountering the first error. This behavior aims to
protect developers who might have decided to avoid reading this documentation. However, as seen further below, this is
easy to override.
In the scenario where you want to halt one of the components when a particular warning/error is emitted, error listeners
have the ability to throw an PErrorListenerException
. This exception acts as a wrapper over any exception you'd like to
halt with. For example:
import org.partiql.spi.errors.PError;
import org.partiql.spi.errors.PErrorListener;
import java.lang.annotation.Native;
class AbortWhenAlwaysMissing extends PErrorListener {
// This is to be used to halt my application after the component finishes execution
boolean hasErrors = false;
@Override
void error(@NotNull PError error) throws PErrorListenerException {
System.out.println("e: " + getErrorMessage(error));
hasErrors = true;
}
@Override
void warning(@NotNull PError error) throws PErrorListenerException {
if (error.getCode() == Error.ALWAYS_MISSING) {
throw new PErrorListenerException("This system does not allow for expressions that always return missing!");
}
println("w: " + getErrorMessage(error));
}
private fun getErrorMessage(@NotNull PError error) {
// Internal implementation details
}
}
NOTE: If you throw an exception that is not an PErrorListenerException
, the component that contains your registered
ErrorListener
will catch the exception and send an error to your ErrorListener
with a code of
Error.INTERNAL_ERROR
. This will lead to a duplication of errors (which can be a bad experience for your
customers).
Each component allows for the registration of a custom error listener upon instantiation. For example, let's say you
intend on registering the AbortWhenAlwaysMissing
error listener from above:
public class Foo {
public static void main(String[] args) {
// Error Listener
PErrorListener listener = AbortWhenAlwaysMissing();
// User Input
String query = args[0];
Statement ast = parse(query);
// Planner Component
PartiQLPlanner planner = PartiQLPlanner.standard();
Context plannerConfig = Context.of(listener); // Registration here!!
// Planning and catching the PErrorListenerException
Plan plan;
try {
plan = planner.plan(ast, plannerConfig);
} catch (PErrorListenerException ex) {
throw ex;
}
// Do more ...
}
private Statement parse(String query) {
// Calling the PartiQL Parser, handling PErrorListenerExceptions, etc.
}
}
Errors and warnings are both represented by the same data structure, an Error
. In the case of an error/warning, it is
up to the respective component to correctly send the Error
to either ErrorListener.error()
or
ErrorListener.warning()
.
The Error
Java class allows for developers to introspect its properties to determine how to create their own error
messages. See the [Javadocs] for the available methods.
As mentioned above, the Error
Java class exposes information for database implementers to write high quality error
messages. Specifically, Error
exposes a method, int getCode()
, to return the enumerated error code received. All
possible error codes are represented as static final fields in
the Error Javadocs.
An error code MAY have additional properties accessible via the .get(...)
API – please consult the Javadocs for an
error code's property usage.
Now, here's an example of how you might write a quality error message:
public class ConsolePErrorListener extends PErrorListener {
boolean hasErrors = false;
@Override
void error(@NotNull PError error) throws PErrorListenerException {
String message = getMessage(error, "e: ");
System.out.println(message);
hasErrors = true;
}
@Override
void warning(@NotNull PError error) throws PErrorListenerException {
String message = getMessage(error, "w: ");
System.out.println(message);
}
static String getMessage(@NotNull PError error, @NotNull String prefix) {
switch (error.getCode()) {
case Error.ALWAYS_MISSING:
SourceLocation location = error.location;
String locationStr = getNullSafeLocation(location);
return prefix + locationStr + " Expression always evaluates to missing.";
case Error.FEATURE_NOT_SUPPORTED:
String name = (String) error.getProperty(Property.FEATURE_NAME);
if (name == null) {
name = "UNKNOWN";
}
return prefix + "Feature (" + name + ") not yet supported.";
default:
return "Unhandled error code received.";
}
}
@NotNull
String getNullSafeLocation(@Nullable SourceLocation location) {
// Internal implementation
}
}
Each of PartiQL's components produce a structure for future use. The parser outputs an AST, the planner outputs a plan, and the compiler outputs an executable. What happens when any of the components experience an error/warning?
The answer, as is often in software, depends. Since this error reporting mechanism allows developers to register error listeners that accumulate all errors, the PartiQL components still continue processing until terminated by an error listener. That being said, when error listeners receive an error, one must assume that the output of the component is a dud and is incorrect. Therefore, if the parser has produced errors with a malformed AST, you shouldn't pass the AST to the planner to continue evaluation.
However, if warnings have been emitted, the output can still be safely relied upon. For example, let's use the same error listener we wrote further above:
class Example {
public Plan planInternal(Statement ast) throws PlanningFailure {
AbortWhenAlwaysMissing listener = AbortWhenAlwaysMissing();
PartiQLPlanner planner = PartiQLPlanner.standard();
Context plannerConfig = Context.of(listener);
Plan plan;
try {
plan = planner.plan(ast, plannerConfig);
} catch (PErrorListenerException ex) {
throw new PlanningFailure(ex);
}
// If an error has been reported to the listener, implementers
// should NOT trust the plan that has been returned.
if (listener.hasErrors) {
throw new PlanningFailure("Errors encountered. Exiting.");
}
return plan;
}
}
Error listeners are specifically meant to provide control over the reporting of errors for PartiQL's major components (parser,
planner, and compiler). However, for the execution of compiled statements, PartiQL still provides errors (and error codes)
by throwing an EvaluationException
which exposes a method, Error getError()
. The EvaluationException
does not
expose a message, cause, or stacktrace.
Here is an example of how you can leverage this functionality below:
class MyApplication {
void executeAndPrint(PreparedStatement stmt, Session session) {
Datum lazyData;
try {
lazyData = stmt.execute(session);
// Iterate through the lazyData and print to the console.
} catch (EvaluationException e) {
System.out.println(ConsoleErrorListener.getMessage(e.getError(), "e: "));
}
}
}
The PartiQL CLI offers multiple ways to process warnings/errors. See the flags -Werror
, -w
,
--max-errors
, and more when you run partiql --help
. See the CLI Usage Guide
here. The implementation details can be found in the
CLI subproject.
- General
- Tutorials
- Documentation
- Clauses
- Testing
- Serde
- Upgrade Guides
- Design & Development Documents
- V1 Documentation
- Basics
- PartiQL CLI
- Developer Usage Guides