diff --git a/hoptimator-cli/src/main/java/sqlline/HoptimatorAppConfig.java b/hoptimator-cli/src/main/java/sqlline/HoptimatorAppConfig.java index e7179978..1b1105d5 100644 --- a/hoptimator-cli/src/main/java/sqlline/HoptimatorAppConfig.java +++ b/hoptimator-cli/src/main/java/sqlline/HoptimatorAppConfig.java @@ -23,7 +23,6 @@ import org.jline.reader.Completer; import java.nio.charset.StandardCharsets; -import java.sql.SQLException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -138,7 +137,7 @@ public void execute(String line, DispatchCallback dispatchCallback) { new HoptimatorDriver.Prepare(conn), plan, create, querySql); } sqlline.output(plan.sql(conn).apply(SqlDialect.ANSI)); - } catch (SQLException e) { + } catch (Exception e) { sqlline.error(e); dispatchCallback.setToFailure(); } @@ -216,7 +215,7 @@ public void execute(String line, DispatchCallback dispatchCallback) { sqlline.output(resolved.sourceConnectorConfigs().toString() + "\n"); sqlline.output("Sink configs:"); sqlline.output(resolved.sinkConnectorConfigs().toString() + "\n"); - } catch (SQLException e) { + } catch (Exception e) { sqlline.error(e); dispatchCallback.setToFailure(); } @@ -284,7 +283,7 @@ public void execute(String line, DispatchCallback dispatchCallback) { try { List specs = HoptimatorDdlUtils.specifyFromSql(sql, conn).specs; specs.forEach(x -> sqlline.output(x + "\n\n---\n\n")); - } catch (SQLException e) { + } catch (Exception e) { sqlline.error(e); dispatchCallback.setToFailure(); } @@ -414,7 +413,7 @@ public void execute(String line, DispatchCallback dispatchCallback) { && depth >= 1 && graph.root() instanceof GraphNode.External && isDegenerate(graph)) { sqlline.output(degenerateGraphWarning()); } - } catch (SQLException e) { + } catch (Exception e) { sqlline.error(e); dispatchCallback.setToFailure(); } diff --git a/hoptimator-k8s/src/main/java/com/linkedin/hoptimator/k8s/DependencyLabels.java b/hoptimator-k8s/src/main/java/com/linkedin/hoptimator/k8s/DependencyLabels.java index 93d3ff70..c20ac44a 100644 --- a/hoptimator-k8s/src/main/java/com/linkedin/hoptimator/k8s/DependencyLabels.java +++ b/hoptimator-k8s/src/main/java/com/linkedin/hoptimator/k8s/DependencyLabels.java @@ -132,8 +132,27 @@ public static Set parseAnnotation(String annotation) { return out; } + /** + * Trims {@code value} to a valid K8s label value: at most {@link #MAX_LABEL_VALUE} characters, + * with leading/trailing non-alphanumeric characters stripped. Truncating at a fixed offset can + * land on a '-', '_', or '.' (e.g. a long table name cut mid-word), which K8s rejects because a + * label value must start and end with an alphanumeric character. + */ private static String truncate(String value) { - return value.length() <= MAX_LABEL_VALUE ? value : value.substring(0, MAX_LABEL_VALUE); + String truncated = value.length() <= MAX_LABEL_VALUE ? value : value.substring(0, MAX_LABEL_VALUE); + int start = 0; + int end = truncated.length(); + while (start < end && !isAlphanumeric(truncated.charAt(start))) { + start++; + } + while (end > start && !isAlphanumeric(truncated.charAt(end - 1))) { + end--; + } + return truncated.substring(start, end); + } + + private static boolean isAlphanumeric(char c) { + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9'); } private static byte[] sha256(byte[] input) { diff --git a/hoptimator-k8s/src/test/java/com/linkedin/hoptimator/k8s/DependencyLabelsTest.java b/hoptimator-k8s/src/test/java/com/linkedin/hoptimator/k8s/DependencyLabelsTest.java index eea31ef3..6b1027c8 100644 --- a/hoptimator-k8s/src/test/java/com/linkedin/hoptimator/k8s/DependencyLabelsTest.java +++ b/hoptimator-k8s/src/test/java/com/linkedin/hoptimator/k8s/DependencyLabelsTest.java @@ -111,6 +111,23 @@ void stampLabelValueTruncatedAtSixtyThreeChars() { assertTrue(value.length() <= 63); } + @Test + void stampLabelValueStrippedWhenTruncationLandsOnSeparator() { + // Regression: a real identifier truncated at exactly 63 chars ended on '_', which K8s + // rejects. This dummy path is sized so truncation lands on a '_' too; it must be stripped. + V1ObjectMeta meta = stamp( + Collections.singletonList( + src("__dummy-database", "SCHEMA", "dummy_table_name_for_internal_testing____")), + null); + String value = meta.getLabels().values().iterator().next(); + + assertTrue(value.length() <= 63); + assertTrue(value.matches("(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])?"), + "value must satisfy K8s label-value regex, got: " + value); + assertEquals("dummy-database_SCHEMA.dummy_table_name_for_internal_testing", value, + "leading/trailing separator must be stripped, got: " + value); + } + @Test void stampLabelValueIsKubernetesLabelValueCompliant() { // K8s label values must match (([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])?