fix(athena): report a failed query's error the way Athena does
StateChangeReason carried the raw engine call failure, HTTP envelope and all:
floci-duck execute returned HTTP 500: {"status":"error","message":...},
ending with the statement as the engine saw it, wrapped in COPY (...) TO. It is
now the engine's own message alone, such as Catalog Error: Table with name nope
does not exist!
This commit is contained in:
@@ -59,6 +59,8 @@ public class AthenaService {
|
||||
public static final String DEFAULT_CATALOG = "AwsDataCatalog";
|
||||
private static final String DEFAULT_OUTPUT_BUCKET = "floci-athena-results";
|
||||
private static final String DEFAULT_WORKGROUP = "primary";
|
||||
private static final Pattern ENGINE_FAILURE = Pattern.compile("^floci-duck \\w+ returned HTTP \\d+: (\\{.*})$", Pattern.DOTALL);
|
||||
private static final com.fasterxml.jackson.databind.ObjectMapper FAILURE_READER = new com.fasterxml.jackson.databind.ObjectMapper();
|
||||
private static final Pattern TRAILING_SEMICOLONS = Pattern.compile("[\\s;]+$");
|
||||
private static final String DEFAULT_ENGINE_VERSION = "Athena engine version 3";
|
||||
private static final long MIN_BYTES_SCANNED_CUTOFF_PER_QUERY = 10_000_000L;
|
||||
@@ -610,10 +612,35 @@ public class AthenaService {
|
||||
|
||||
private void markFailed(String id, QueryExecution execution, Throwable failure) {
|
||||
execution.getStatus().setState(QueryExecutionState.FAILED);
|
||||
execution.getStatus().setStateChangeReason(failure.getMessage());
|
||||
execution.getStatus().setStateChangeReason(failureReason(failure.getMessage()));
|
||||
queryStore.put(id, execution);
|
||||
}
|
||||
|
||||
/**
|
||||
* The engine's own explanation of a failed query, as Athena reports it: without the HTTP
|
||||
* envelope of the engine call, and without the echoed statement, which shows the
|
||||
* internal {@code COPY ... TO} wrapper rather than the query that was sent.
|
||||
*/
|
||||
static String failureReason(String message) {
|
||||
if (message == null) {
|
||||
return null;
|
||||
}
|
||||
Matcher envelope = ENGINE_FAILURE.matcher(message);
|
||||
if (!envelope.matches()) {
|
||||
return message;
|
||||
}
|
||||
try {
|
||||
String engineMessage = FAILURE_READER.readTree(envelope.group(1)).path("message").asText("");
|
||||
if (engineMessage.isBlank()) {
|
||||
return message;
|
||||
}
|
||||
int echo = engineMessage.indexOf("\n\nLINE ");
|
||||
return (echo >= 0 ? engineMessage.substring(0, echo) : engineMessage).strip();
|
||||
} catch (Exception e) {
|
||||
return message;
|
||||
}
|
||||
}
|
||||
|
||||
private record CreateDatabaseDdl(String name, boolean ifNotExists, String comment,
|
||||
String location, Map<String, String> properties) {
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
package io.github.hectorvent.floci.services.athena;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
class AthenaQueryTextTest {
|
||||
|
||||
@Test
|
||||
void dropsTheClosingSemicolonsAthenaAccepts() {
|
||||
assertEquals("SELECT * FROM orders LIMIT 10", AthenaService.withoutTrailingSemicolons("SELECT * FROM orders LIMIT 10;"));
|
||||
assertEquals("SELECT 1", AthenaService.withoutTrailingSemicolons("SELECT 1 ; ;\n"));
|
||||
assertEquals("SELECT ';' AS s", AthenaService.withoutTrailingSemicolons("SELECT ';' AS s"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void reportsTheEngineErrorWithoutItsEnvelopeOrTheWrappedStatement() {
|
||||
String engine = "floci-duck execute returned HTTP 500: {\"status\":\"error\",\"message\":\"Catalog Error: Table with name nope does not exist!\\nDid you mean \\\"orders\\\"?\\n\\nLINE 1: COPY (SELECT * FROM nope) TO 's3://results/q.csv'\"}";
|
||||
assertEquals("Catalog Error: Table with name nope does not exist!\nDid you mean \"orders\"?", AthenaService.failureReason(engine));
|
||||
assertEquals("Failed to call floci-duck execute: timeout", AthenaService.failureReason("Failed to call floci-duck execute: timeout"));
|
||||
}
|
||||
}
|
||||
-15
@@ -1,15 +0,0 @@
|
||||
package io.github.hectorvent.floci.services.athena;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
class AthenaTrailingSemicolonTest {
|
||||
|
||||
@Test
|
||||
void dropsTheClosingSemicolonsAthenaAccepts() {
|
||||
assertEquals("SELECT * FROM orders LIMIT 10", AthenaService.withoutTrailingSemicolons("SELECT * FROM orders LIMIT 10;"));
|
||||
assertEquals("SELECT 1", AthenaService.withoutTrailingSemicolons("SELECT 1 ; ;\n"));
|
||||
assertEquals("SELECT ';' AS s", AthenaService.withoutTrailingSemicolons("SELECT ';' AS s"));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user