diff --git a/api/src/org/labkey/api/data/dialect/SqlDialect.java b/api/src/org/labkey/api/data/dialect/SqlDialect.java index 35eab33eedc..537929ef88e 100644 --- a/api/src/org/labkey/api/data/dialect/SqlDialect.java +++ b/api/src/org/labkey/api/data/dialect/SqlDialect.java @@ -27,6 +27,7 @@ import org.labkey.api.collections.CsvSet; import org.labkey.api.collections.Sets; import org.labkey.api.data.ColumnInfo; +import org.labkey.api.data.CompareType; import org.labkey.api.data.ConnectionWrapper; import org.labkey.api.data.CoreSchema; import org.labkey.api.data.DatabaseTableType; @@ -535,6 +536,40 @@ public SQLFragment appendInClauseSql(SQLFragment sql, @NotNull Collection par return DEFAULT_GENERATOR.appendInClauseSql(sql, params); } + public SQLFragment appendCaseInsensitiveLikeClause(SQLFragment sql, @NotNull String matchStr, @Nullable String wildcardPrefix, @Nullable String wildcardSuffix, char escapeChar) + { + String prefix = wildcardPrefix != null ? wildcardPrefix : ""; + String suffix = wildcardSuffix != null ? wildcardSuffix : ""; + String prefixLike = prefix + CompareType.escapeLikePattern(matchStr, escapeChar) + suffix; + String escapeToken = " ESCAPE '" + escapeChar + "'"; + sql.append(" ") + .append(getCaseInsensitiveLikeOperator()) + .append(" ") + .appendValue(prefixLike) + .append(escapeToken); + return sql; + } + + public SQLFragment appendCaseInsensitiveLikeClause(SQLFragment sql, @NotNull String matchStr, @Nullable String wildcardPrefix, @Nullable String wildcardSuffix) + { + return appendCaseInsensitiveLikeClause(sql, matchStr, wildcardPrefix, wildcardSuffix, '!'); + } + + public SQLFragment appendCaseInsensitiveLikeClause(SQLFragment sql, @NotNull String matchStr) + { + return appendCaseInsensitiveLikeClause(sql, matchStr, "%", "%", '!'); + } + + public SQLFragment appendCaseInsensitiveStartsWith(SQLFragment sql, @NotNull String matchStr) + { + return appendCaseInsensitiveLikeClause(sql, matchStr, null, "%", '!'); + } + + public SQLFragment appendCaseInsensitiveEndsWith(SQLFragment sql, @NotNull String matchStr) + { + return appendCaseInsensitiveLikeClause(sql, matchStr, "%", null, '!'); + } + public abstract boolean requiresStatementMaxRows(); /** @@ -2110,6 +2145,7 @@ public void testScopes() this.s = scope; this.d = scope.getSqlDialect(); testDialectStringHandler(); + testLikeOperator(); }); } @@ -2136,5 +2172,17 @@ void testDialectStringHandler() for (String v : Arrays.asList("\\b", "\\f", "\\n", "\\r", "\\t", "\\1", "\\22", "\\333", "\\xf", "\\x20", "\\1234", "\\U12345678")) testEquals(v, new SQLFragment("SELECT ").appendStringLiteral(v, d)); } + + void testLikeOperator() + { + String stringLiteralPrefix = d.isSqlServer() ? " N" : " "; + assertEquals("SELECT * FROM A WHERE Name " + d.getCaseInsensitiveLikeOperator() + stringLiteralPrefix + "'ABC%' ESCAPE '!'", d.appendCaseInsensitiveStartsWith(new SQLFragment("SELECT * FROM A WHERE Name"), "ABC").toDebugString()); + assertEquals("SELECT * FROM A WHERE Name " + d.getCaseInsensitiveLikeOperator() + stringLiteralPrefix + "'a!%bc%' ESCAPE '!'", d.appendCaseInsensitiveStartsWith(new SQLFragment("SELECT * FROM A WHERE Name"), "a%bc").toDebugString()); + assertEquals("SELECT * FROM A WHERE Name " + d.getCaseInsensitiveLikeOperator() + stringLiteralPrefix + "'%ab!_C' ESCAPE '!'", d.appendCaseInsensitiveEndsWith(new SQLFragment("SELECT * FROM A WHERE Name"), "ab_C").toDebugString()); + assertEquals("SELECT * FROM A WHERE Name " + d.getCaseInsensitiveLikeOperator() + stringLiteralPrefix + "'%a![b]C%' ESCAPE '!'", d.appendCaseInsensitiveLikeClause(new SQLFragment("SELECT * FROM A WHERE Name"), "a[b]C").toDebugString()); + assertEquals("SELECT * FROM A WHERE Name " + d.getCaseInsensitiveLikeOperator() + stringLiteralPrefix + "'a![b]C_' ESCAPE '!'", d.appendCaseInsensitiveLikeClause(new SQLFragment("SELECT * FROM A WHERE Name"), "a[b]C", null, "_").toDebugString()); + assertEquals("SELECT * FROM A WHERE Name " + d.getCaseInsensitiveLikeOperator() + stringLiteralPrefix + "'_a!_![b]C%' ESCAPE '!'", d.appendCaseInsensitiveLikeClause(new SQLFragment("SELECT * FROM A WHERE Name"), "a_[b]C", "_", "%").toDebugString()); + assertEquals("SELECT * FROM A WHERE Name " + d.getCaseInsensitiveLikeOperator() + stringLiteralPrefix + "'_a[_[[b]C!d%' ESCAPE '['", d.appendCaseInsensitiveLikeClause(new SQLFragment("SELECT * FROM A WHERE Name"), "a_[b]C!d", "_", "%", '[').toDebugString()); + } } } diff --git a/experiment/src/org/labkey/experiment/api/ExpIdentifiableBaseImpl.java b/experiment/src/org/labkey/experiment/api/ExpIdentifiableBaseImpl.java index 1ab2e7dacaa..63ee0169963 100644 --- a/experiment/src/org/labkey/experiment/api/ExpIdentifiableBaseImpl.java +++ b/experiment/src/org/labkey/experiment/api/ExpIdentifiableBaseImpl.java @@ -17,11 +17,13 @@ package org.labkey.experiment.api; import org.jetbrains.annotations.Nullable; +import org.labkey.api.data.CompareType; import org.labkey.api.data.DbScope; import org.labkey.api.data.SQLFragment; import org.labkey.api.data.SqlSelector; import org.labkey.api.data.Table; import org.labkey.api.data.TableInfo; +import org.labkey.api.data.dialect.SqlDialect; import org.labkey.api.exp.IdentifiableBase; import org.labkey.api.exp.Lsid; import org.labkey.api.exp.OntologyManager; @@ -175,13 +177,13 @@ protected Function getMaxCounterWithPrefixFunction(TableInfo table // Here we don't apply a container filter and instead rely on the "CpasType" of the associated data. // This allows for us to process max counter from all matching results within the provided type. - String prefixLike = namePrefix.toLowerCase() + "%"; // case insensitive SQLFragment sql = new SQLFragment() .append("SELECT Name\n") .append("FROM ").append(tableInfo, "i") - .append(" WHERE i.CpasType = ? AND LOWER(i.NAME) LIKE ?") - .add(dataTypeLsid) - .add(prefixLike); + .append(" WHERE i.CpasType = ? AND i.NAME") + .add(dataTypeLsid); + + tableInfo.getSqlDialect().appendCaseInsensitiveStartsWith(sql, namePrefix); List names = new SqlSelector(tableInfo.getSchema(), sql).getArrayList(String.class);