This is an automated email from the ASF dual-hosted git repository.
mbudiu pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/calcite.git
The following commit(s) were added to refs/heads/main by this push:
new 174a79b03e [CALCITE-7259] Drop commons-lang3 dependency
174a79b03e is described below
commit 174a79b03e54be0712cc9cfe1e14465a37b801b2
Author: Hongyu Guo <[email protected]>
AuthorDate: Thu Mar 12 22:52:58 2026 +0800
[CALCITE-7259] Drop commons-lang3 dependency
Replace commons-lang3 usages with local helpers and add lint checks to
prevent re-introducing commons-lang3 imports.
---
arrow/build.gradle.kts | 1 -
bom/build.gradle.kts | 1 -
.../apache/calcite/test/CassandraExtension.java | 4 +-
core/build.gradle.kts | 1 -
.../rel/metadata/RelMdColumnUniqueness.java | 8 +-
.../calcite/runtime/CompressionFunctions.java | 4 +-
.../org/apache/calcite/runtime/SqlFunctions.java | 7 +-
.../org/apache/calcite/runtime/XmlFunctions.java | 7 +-
.../main/java/org/apache/calcite/util/Util.java | 101 +++++++++++++++++++++
.../calcite/util/format/FormatElementEnum.java | 15 ++-
.../calcite/plan/volcano/VolcanoPlannerTest.java | 18 +++-
.../java/org/apache/calcite/test/LintTest.java | 27 ++++++
.../java/org/apache/calcite/util/UtilTest.java | 39 ++++++++
druid/build.gradle.kts | 1 -
.../apache/calcite/adapter/druid/DruidRules.java | 36 +++++---
file/build.gradle.kts | 1 -
.../apache/calcite/adapter/file/CsvEnumerator.java | 62 +++++++++----
geode/build.gradle.kts | 1 -
.../calcite/adapter/geode/util/GeodeUtils.java | 7 +-
gradle.properties | 1 -
innodb/build.gradle.kts | 1 -
.../calcite/adapter/innodb/InnodbSchema.java | 4 +-
.../adapter/innodb/InnodbSchemaFactory.java | 4 +-
.../adapter/innodb/InnodbAdapterDataTypesTest.java | 54 +++++++----
.../apache/calcite/adapter/os/OsAdapterTest.java | 18 ++--
redis/build.gradle.kts | 1 -
.../calcite/adapter/redis/RedisDataProcess.java | 6 +-
.../calcite/adapter/redis/RedisEnumerator.java | 9 +-
.../calcite/adapter/redis/RedisJedisManager.java | 3 +-
.../apache/calcite/adapter/redis/RedisSchema.java | 49 ++++++++--
.../calcite/adapter/redis/RedisCaseBase.java | 7 +-
testkit/build.gradle.kts | 1 -
.../org/apache/calcite/test/CalciteAssert.java | 15 ++-
33 files changed, 383 insertions(+), 131 deletions(-)
diff --git a/arrow/build.gradle.kts b/arrow/build.gradle.kts
index ecbe01e293..598aa8a879 100644
--- a/arrow/build.gradle.kts
+++ b/arrow/build.gradle.kts
@@ -29,7 +29,6 @@
testImplementation("org.apache.arrow:arrow-jdbc")
testImplementation("net.hydromatic:scott-data-hsqldb")
- testImplementation("org.apache.commons:commons-lang3")
testImplementation(project(":core"))
testImplementation(project(":testkit"))
}
diff --git a/bom/build.gradle.kts b/bom/build.gradle.kts
index 51c4177d10..5a1ad75511 100644
--- a/bom/build.gradle.kts
+++ b/bom/build.gradle.kts
@@ -106,7 +106,6 @@ fun DependencyConstraintHandlerScope.runtimev(
apiv("org.apache.calcite.avatica:avatica-server", "calcite.avatica")
apiv("org.apache.cassandra:cassandra-all")
apiv("org.apache.commons:commons-dbcp2")
- apiv("org.apache.commons:commons-lang3")
apiv("org.apache.commons:commons-math3")
apiv("org.apache.commons:commons-pool2")
apiv("org.apache.commons:commons-collections4")
diff --git
a/cassandra/src/test/java/org/apache/calcite/test/CassandraExtension.java
b/cassandra/src/test/java/org/apache/calcite/test/CassandraExtension.java
index fa66f78edc..03097925a5 100644
--- a/cassandra/src/test/java/org/apache/calcite/test/CassandraExtension.java
+++ b/cassandra/src/test/java/org/apache/calcite/test/CassandraExtension.java
@@ -19,12 +19,12 @@
import org.apache.calcite.config.CalciteSystemProperty;
import org.apache.calcite.util.Sources;
import org.apache.calcite.util.TestUtil;
+import org.apache.calcite.util.Util;
import org.apache.cassandra.concurrent.Stage;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.service.CassandraDaemon;
import org.apache.cassandra.service.StorageService;
-import org.apache.commons.lang3.SystemUtils;
import com.datastax.oss.driver.api.core.CqlSession;
import com.google.common.collect.ImmutableMap;
@@ -134,7 +134,7 @@ private static CassandraResource
getOrCreate(ExtensionContext context) {
boolean compatibleGuava = TestUtil.getGuavaMajorVersion() >= 23;
// remove JVM check once Cassandra supports Eclipse OpenJ9 JVM
boolean compatibleJVM = !"Eclipse
OpenJ9".equals(TestUtil.getJavaVirtualMachineVendor());
- boolean compatibleOS = !SystemUtils.IS_OS_WINDOWS;
+ boolean compatibleOS = !Util.isWindows();
if (enabled && compatibleJdk && compatibleGuava && compatibleJVM &&
compatibleOS) {
return ConditionEvaluationResult.enabled("Cassandra tests enabled");
}
diff --git a/core/build.gradle.kts b/core/build.gradle.kts
index 842a34fba6..2cb75e9af0 100644
--- a/core/build.gradle.kts
+++ b/core/build.gradle.kts
@@ -69,7 +69,6 @@
implementation("commons-codec:commons-codec")
implementation("net.hydromatic:aggdesigner-algorithm")
implementation("org.apache.commons:commons-dbcp2")
- implementation("org.apache.commons:commons-lang3")
implementation("org.apache.commons:commons-math3")
implementation("org.apache.commons:commons-text")
implementation("org.jooq:joou-java-6")
diff --git
a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdColumnUniqueness.java
b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdColumnUniqueness.java
index a4ac2e9c5a..e69da27745 100644
---
a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdColumnUniqueness.java
+++
b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdColumnUniqueness.java
@@ -55,8 +55,6 @@
import org.apache.calcite.util.Pair;
import org.apache.calcite.util.Util;
-import org.apache.commons.lang3.mutable.MutableBoolean;
-
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
@@ -571,17 +569,17 @@ private static void
addInputRefIfOtherConstant(ImmutableBitSet.Builder builder,
*/
private static boolean isConstantScalarQuery(RexNode rexNode) {
if (rexNode.getKind() == SqlKind.SCALAR_QUERY) {
- MutableBoolean hasCorrelatingVars = new MutableBoolean(false);
+ final boolean[] hasCorrelatingVars = {false};
((RexSubQuery) rexNode).rel.accept(new RelShuttleImpl() {
@Override public RelNode visit(final LogicalFilter filter) {
if (RexUtil.containsCorrelation(filter.getCondition())) {
- hasCorrelatingVars.setTrue();
+ hasCorrelatingVars[0] = true;
return filter;
}
return super.visit(filter);
}
});
- return hasCorrelatingVars.isFalse();
+ return !hasCorrelatingVars[0];
}
return false;
}
diff --git
a/core/src/main/java/org/apache/calcite/runtime/CompressionFunctions.java
b/core/src/main/java/org/apache/calcite/runtime/CompressionFunctions.java
index 1df63573ee..59bac5d5fd 100644
--- a/core/src/main/java/org/apache/calcite/runtime/CompressionFunctions.java
+++ b/core/src/main/java/org/apache/calcite/runtime/CompressionFunctions.java
@@ -18,8 +18,6 @@
import org.apache.calcite.avatica.util.ByteString;
-import org.apache.commons.lang3.StringUtils;
-
import org.checkerframework.checker.nullness.qual.Nullable;
import java.io.ByteArrayOutputStream;
@@ -47,7 +45,7 @@ private CompressionFunctions() {
if (data == null) {
return null;
}
- if (StringUtils.isEmpty(data)) {
+ if (data.isEmpty()) {
return new ByteString(new byte[0]);
}
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
diff --git a/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
b/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
index a729d34916..e2adce8ea1 100644
--- a/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
+++ b/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
@@ -60,7 +60,6 @@
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.codec.language.Soundex;
-import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.math3.util.CombinatoricsUtils;
import org.apache.commons.text.StringEscapeUtils;
import org.apache.commons.text.similarity.LevenshteinDistance;
@@ -6521,7 +6520,7 @@ public static long customTimestampCeil(DataContext root,
/** SQL {@code TRANSLATE(string, search_chars, replacement_chars)}
* function. */
public static String translate3(String s, String search, String replacement)
{
- return org.apache.commons.lang3.StringUtils.replaceChars(s, search,
replacement);
+ return Util.replaceChars(s, search, replacement);
}
/** SQL {@code REPLACE(string, search, replacement)} function. */
@@ -6534,7 +6533,7 @@ public static String replace(String s, String search,
String replacement,
return s.replace(search, replacement);
}
// for MSSQL's REPLACE function, search pattern is case-insensitive during
matching
- return org.apache.commons.lang3.Strings.CI.replace(s, search, replacement);
+ return Util.replaceIgnoreCase(s, search, replacement);
}
/** Helper for "array element reference". Caller has already ensured that
@@ -7699,7 +7698,7 @@ private static String age(long timestamp1, long
timestamp2) {
sb.append(
String.format(Locale.ROOT, "%02d:%02d:%02d.%s", hours, minutes,
seconds,
millisString));
- } else if (ObjectUtils.isNotEmpty(sb)
+ } else if (sb.length() != 0
&& hours == 0 && minutes == 0 && seconds == 0 && millis == 0) {
return sb.toString().trim();
} else {
diff --git a/core/src/main/java/org/apache/calcite/runtime/XmlFunctions.java
b/core/src/main/java/org/apache/calcite/runtime/XmlFunctions.java
index 2a8af55e23..00660a1b46 100644
--- a/core/src/main/java/org/apache/calcite/runtime/XmlFunctions.java
+++ b/core/src/main/java/org/apache/calcite/runtime/XmlFunctions.java
@@ -18,8 +18,7 @@
import org.apache.calcite.util.SimpleNamespaceContext;
import org.apache.calcite.util.TryThreadLocal;
-
-import org.apache.commons.lang3.StringUtils;
+import org.apache.calcite.util.Util;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.w3c.dom.Node;
@@ -133,7 +132,7 @@ private XmlFunctions() {
() -> "firstChild of node " + item);
result.add(firstChild.getTextContent());
}
- return StringUtils.join(result, " ");
+ return Util.joinNullable(result, " ");
} catch (XPathExpressionException e) {
return xpathExpression.evaluate(documentNode);
}
@@ -189,7 +188,7 @@ private XmlFunctions() {
for (int i = 0; i < nodes.getLength(); i++) {
result.add(convertNodeToString(castNonNull(nodes.item(i))));
}
- return StringUtils.join(result, "");
+ return Util.joinNullable(result, "");
} catch (XPathExpressionException e) {
Node node = (Node) xpathExpression
.evaluate(documentNode, XPathConstants.NODE);
diff --git a/core/src/main/java/org/apache/calcite/util/Util.java
b/core/src/main/java/org/apache/calcite/util/Util.java
index d911103f26..05b9927192 100644
--- a/core/src/main/java/org/apache/calcite/util/Util.java
+++ b/core/src/main/java/org/apache/calcite/util/Util.java
@@ -31,6 +31,7 @@
import org.apache.calcite.sql.util.SqlBasicVisitor;
import com.google.common.base.Preconditions;
+import com.google.common.base.Strings;
import com.google.common.base.Throwables;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
@@ -642,6 +643,106 @@ public static String replace(
return sb.toString();
}
+ /** Right-pads a string with {@code padChar} until it reaches {@code size}.
+ *
+ * <p>Equivalent to {@code
org.apache.commons.lang3.StringUtils#rightPad(String, int, char)}
+ * for non-null inputs.
+ */
+ public static String rightPad(String s, int size, char padChar) {
+ return Strings.padEnd(s, size, padChar);
+ }
+
+ /** Joins {@code parts} using {@code sep}.
+ *
+ * <p>Null elements are treated as empty strings.
+ */
+ public static String joinNullable(Iterable<? extends @Nullable Object>
parts, String sep) {
+ final List<String> strings = new ArrayList<>();
+ for (Object o : parts) {
+ strings.add(o == null ? "" : o.toString());
+ }
+ return String.join(sep, strings);
+ }
+
+ /** Returns whether the current runtime is Windows.
+ *
+ * <p>Derived from system property {@code os.name}, using case-insensitive
+ * prefix match against {@code "Windows"}.
+ *
+ * <p>This method is intended to replace commons-lang3's
+ * {@code SystemUtils#IS_OS_WINDOWS}.
+ */
+ public static boolean isWindows() {
+ final String osName = System.getProperty("os.name");
+ return osName != null
+ && osName.regionMatches(true, 0, "Windows", 0, "Windows".length());
+ }
+
+ /** Replaces characters in {@code s} according to {@code search}/{@code
replacement} mapping.
+ *
+ * <p>Semantics are aligned with {@code
org.apache.commons.lang3.StringUtils#replaceChars}:
+ * characters found in {@code search} are replaced by the character in the
same position in
+ * {@code replacement}; if {@code replacement} is shorter, remaining matches
are removed.
+ */
+ public static @PolyNull String replaceChars(@PolyNull String s, @Nullable
String search,
+ @Nullable String replacement) {
+ if (s == null || s.isEmpty() || search == null || search.isEmpty()) {
+ return s;
+ }
+ final String repl = replacement == null ? "" : replacement;
+ boolean modified = false;
+ final StringBuilder b = new StringBuilder(s.length());
+ for (int i = 0; i < s.length(); i++) {
+ final char ch = s.charAt(i);
+ final int j = search.indexOf(ch);
+ if (j >= 0) {
+ modified = true;
+ if (j < repl.length()) {
+ b.append(repl.charAt(j));
+ }
+ // else: delete character
+ } else {
+ b.append(ch);
+ }
+ }
+ return modified ? b.toString() : s;
+ }
+
+ /** Case-insensitive replace of all occurrences of {@code search} in {@code
s}.
+ *
+ * <p>Equivalent to commons-lang's {@code StringUtils.replaceIgnoreCase},
but only supports
+ * non-null inputs.
+ */
+ public static String replaceIgnoreCase(String s, String search, String
replacement) {
+ if (search.isEmpty()) {
+ return s;
+ }
+ final int replLength = search.length();
+ int start = 0;
+ int end = indexOfIgnoreCase(s, search, start);
+ if (end < 0) {
+ return s;
+ }
+ final StringBuilder out = new StringBuilder(s.length());
+ while (end >= 0) {
+ out.append(s, start, end).append(replacement);
+ start = end + replLength;
+ end = indexOfIgnoreCase(s, search, start);
+ }
+ out.append(s, start, s.length());
+ return out.toString();
+ }
+
+ private static int indexOfIgnoreCase(String str, String search, int
fromIndex) {
+ final int max = str.length() - search.length();
+ for (int i = Math.max(0, fromIndex); i <= max; i++) {
+ if (str.regionMatches(true, i, search, 0, search.length())) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
/**
* Creates a file-protocol URL for the given file.
*/
diff --git
a/core/src/main/java/org/apache/calcite/util/format/FormatElementEnum.java
b/core/src/main/java/org/apache/calcite/util/format/FormatElementEnum.java
index 0e289cf678..66061d3596 100644
--- a/core/src/main/java/org/apache/calcite/util/format/FormatElementEnum.java
+++ b/core/src/main/java/org/apache/calcite/util/format/FormatElementEnum.java
@@ -18,8 +18,7 @@
import org.apache.calcite.avatica.util.DateTimeUtils;
import org.apache.calcite.util.TryThreadLocal;
-
-import org.apache.commons.lang3.StringUtils;
+import org.apache.calcite.util.Util;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
@@ -147,7 +146,7 @@ public enum FormatElementEnum implements FormatElement {
// Padding zeroes to right as SimpleDateFormat supports precision only
up to 3 places.
// Refer to <a
href="https://issues.apache.org/jira/projects/CALCITE/issues/CALCITE-6269">
// [CALCITE-6269] Fix missing/broken BigQuery date-time format
elements</a>.
- sb.append(StringUtils.rightPad(work.sssFormat.format(date), 4, "0"));
+ sb.append(Util.rightPad(work.sssFormat.format(date), 4, '0'));
}
},
FF5("S", "Fractional seconds to 5 digits") {
@@ -156,7 +155,7 @@ public enum FormatElementEnum implements FormatElement {
// Padding zeroes to right as SimpleDateFormat supports precision only
up to 3 places.
// Refer to <a
href="https://issues.apache.org/jira/projects/CALCITE/issues/CALCITE-6269">
// [CALCITE-6269] Fix missing/broken BigQuery date-time format
elements</a>.
- sb.append(StringUtils.rightPad(work.sssFormat.format(date), 5, "0"));
+ sb.append(Util.rightPad(work.sssFormat.format(date), 5, '0'));
}
},
FF6("S", "Fractional seconds to 6 digits") {
@@ -165,7 +164,7 @@ public enum FormatElementEnum implements FormatElement {
// Padding zeroes to right as SimpleDateFormat supports precision only
up to 3 places.
// Refer to <a
href="https://issues.apache.org/jira/projects/CALCITE/issues/CALCITE-6269">
// [CALCITE-6269] Fix missing/broken BigQuery date-time format
elements</a>.
- sb.append(StringUtils.rightPad(work.sssFormat.format(date), 6, "0"));
+ sb.append(Util.rightPad(work.sssFormat.format(date), 6, '0'));
}
},
FF7("S", "Fractional seconds to 6 digits") {
@@ -174,7 +173,7 @@ public enum FormatElementEnum implements FormatElement {
// Padding zeroes to right as SimpleDateFormat supports precision only
up to 3 places.
// Refer to <a
href="https://issues.apache.org/jira/projects/CALCITE/issues/CALCITE-6269">
// [CALCITE-6269] Fix missing/broken BigQuery date-time format
elements</a>.
- sb.append(StringUtils.rightPad(work.sssFormat.format(date), 7, "0"));
+ sb.append(Util.rightPad(work.sssFormat.format(date), 7, '0'));
}
},
FF8("S", "Fractional seconds to 6 digits") {
@@ -183,7 +182,7 @@ public enum FormatElementEnum implements FormatElement {
// Padding zeroes to right as SimpleDateFormat supports precision only
up to 3 places.
// Refer to <a
href="https://issues.apache.org/jira/projects/CALCITE/issues/CALCITE-6269">
// [CALCITE-6269] Fix missing/broken BigQuery date-time format
elements</a>.
- sb.append(StringUtils.rightPad(work.sssFormat.format(date), 8, "0"));
+ sb.append(Util.rightPad(work.sssFormat.format(date), 8, '0'));
}
},
FF9("S", "Fractional seconds to 6 digits") {
@@ -192,7 +191,7 @@ public enum FormatElementEnum implements FormatElement {
// Padding zeroes to right as SimpleDateFormat supports precision only
up to 3 places.
// Refer to <a
href="https://issues.apache.org/jira/projects/CALCITE/issues/CALCITE-6269">
// [CALCITE-6269] Fix missing/broken BigQuery date-time format
elements</a>.
- sb.append(StringUtils.rightPad(work.sssFormat.format(date), 9, "0"));
+ sb.append(Util.rightPad(work.sssFormat.format(date), 9, '0'));
}
},
HH12("h", "The hour (12-hour clock) as a decimal number (01-12)") {
diff --git
a/core/src/test/java/org/apache/calcite/plan/volcano/VolcanoPlannerTest.java
b/core/src/test/java/org/apache/calcite/plan/volcano/VolcanoPlannerTest.java
index 909312dd4e..12c5fbeba3 100644
--- a/core/src/test/java/org/apache/calcite/plan/volcano/VolcanoPlannerTest.java
+++ b/core/src/test/java/org/apache/calcite/plan/volcano/VolcanoPlannerTest.java
@@ -42,8 +42,6 @@
import org.apache.calcite.tools.RelBuilder;
import org.apache.calcite.util.Pair;
-import org.apache.commons.lang3.exception.ExceptionUtils;
-
import org.immutables.value.Value;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
@@ -362,7 +360,7 @@ public interface Config extends RelRule.Config {
"Should throw exception fail since the type mismatches after "
+ "applying rule.");
- Throwable exception = ExceptionUtils.getRootCause(ex);
+ Throwable exception = getRootCause(ex);
assertThat(exception, instanceOf(IllegalArgumentException.class));
assertThat(
exception.getMessage(), isLinux("Type mismatch:\n"
@@ -372,6 +370,20 @@ public interface Config extends RelRule.Config {
+ "this: JavaType(class java.lang.Integer) -> JavaType(void) NOT
NULL\n"));
}
+ public static Throwable getRootCause(final Throwable throwable) {
+ final List<Throwable> list = getThrowableList(throwable);
+ return list.isEmpty() ? null : list.get(list.size() - 1);
+ }
+
+ public static List<Throwable> getThrowableList(Throwable throwable) {
+ final List<Throwable> list = new ArrayList<>();
+ while (throwable != null && !list.contains(throwable)) {
+ list.add(throwable);
+ throwable = throwable.getCause();
+ }
+ return list;
+ }
+
private static <E extends Comparable> List<E> sort(List<E> list) {
final List<E> list2 = new ArrayList<>(list);
Collections.sort(list2);
diff --git a/core/src/test/java/org/apache/calcite/test/LintTest.java
b/core/src/test/java/org/apache/calcite/test/LintTest.java
index ff012bfa16..089d76edba 100644
--- a/core/src/test/java/org/apache/calcite/test/LintTest.java
+++ b/core/src/test/java/org/apache/calcite/test/LintTest.java
@@ -49,6 +49,7 @@
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.hasSize;
+import static org.hamcrest.Matchers.hasToString;
import static org.hamcrest.Matchers.startsWith;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
@@ -64,6 +65,10 @@ class LintTest {
compile("^(\\[CALCITE-[0-9]{1,4}][ ]).*");
private static final Pattern PATTERN = compile("^ *(// )?");
+ private static final Pattern COMMONS_LANG3_IMPORT_PATTERN =
+ compile("^\\s*import\\s+(static\\s+)?"
+ + "org\\.apache\\.commons\\.lang3\\..*;\\s*$");
+
private static final String TERMINOLOGY_ERROR_MSG =
"Message contains '%s' word; use one of the following instead: %s";
private static final List<TermRule> TERM_RULES = initTerminologyRules();
@@ -94,6 +99,13 @@ private Puffin.Program<GlobalState> makeProgram() {
&& !skipping(line),
line -> line.state().message("Tab", line))
+ // Forbid importing commons-lang3 (Calcite should not use it).
+ .add(line -> isJava(line.filename())
+ && line.matches(COMMONS_LANG3_IMPORT_PATTERN.pattern()),
+ line -> line.state().message(
+ "Forbidden import from 'org.apache.commons.lang3'
(commons-lang3 is not allowed)",
+ line))
+
// Comment without space
.add(line -> line.matches(".* //[^ ].*")
&& !line.source().fileOpt()
@@ -236,6 +248,21 @@ && isJava(line.filename()),
.build();
}
+ @Test void testNoCommonsLang3Import() {
+ final Puffin.Program<GlobalState> program = makeProgram();
+ final String code = "import org.apache.commons.lang3.StringUtils;\n"
+ + "class X {}\n";
+ final StringWriter sw = new StringWriter();
+ final GlobalState g;
+ try (PrintWriter pw = new PrintWriter(sw)) {
+ g = program.execute(Stream.of(Sources.of(code)), pw);
+ }
+ final String expected = "[GuavaCharSource{memory}:1:"
+ + "Forbidden import from 'org.apache.commons.lang3' "
+ + "(commons-lang3 is not allowed)]";
+ assertThat(g.messages, hasToString(expected));
+ }
+
/** Strips the last character from a string. */
private static String skipLast(String s) {
return s.substring(0, s.length() - 1);
diff --git a/core/src/test/java/org/apache/calcite/util/UtilTest.java
b/core/src/test/java/org/apache/calcite/util/UtilTest.java
index 7d8797f19c..2d7ab0f606 100644
--- a/core/src/test/java/org/apache/calcite/util/UtilTest.java
+++ b/core/src/test/java/org/apache/calcite/util/UtilTest.java
@@ -241,6 +241,45 @@ class UtilTest {
assertThatScientific("-0.0", is("-0.0E0"));
}
+ @Test void testRightPad() {
+ assertThat(Util.rightPad("a", 1, 'x'), is("a"));
+ assertThat(Util.rightPad("a", 2, 'x'), is("ax"));
+ assertThat(Util.rightPad("a", 4, 'x'), is("axxx"));
+ assertThat(Util.rightPad("", 3, 'x'), is("xxx"));
+ }
+
+ @Test void testJoinNullable() {
+ final List<@Nullable Object> parts = Arrays.asList("a", null, "b");
+ assertThat(Util.joinNullable(parts, ":"), is("a::b"));
+ assertThat(Util.joinNullable(Collections.emptyList(), ","), is(""));
+ assertThat(Util.joinNullable(Arrays.asList(null, null), ":"), is(":"));
+ }
+
+ @Test void testReplaceChars() {
+ assertNull(Util.replaceChars((String) null, "ab", "x"));
+ assertThat(Util.replaceChars("", "ab", "x"), is(""));
+
+ final String s = "abc";
+ assertThat(Util.replaceChars(s, null, "x"), sameInstance(s));
+ assertThat(Util.replaceChars(s, "", "x"), sameInstance(s));
+ assertThat(Util.replaceChars(s, "x", "y"), sameInstance(s));
+
+ assertThat(Util.replaceChars("abc", "ab", null), is("c"));
+ assertThat(Util.replaceChars("abc", "abc", "12"), is("12"));
+ assertThat(Util.replaceChars("abc", "a", "a"), is("abc"));
+ assertNotSame("abc", Util.replaceChars("abc", "a", "a"));
+ }
+
+ @Test void testReplaceIgnoreCase() {
+ final String s = "abc";
+ assertThat(Util.replaceIgnoreCase(s, "", "x"), sameInstance(s));
+ assertThat(Util.replaceIgnoreCase(s, "ZZ", "x"), sameInstance(s));
+
+ assertThat(Util.replaceIgnoreCase("aBAba", "ab", "x"), is("xxa"));
+ assertThat(Util.replaceIgnoreCase("xxxx", "X", "yz"), is("yzyzyzyz"));
+ assertThat(Util.replaceIgnoreCase("aaaa", "aa", "b"), is("bb"));
+ }
+
@Test void testToJavaId() throws UnsupportedEncodingException {
assertThat(Util.toJavaId("foo", 0), is("ID$0$foo"));
assertThat(Util.toJavaId("foo bar", 0), is("ID$0$foo_20_bar"));
diff --git a/druid/build.gradle.kts b/druid/build.gradle.kts
index 1b1fcf712e..34cae5bd05 100644
--- a/druid/build.gradle.kts
+++ b/druid/build.gradle.kts
@@ -32,7 +32,6 @@
implementation("com.fasterxml.jackson.core:jackson-databind")
implementation("com.google.guava:guava")
- implementation("org.apache.commons:commons-lang3")
testImplementation(project(":testkit"))
testImplementation("org.mockito:mockito-core")
diff --git
a/druid/src/main/java/org/apache/calcite/adapter/druid/DruidRules.java
b/druid/src/main/java/org/apache/calcite/adapter/druid/DruidRules.java
index d313e30e93..ff64d44ed2 100644
--- a/druid/src/main/java/org/apache/calcite/adapter/druid/DruidRules.java
+++ b/druid/src/main/java/org/apache/calcite/adapter/druid/DruidRules.java
@@ -54,9 +54,6 @@
import org.apache.calcite.util.Util;
import org.apache.calcite.util.trace.CalciteTrace;
-import org.apache.commons.lang3.tuple.ImmutableTriple;
-import org.apache.commons.lang3.tuple.Triple;
-
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
@@ -220,15 +217,15 @@ protected DruidFilterRule(DruidFilterRuleConfig config) {
query.getRowType().getFieldNames()
.indexOf(query.druidTable.timestampFieldName);
RelNode newDruidQuery = query;
- final Triple<List<RexNode>, List<RexNode>, List<RexNode>> triple =
+ final SplitFiltersResult split =
splitFilters(validPreds, nonValidPreds, timestampFieldIdx);
- if (triple.getLeft().isEmpty() && triple.getMiddle().isEmpty()) {
+ if (split.timeRangeNodes.isEmpty() && split.pushableNodes.isEmpty()) {
// it sucks, nothing to push
return;
}
- final List<RexNode> residualPreds = new ArrayList<>(triple.getRight());
+ final List<RexNode> residualPreds = new
ArrayList<>(split.nonPushableNodes);
List<Interval> intervals = null;
- if (!triple.getLeft().isEmpty()) {
+ if (!split.timeRangeNodes.isEmpty()) {
final CalciteConnectionConfig connectionConfig =
requireNonNull(
cluster.getPlanner().getContext()
@@ -236,17 +233,17 @@ protected DruidFilterRule(DruidFilterRuleConfig config) {
requireNonNull(connectionConfig.timeZone());
intervals =
DruidDateTimeUtils.createInterval(
- RexUtil.composeConjunction(rexBuilder, triple.getLeft()));
+ RexUtil.composeConjunction(rexBuilder, split.timeRangeNodes));
if (intervals == null || intervals.isEmpty()) {
// Case we have a filter with extract that can not be written as
interval push down
- triple.getMiddle().addAll(triple.getLeft());
+ split.pushableNodes.addAll(split.timeRangeNodes);
}
}
- if (!triple.getMiddle().isEmpty()) {
+ if (!split.pushableNodes.isEmpty()) {
final RelNode newFilter =
filter.copy(filter.getTraitSet(), Util.last(query.rels),
- RexUtil.composeConjunction(rexBuilder, triple.getMiddle()));
+ RexUtil.composeConjunction(rexBuilder, split.pushableNodes));
newDruidQuery = DruidQuery.extendQuery(query, newFilter);
}
if (intervals != null && !intervals.isEmpty()) {
@@ -269,7 +266,20 @@ protected DruidFilterRule(DruidFilterRuleConfig config) {
* 2-m) condition filters that can be pushed to Druid,
* 3-r) condition filters that cannot be pushed to Druid.
*/
- private static Triple<List<RexNode>, List<RexNode>, List<RexNode>>
splitFilters(
+ private static final class SplitFiltersResult {
+ final List<RexNode> timeRangeNodes;
+ final List<RexNode> pushableNodes;
+ final List<RexNode> nonPushableNodes;
+
+ private SplitFiltersResult(List<RexNode> timeRangeNodes, List<RexNode>
pushableNodes,
+ List<RexNode> nonPushableNodes) {
+ this.timeRangeNodes = timeRangeNodes;
+ this.pushableNodes = pushableNodes;
+ this.nonPushableNodes = nonPushableNodes;
+ }
+ }
+
+ private static SplitFiltersResult splitFilters(
final List<RexNode> validPreds,
final List<RexNode> nonValidPreds, final int timestampFieldIdx) {
final List<RexNode> timeRangeNodes = new ArrayList<>();
@@ -286,7 +296,7 @@ private static Triple<List<RexNode>, List<RexNode>,
List<RexNode>> splitFilters(
pushableNodes.add(conj);
}
}
- return ImmutableTriple.of(timeRangeNodes, pushableNodes,
nonPushableNodes);
+ return new SplitFiltersResult(timeRangeNodes, pushableNodes,
nonPushableNodes);
}
/** Rule configuration. */
diff --git a/file/build.gradle.kts b/file/build.gradle.kts
index 37373ccf92..04036a888e 100644
--- a/file/build.gradle.kts
+++ b/file/build.gradle.kts
@@ -31,7 +31,6 @@
implementation("net.sf.opencsv:opencsv")
implementation("org.apache.calcite.avatica:avatica-core")
implementation("commons-io:commons-io")
- implementation("org.apache.commons:commons-lang3")
implementation("org.jsoup:jsoup")
implementation("com.fasterxml.jackson.core:jackson-core")
implementation("com.fasterxml.jackson.core:jackson-databind")
diff --git
a/file/src/main/java/org/apache/calcite/adapter/file/CsvEnumerator.java
b/file/src/main/java/org/apache/calcite/adapter/file/CsvEnumerator.java
index 4762c20424..012be61450 100644
--- a/file/src/main/java/org/apache/calcite/adapter/file/CsvEnumerator.java
+++ b/file/src/main/java/org/apache/calcite/adapter/file/CsvEnumerator.java
@@ -27,8 +27,6 @@
import org.apache.calcite.util.Source;
import org.apache.calcite.util.trace.CalciteLogger;
-import org.apache.commons.lang3.time.FastDateFormat;
-
import au.com.bytecode.opencsv.CSVReader;
import com.google.common.annotations.VisibleForTesting;
@@ -40,6 +38,7 @@
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.ParseException;
+import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
@@ -76,20 +75,43 @@ public class CsvEnumerator<E> implements Enumerator<E> {
private final RowConverter<E> rowConverter;
private @Nullable E current;
- private static final FastDateFormat TIME_FORMAT_DATE;
- private static final FastDateFormat TIME_FORMAT_TIME;
- private static final FastDateFormat TIME_FORMAT_TIMESTAMP;
+ private static final TimeZone GMT = TimeZone.getTimeZone("GMT");
+
+ // FastDateFormat is thread-safe and lenient; mimic with
ThreadLocal(SimpleDateFormat).
+ private static final ThreadLocal<SimpleDateFormat> TIME_FORMAT_DATE =
+ ThreadLocal.withInitial(() -> {
+ SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd", Locale.ROOT);
+ f.setTimeZone(GMT);
+ f.setLenient(true);
+ return f;
+ });
+
+ private static final ThreadLocal<SimpleDateFormat> TIME_FORMAT_TIME =
+ ThreadLocal.withInitial(() -> {
+ SimpleDateFormat f = new SimpleDateFormat("HH:mm:ss", Locale.ROOT);
+ f.setTimeZone(GMT);
+ f.setLenient(true);
+ return f;
+ });
+
+ private static final ThreadLocal<SimpleDateFormat> TIME_FORMAT_TIMESTAMP =
+ ThreadLocal.withInitial(() -> {
+ SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss",
Locale.ROOT);
+ f.setTimeZone(GMT);
+ f.setLenient(true);
+ return f;
+ });
+
+ /** Clears per-thread cached date/time formatters to avoid ThreadLocal leaks
in long-lived
+ * thread pools or container environments. Must be called on the same thread
that used them. */
+ private static void clearTimeFormats() {
+ TIME_FORMAT_DATE.remove();
+ TIME_FORMAT_TIME.remove();
+ TIME_FORMAT_TIMESTAMP.remove();
+ }
private static final Pattern DECIMAL_TYPE_PATTERN = Pattern
.compile("\"decimal\\(([0-9]+),([0-9]+)\\)");
- static {
- final TimeZone gmt = TimeZone.getTimeZone("GMT");
- TIME_FORMAT_DATE = FastDateFormat.getInstance("yyyy-MM-dd", gmt);
- TIME_FORMAT_TIME = FastDateFormat.getInstance("HH:mm:ss", gmt);
- TIME_FORMAT_TIMESTAMP =
- FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss", gmt);
- }
-
public CsvEnumerator(Source source, AtomicBoolean cancelFlag,
List<RelDataType> fieldTypes, List<Integer> fields) {
//noinspection unchecked
@@ -252,7 +274,11 @@ static CSVReader openCsv(Source source) throws IOException
{
continue;
}
current = null;
- reader.close();
+ try {
+ reader.close();
+ } finally {
+ clearTimeFormats();
+ }
return false;
}
if (filterValues != null) {
@@ -282,6 +308,8 @@ static CSVReader openCsv(Source source) throws IOException {
reader.close();
} catch (IOException e) {
throw new RuntimeException("Error closing CSV reader", e);
+ } finally {
+ clearTimeFormats();
}
}
@@ -357,7 +385,7 @@ abstract static class RowConverter<E> {
return null;
}
try {
- Date date = TIME_FORMAT_DATE.parse(string);
+ Date date = TIME_FORMAT_DATE.get().parse(string);
return (int) (date.getTime() / DateTimeUtils.MILLIS_PER_DAY);
} catch (ParseException e) {
return null;
@@ -367,7 +395,7 @@ abstract static class RowConverter<E> {
return null;
}
try {
- Date date = TIME_FORMAT_TIME.parse(string);
+ Date date = TIME_FORMAT_TIME.get().parse(string);
return (int) date.getTime();
} catch (ParseException e) {
return null;
@@ -377,7 +405,7 @@ abstract static class RowConverter<E> {
return null;
}
try {
- Date date = TIME_FORMAT_TIMESTAMP.parse(string);
+ Date date = TIME_FORMAT_TIMESTAMP.get().parse(string);
return date.getTime();
} catch (ParseException e) {
return null;
diff --git a/geode/build.gradle.kts b/geode/build.gradle.kts
index bf51ecdb58..0ef1774167 100644
--- a/geode/build.gradle.kts
+++ b/geode/build.gradle.kts
@@ -30,7 +30,6 @@
implementation("com.google.guava:guava")
implementation("org.apache.calcite.avatica:avatica-core")
- implementation("org.apache.commons:commons-lang3")
testImplementation(project(":testkit"))
testImplementation("com.fasterxml.jackson.core:jackson-databind")
diff --git
a/geode/src/main/java/org/apache/calcite/adapter/geode/util/GeodeUtils.java
b/geode/src/main/java/org/apache/calcite/adapter/geode/util/GeodeUtils.java
index 06b74f20b1..2aa212dc5a 100644
--- a/geode/src/main/java/org/apache/calcite/adapter/geode/util/GeodeUtils.java
+++ b/geode/src/main/java/org/apache/calcite/adapter/geode/util/GeodeUtils.java
@@ -22,7 +22,6 @@
import org.apache.calcite.rel.type.RelDataTypeField;
import org.apache.calcite.util.Util;
-import org.apache.commons.lang3.Strings;
import org.apache.geode.cache.CacheClosedException;
import org.apache.geode.cache.GemFireCache;
import org.apache.geode.cache.Region;
@@ -84,7 +83,7 @@ public static synchronized ClientCache
createClientCache(String locatorHost,
int locatorPort, String autoSerializerPackagePath,
boolean readSerialized) {
if (locatorPort != currentLocatorPort
- || !Strings.CI.equals(currentLocatorHost, locatorHost)) {
+ || !equalsIgnoreCase(currentLocatorHost, locatorHost)) {
LOGGER.info("Close existing ClientCache ["
+ currentLocatorHost + ":" + currentLocatorPort + "] for new Locator
connection at: ["
+ locatorHost + ":" + locatorPort + "]");
@@ -117,6 +116,10 @@ public static synchronized void closeClientCache() {
REGION_MAP.clear();
}
+ private static boolean equalsIgnoreCase(@Nullable String a, @Nullable String
b) {
+ return a == null ? b == null : b != null && a.equalsIgnoreCase(b);
+ }
+
/**
* Obtains a proxy pointing to an existing Region on the server.
*
diff --git a/gradle.properties b/gradle.properties
index 974408d02b..3005b96424 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -92,7 +92,6 @@ chinook-data-hsqldb.version=0.2
commons-codec.version=1.16.0
commons-dbcp2.version=2.11.0
commons-io.version=2.15.0
-commons-lang3.version=3.18.0
commons-math3.version=3.6.1
commons-pool2.version=2.12.0
commons-collections4.version=4.4
diff --git a/innodb/build.gradle.kts b/innodb/build.gradle.kts
index 88b00aa809..81143af455 100644
--- a/innodb/build.gradle.kts
+++ b/innodb/build.gradle.kts
@@ -31,7 +31,6 @@
api("com.google.guava:guava")
implementation("org.apache.calcite.avatica:avatica-core")
- implementation("org.apache.commons:commons-lang3")
implementation("org.slf4j:slf4j-api")
testImplementation(project(":testkit"))
diff --git
a/innodb/src/main/java/org/apache/calcite/adapter/innodb/InnodbSchema.java
b/innodb/src/main/java/org/apache/calcite/adapter/innodb/InnodbSchema.java
index c6dbf0139a..759f5cd607 100644
--- a/innodb/src/main/java/org/apache/calcite/adapter/innodb/InnodbSchema.java
+++ b/innodb/src/main/java/org/apache/calcite/adapter/innodb/InnodbSchema.java
@@ -25,8 +25,6 @@
import org.apache.calcite.sql.type.SqlTypeFactoryImpl;
import org.apache.calcite.sql.type.SqlTypeName;
-import org.apache.commons.lang3.StringUtils;
-
import com.alibaba.innodb.java.reader.TableReaderFactory;
import com.alibaba.innodb.java.reader.column.ColumnType;
import com.alibaba.innodb.java.reader.schema.Column;
@@ -57,7 +55,7 @@ public InnodbSchema(List<String> sqlFilePathList,
String ibdDataFileBasePath) {
checkArgument(sqlFilePathList != null && !sqlFilePathList.isEmpty(),
"SQL file path list cannot be empty");
- checkArgument(StringUtils.isNotEmpty(ibdDataFileBasePath),
+ checkArgument(ibdDataFileBasePath != null &&
!ibdDataFileBasePath.isEmpty(),
"InnoDB data file with ibd suffix cannot be empty");
this.sqlFilePathList = sqlFilePathList;
this.ibdDataFileBasePath = ibdDataFileBasePath;
diff --git
a/innodb/src/main/java/org/apache/calcite/adapter/innodb/InnodbSchemaFactory.java
b/innodb/src/main/java/org/apache/calcite/adapter/innodb/InnodbSchemaFactory.java
index 7d9599b049..00b0b287ba 100644
---
a/innodb/src/main/java/org/apache/calcite/adapter/innodb/InnodbSchemaFactory.java
+++
b/innodb/src/main/java/org/apache/calcite/adapter/innodb/InnodbSchemaFactory.java
@@ -20,8 +20,6 @@
import org.apache.calcite.schema.SchemaFactory;
import org.apache.calcite.schema.SchemaPlus;
-import org.apache.commons.lang3.StringUtils;
-
import java.util.List;
import java.util.Map;
@@ -37,7 +35,7 @@ public InnodbSchemaFactory() {
final List<String> sqlFilePathList = (List<String>)
operand.get("sqlFilePath");
final String ibdDataFileBasePath = (String)
operand.get("ibdDataFileBasePath");
final String timeZone = (String) operand.get("timeZone");
- if (StringUtils.isNotEmpty(timeZone)) {
+ if (timeZone != null && !timeZone.isEmpty()) {
System.setProperty("innodb.java.reader.server.timezone", timeZone);
}
diff --git
a/innodb/src/test/java/org/apache/calcite/adapter/innodb/InnodbAdapterDataTypesTest.java
b/innodb/src/test/java/org/apache/calcite/adapter/innodb/InnodbAdapterDataTypesTest.java
index 588e0fa874..d04300303e 100644
---
a/innodb/src/test/java/org/apache/calcite/adapter/innodb/InnodbAdapterDataTypesTest.java
+++
b/innodb/src/test/java/org/apache/calcite/adapter/innodb/InnodbAdapterDataTypesTest.java
@@ -19,8 +19,6 @@
import org.apache.calcite.test.CalciteAssert;
import org.apache.calcite.util.Sources;
-import org.apache.commons.lang3.StringUtils;
-
import com.alibaba.innodb.java.reader.util.Utils;
import com.google.common.collect.ImmutableMap;
@@ -119,18 +117,18 @@ public class InnodbAdapterDataTypesTest {
+ "f_decimal4=123.100; "
+ "f_decimal5=12346; "
+ "f_decimal6=12345.1234567890123456789012345; "
- + "f_varchar=c" + StringUtils.repeat('x', 31) + "; "
- + "f_varchar_overflow=c" + StringUtils.repeat("データ", 300) + ";
"
+ + "f_varchar=c" + repeat('x', 31) + "; "
+ + "f_varchar_overflow=c" + repeat("データ", 300) + "; "
+ "f_varchar_null=null; "
- + "f_char_32=c" + StringUtils.repeat("данные", 2) + "; "
- + "f_char_255=c" + StringUtils.repeat("数据", 100) + "; "
+ + "f_char_32=c" + repeat("данные", 2) + "; "
+ + "f_char_255=c" + repeat("数据", 100) + "; "
+ "f_char_null=null; "
+ "f_boolean=false; "
+ "f_bool=true; "
- + "f_tinytext=c" + StringUtils.repeat("Data", 50) + "; "
- + "f_text=c" + StringUtils.repeat("Daten", 200) + "; "
- + "f_mediumtext=c" + StringUtils.repeat("Datos", 200) + "; "
- + "f_longtext=c" + StringUtils.repeat("Les données", 800) + ";
"
+ + "f_tinytext=c" + repeat("Data", 50) + "; "
+ + "f_text=c" + repeat("Daten", 200) + "; "
+ + "f_mediumtext=c" + repeat("Datos", 200) + "; "
+ + "f_longtext=c" + repeat("Les données", 800) + "; "
+ "f_tinyblob="
+ genByteArrayString("63", (byte) 0x0a, 100) + "; "
+ "f_blob="
@@ -164,18 +162,18 @@ public class InnodbAdapterDataTypesTest {
+ "f_decimal4=456.000; "
+ "f_decimal5=0; "
+ "f_decimal6=-0.0123456789012345678912345; "
- + "f_varchar=d" + StringUtils.repeat('y', 31) + "; "
- + "f_varchar_overflow=d" + StringUtils.repeat("データ", 300) + ";
"
+ + "f_varchar=d" + repeat('y', 31) + "; "
+ + "f_varchar_overflow=d" + repeat("データ", 300) + "; "
+ "f_varchar_null=null; "
- + "f_char_32=d" + StringUtils.repeat("данные", 2) + "; "
- + "f_char_255=d" + StringUtils.repeat("数据", 100) + "; "
+ + "f_char_32=d" + repeat("данные", 2) + "; "
+ + "f_char_255=d" + repeat("数据", 100) + "; "
+ "f_char_null=null; "
+ "f_boolean=false; "
+ "f_bool=true; "
- + "f_tinytext=d" + StringUtils.repeat("Data", 50) + "; "
- + "f_text=d" + StringUtils.repeat("Daten", 200) + "; "
- + "f_mediumtext=d" + StringUtils.repeat("Datos", 200) + "; "
- + "f_longtext=d" + StringUtils.repeat("Les données", 800) + ";
"
+ + "f_tinytext=d" + repeat("Data", 50) + "; "
+ + "f_text=d" + repeat("Daten", 200) + "; "
+ + "f_mediumtext=d" + repeat("Datos", 200) + "; "
+ + "f_longtext=d" + repeat("Les données", 800) + "; "
+ "f_tinyblob="
+ genByteArrayString("64", (byte) 0x0a, 100) + "; "
+ "f_blob="
@@ -192,6 +190,26 @@ public class InnodbAdapterDataTypesTest {
+ "f_set=a,e,i,o,u");
}
+ private static String repeat(String s, int n) {
+ if (n <= 0) {
+ return "";
+ }
+ StringBuilder b = new StringBuilder(s.length() * n);
+ for (int i = 0; i < n; i++) {
+ b.append(s);
+ }
+ return b.toString();
+ }
+
+ private static String repeat(char c, int n) {
+ if (n <= 0) {
+ return "";
+ }
+ char[] a = new char[n];
+ java.util.Arrays.fill(a, c);
+ return String.valueOf(a);
+ }
+
private String genByteArrayString(String prefix, byte b, int repeat) {
StringBuilder str = new StringBuilder();
str.append(prefix);
diff --git
a/plus/src/test/java/org/apache/calcite/adapter/os/OsAdapterTest.java
b/plus/src/test/java/org/apache/calcite/adapter/os/OsAdapterTest.java
index 9ff5c3f99c..e1158d5aa6 100644
--- a/plus/src/test/java/org/apache/calcite/adapter/os/OsAdapterTest.java
+++ b/plus/src/test/java/org/apache/calcite/adapter/os/OsAdapterTest.java
@@ -71,12 +71,6 @@
* </ul>
*/
class OsAdapterTest {
- private static final String OS_NAME = System.getProperty("os.name");
-
- private static boolean isWindows() {
- return OS_NAME.startsWith("Windows");
- }
-
/** Returns whether there is a ".git" directory in this directory or in a
* directory between this directory and root. */
private static boolean hasGit() {
@@ -114,7 +108,7 @@ private static boolean checkProcessExists(String command) {
}
@Test void testDu() {
- assumeFalse(isWindows(), "Skip: the 'du' table does not work on Windows");
+ assumeFalse(Util.isWindows(), "Skip: the 'du' table does not work on
Windows");
assumeToolExists("du");
sql("select * from du")
.returns(r -> {
@@ -130,7 +124,7 @@ private static boolean checkProcessExists(String command) {
}
@Test void testDuFilterSortLimit() {
- assumeFalse(isWindows(), "Skip: the 'du' table does not work on Windows");
+ assumeFalse(Util.isWindows(), "Skip: the 'du' table does not work on
Windows");
assumeToolExists("du");
sql("select * from du where path like '%/src/test/java/%'\n"
+ "order by 1 limit 2")
@@ -149,14 +143,14 @@ private static boolean checkProcessExists(String command)
{
}
@Test void testFiles() {
- assumeFalse(isWindows(), "Skip: the 'files' table does not work on
Windows");
+ assumeFalse(Util.isWindows(), "Skip: the 'files' table does not work on
Windows");
sql("select distinct type from files")
.returnsUnordered("type=d",
"type=f");
}
@Test void testPs() {
- assumeFalse(isWindows(), "Skip: the 'ps' table does not work on Windows");
+ assumeFalse(Util.isWindows(), "Skip: the 'ps' table does not work on
Windows");
assumeToolExists("ps");
sql("select * from ps")
.returns(r -> {
@@ -176,7 +170,7 @@ private static boolean checkProcessExists(String command) {
}
@Test void testPsDistinct() {
- assumeFalse(isWindows(), "Skip: the 'ps' table does not work on Windows");
+ assumeFalse(Util.isWindows(), "Skip: the 'ps' table does not work on
Windows");
assumeToolExists("ps");
sql("select distinct `user` from ps")
.returns(r -> {
@@ -229,7 +223,7 @@ private static boolean checkProcessExists(String command) {
}
@Test void testVmstat() {
- assumeFalse(isWindows(), "Skip: the 'files' table does not work on
Windows");
+ assumeFalse(Util.isWindows(), "Skip: the 'files' table does not work on
Windows");
assumeToolExists("vmstat");
sql("select * from vmstat")
.returns(r -> {
diff --git a/redis/build.gradle.kts b/redis/build.gradle.kts
index 0cbc43112f..11eb99f93d 100644
--- a/redis/build.gradle.kts
+++ b/redis/build.gradle.kts
@@ -24,7 +24,6 @@
implementation("com.google.guava:guava")
implementation("commons-io:commons-io")
implementation("org.apache.calcite.avatica:avatica-core")
- implementation("org.apache.commons:commons-lang3")
implementation("org.apache.commons:commons-pool2")
implementation("org.slf4j:slf4j-api")
diff --git
a/redis/src/main/java/org/apache/calcite/adapter/redis/RedisDataProcess.java
b/redis/src/main/java/org/apache/calcite/adapter/redis/RedisDataProcess.java
index 853507cd1a..024a0e7df9 100644
--- a/redis/src/main/java/org/apache/calcite/adapter/redis/RedisDataProcess.java
+++ b/redis/src/main/java/org/apache/calcite/adapter/redis/RedisDataProcess.java
@@ -16,8 +16,6 @@
*/
package org.apache.calcite.adapter.redis;
-import org.apache.commons.lang3.StringUtils;
-
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
@@ -77,7 +75,7 @@ public List<Object[]> read() {
}
private Object[] parseJson(String value) {
- assert StringUtils.isNotEmpty(value);
+ assert value != null && !value.isEmpty();
Object[] arr = new Object[fields.size()];
try {
JsonNode jsonNode = objectMapper.readTree(value);
@@ -97,7 +95,7 @@ private Object[] parseJson(String value) {
}
private Object[] parseCsv(String value) {
- assert StringUtils.isNotEmpty(value);
+ assert value != null && !value.isEmpty();
String[] values = value.split(keyDelimiter);
Object[] arr = new Object[fields.size()];
assert values.length == arr.length;
diff --git
a/redis/src/main/java/org/apache/calcite/adapter/redis/RedisEnumerator.java
b/redis/src/main/java/org/apache/calcite/adapter/redis/RedisEnumerator.java
index 0643d804fe..dede17cd48 100644
--- a/redis/src/main/java/org/apache/calcite/adapter/redis/RedisEnumerator.java
+++ b/redis/src/main/java/org/apache/calcite/adapter/redis/RedisEnumerator.java
@@ -19,8 +19,6 @@
import org.apache.calcite.linq4j.Enumerator;
import org.apache.calcite.linq4j.Linq4j;
-import org.apache.commons.lang3.StringUtils;
-
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -38,13 +36,14 @@ class RedisEnumerator implements Enumerator<Object[]> {
RedisEnumerator(RedisConfig redisConfig, RedisSchema schema, String
tableName) {
RedisTableFieldInfo tableFieldInfo = schema.getTableFieldInfo(tableName);
+ String password = redisConfig.getPassword();
RedisJedisManager redisManager =
new RedisJedisManager(redisConfig.getHost(), redisConfig.getPort(),
- redisConfig.getDatabase(), redisConfig.getPassword());
+ redisConfig.getDatabase(), password);
try (Jedis jedis = redisManager.getResource()) {
- if (StringUtils.isNotEmpty(redisConfig.getPassword())) {
- jedis.auth(redisConfig.getPassword());
+ if (password != null && !password.isEmpty()) {
+ jedis.auth(password);
}
RedisDataProcess dataProcess = new RedisDataProcess(jedis,
tableFieldInfo);
List<Object[]> objs = dataProcess.read();
diff --git
a/redis/src/main/java/org/apache/calcite/adapter/redis/RedisJedisManager.java
b/redis/src/main/java/org/apache/calcite/adapter/redis/RedisJedisManager.java
index 92647d6c14..226af4b7b7 100644
---
a/redis/src/main/java/org/apache/calcite/adapter/redis/RedisJedisManager.java
+++
b/redis/src/main/java/org/apache/calcite/adapter/redis/RedisJedisManager.java
@@ -18,7 +18,6 @@
import org.apache.calcite.util.trace.CalciteTrace;
-import org.apache.commons.lang3.StringUtils;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import com.google.common.cache.CacheBuilder;
@@ -75,7 +74,7 @@ public Jedis getResource() {
private JedisPool createConsumer() {
String pwd = password;
- if (StringUtils.isEmpty(pwd)) {
+ if (pwd == null || pwd.isEmpty()) {
pwd = null;
}
return new JedisPool(jedisPoolConfig, host, port, Protocol.DEFAULT_TIMEOUT,
diff --git
a/redis/src/main/java/org/apache/calcite/adapter/redis/RedisSchema.java
b/redis/src/main/java/org/apache/calcite/adapter/redis/RedisSchema.java
index 1aad9ae459..42c623895b 100644
--- a/redis/src/main/java/org/apache/calcite/adapter/redis/RedisSchema.java
+++ b/redis/src/main/java/org/apache/calcite/adapter/redis/RedisSchema.java
@@ -20,19 +20,20 @@
import org.apache.calcite.schema.Table;
import org.apache.calcite.schema.impl.AbstractSchema;
-import org.apache.commons.lang3.ObjectUtils;
-import org.apache.commons.lang3.StringUtils;
-
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
@@ -92,7 +93,7 @@ public RedisTableFieldInfo getTableFieldInfo(String
tableName) {
if (jsonCustomTable.name.equals(tableName)) {
Map<String, Object> map =
requireNonNull(jsonCustomTable.operand, OPERAND);
- if (ObjectUtils.isEmpty(map.get(DATA_FORMAT))) {
+ if (isEmptyObject(map.get(DATA_FORMAT))) {
throw new RuntimeException("dataFormat is invalid, it must be raw,
csv or json");
}
RedisDataFormat dataFormatEnum =
@@ -100,7 +101,7 @@ public RedisTableFieldInfo getTableFieldInfo(String
tableName) {
if (dataFormatEnum == null) {
throw new RuntimeException("dataFormat is invalid, it must be raw,
csv or json");
}
- if (ObjectUtils.isEmpty(map.get(FIELDS))) {
+ if (isEmptyObject(map.get(FIELDS))) {
throw new RuntimeException("fields is null");
}
dataFormat = map.get(DATA_FORMAT).toString();
@@ -114,9 +115,45 @@ public RedisTableFieldInfo getTableFieldInfo(String
tableName) {
tableFieldInfo.setTableName(tableName);
tableFieldInfo.setDataFormat(dataFormat);
tableFieldInfo.setFields(fields);
- if (StringUtils.isNotEmpty(keyDelimiter)) {
+ if (!keyDelimiter.isEmpty()) {
tableFieldInfo.setKeyDelimiter(keyDelimiter);
}
return tableFieldInfo;
}
+
+ /** Returns whether an object should be considered "empty" for
configuration/validation.
+ *
+ * <p>Semantics are aligned with
+ * {@code org.apache.commons.lang3.ObjectUtils#isEmptyObject(Object)}.
+ * <ul>
+ * <li>{@code null} is empty
+ * <li>{@link CharSequence}: {@code length()==0} is empty
+ * <li>{@link Collection}/{@link Map}: {@code isEmpty()} is empty
+ * <li>{@link Optional}: {@code !isPresent()} is empty
+ * <li>Arrays: {@code length==0} is empty
+ * </ul>
+ */
+ public static boolean isEmptyObject(@Nullable Object o) {
+ if (o == null) {
+ return true;
+ }
+ if (o instanceof CharSequence) {
+ return ((CharSequence) o).length() == 0;
+ }
+ if (o instanceof Collection<?>) {
+ return ((Collection<?>) o).isEmpty();
+ }
+ if (o instanceof Map) {
+ return ((Map<?, ?>) o).isEmpty();
+ }
+ if (o instanceof Optional) {
+ return !((Optional<?>) o).isPresent();
+ }
+ final Class<?> c = o.getClass();
+ if (c.isArray()) {
+ return java.lang.reflect.Array.getLength(o) == 0;
+ }
+ return false;
+ }
+
}
diff --git
a/redis/src/test/java/org/apache/calcite/adapter/redis/RedisCaseBase.java
b/redis/src/test/java/org/apache/calcite/adapter/redis/RedisCaseBase.java
index 44d82385aa..adfc7dd6c6 100644
--- a/redis/src/test/java/org/apache/calcite/adapter/redis/RedisCaseBase.java
+++ b/redis/src/test/java/org/apache/calcite/adapter/redis/RedisCaseBase.java
@@ -17,6 +17,7 @@
package org.apache.calcite.adapter.redis;
import org.apache.calcite.config.CalciteSystemProperty;
+import org.apache.calcite.util.Util;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
@@ -71,7 +72,7 @@ public static void startRedisContainer() {
@BeforeEach
public void createRedisServer() throws IOException {
if (!REDIS_CONTAINER.isRunning()) {
- if (isWindows()) {
+ if (Util.isWindows()) {
redisServer =
RedisServer.builder().port(PORT).setting(MAX_HEAP).build();
} else {
redisServer = new RedisServer(PORT);
@@ -81,10 +82,6 @@ public void createRedisServer() throws IOException {
}
}
- private static boolean isWindows() {
- return System.getProperty("os.name").startsWith("Windows");
- }
-
@AfterEach
public void stopRedisServer() {
if (!REDIS_CONTAINER.isRunning()) {
diff --git a/testkit/build.gradle.kts b/testkit/build.gradle.kts
index 2154cbf82c..613526ebd0 100644
--- a/testkit/build.gradle.kts
+++ b/testkit/build.gradle.kts
@@ -30,7 +30,6 @@
implementation("net.hydromatic:scott-data-hsqldb")
implementation("net.hydromatic:steelwheels-data-hsqldb")
implementation("org.apache.commons:commons-dbcp2")
- implementation("org.apache.commons:commons-lang3")
implementation("org.apache.commons:commons-pool2")
implementation("org.hamcrest:hamcrest")
implementation("org.hsqldb:hsqldb::jdk8")
diff --git a/testkit/src/main/java/org/apache/calcite/test/CalciteAssert.java
b/testkit/src/main/java/org/apache/calcite/test/CalciteAssert.java
index 8a846da87e..cbc2f6c799 100644
--- a/testkit/src/main/java/org/apache/calcite/test/CalciteAssert.java
+++ b/testkit/src/main/java/org/apache/calcite/test/CalciteAssert.java
@@ -137,8 +137,6 @@
import static org.apache.calcite.test.Matchers.containsStringLinux;
import static org.apache.calcite.test.Matchers.isLinux;
-import static org.apache.commons.lang3.StringUtils.countMatches;
-
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.hasItem;
@@ -500,6 +498,19 @@ public static Consumer<ResultSet> checkResultContains(
};
}
+ private static int countMatches(@Nullable String str, @Nullable String sub) {
+ if (str == null || sub == null || sub.isEmpty()) {
+ return 0;
+ }
+ int count = 0;
+ int idx = 0;
+ while ((idx = str.indexOf(sub, idx)) >= 0) {
+ count++;
+ idx += sub.length();
+ }
+ return count;
+ }
+
public static Consumer<ResultSet> checkMaskedResultContains(
final String expected) {
return s -> {