diff --git a/src/main/java/io/github/hectorvent/floci/services/athena/AthenaService.java b/src/main/java/io/github/hectorvent/floci/services/athena/AthenaService.java index 1b2a0ee..13251fb 100644 --- a/src/main/java/io/github/hectorvent/floci/services/athena/AthenaService.java +++ b/src/main/java/io/github/hectorvent/floci/services/athena/AthenaService.java @@ -59,6 +59,7 @@ 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 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; private static final String WORKGROUP_RESOURCE = "workgroup/"; @@ -191,7 +192,7 @@ public class AthenaService { if (outputLocation != null) { ensureOutputBucket(outputLocation); } - duckClient.execute(query, setupDdl, outputLocation); + duckClient.execute(withoutTrailingSemicolons(query), setupDdl, outputLocation); return null; }).onSuccess(v -> { markSucceeded(id, execution); @@ -555,6 +556,15 @@ public class AthenaService { return properties; } + /** + * Athena accepts one statement with or without a closing semicolon. The engine wraps the + * statement in {@code COPY (...) TO ...}, where a semicolon is a syntax error, so it is + * dropped before the query is handed over. + */ + static String withoutTrailingSemicolons(String query) { + return query == null ? null : TRAILING_SEMICOLONS.matcher(query).replaceFirst(""); + } + private static boolean producesResultRows(String query) { return RESULT_STATEMENT_PATTERN.matcher(statementText(query)).find(); } diff --git a/src/test/java/io/github/hectorvent/floci/services/athena/AthenaTrailingSemicolonTest.java b/src/test/java/io/github/hectorvent/floci/services/athena/AthenaTrailingSemicolonTest.java new file mode 100644 index 0000000..c19e3b8 --- /dev/null +++ b/src/test/java/io/github/hectorvent/floci/services/athena/AthenaTrailingSemicolonTest.java @@ -0,0 +1,15 @@ +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")); + } +}