Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 62 additions & 36 deletions api/src/org/labkey/api/data/DbScope.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,15 @@
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.junit.Assert;
import org.junit.Assume;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.labkey.api.action.ApiUsageException;
import org.labkey.api.audit.TransactionAuditProvider;
import org.labkey.api.cache.Cache;
import org.labkey.api.data.ConnectionWrapper.Closer;
import org.labkey.api.data.dialect.SimpleSqlDialect;
import org.labkey.api.data.dialect.SqlDialect;
import org.labkey.api.data.dialect.SqlDialect.DataSourcePropertyReader;
import org.labkey.api.data.dialect.SqlDialectManager;
Expand All @@ -49,6 +53,7 @@
import org.labkey.api.util.DeadlockPreventingException;
import org.labkey.api.util.DebugInfoDumper;
import org.labkey.api.util.GUID;
import org.labkey.api.util.JunitUtil;
import org.labkey.api.util.LoggerWriter;
import org.labkey.api.util.MemTracker;
import org.labkey.api.util.ResultSetUtil;
Expand Down Expand Up @@ -2145,7 +2150,8 @@ public static DbScope getDbScope(String dsName)
/**
* Some DbScopes shouldn't be exercised by junit tests (e.g., an external data source connected to LabKey Server via
* the PostgreSQL wire protocol)
* Tests that use this should be annotated with '@TestWhen(TestWhen.When.DB_SCOPE)'
* Tests that use this should be annotated with '@TestWhen(TestWhen.When.DBSCOPE)' to ensure they run in suites that
* configure external data sources on TeamCity.
*
* @return A collection of DbScopes that are suitable for testing
*/
Expand Down Expand Up @@ -2962,46 +2968,57 @@ public void afterLoadTable(SchemaTableInfo ti)

// Test dialects that are in-use; only for tests that require connecting to the database.
@TestWhen(TestWhen.When.DBSCOPE)
@RunWith(Parameterized.class)
public static class DialectTestCase extends Assert
{
@Parameterized.Parameters(name = "{1}")
public static Collection<Object[]> schemas()
{
return JunitUtil.getDbScopesTestParameters();
}

private final DbScope scope;

public DialectTestCase(DbScope scope, String displayName)
{
this.scope = scope;
}

@Test
public void testAllScopes() throws SQLException, IOException
public void testKeywords() throws SQLException, IOException
{
for (DbScope scope : getDbScopesToTest())
{
SqlDialect dialect = scope.getSqlDialect();
SqlDialect dialect = scope.getSqlDialect();

try (Connection conn = scope.getConnection())
{
SqlExecutor executor = new SqlExecutor(scope, conn).setLogLevel(Level.OFF); // We're about to generate a lot of SQLExceptions
dialect.testDialectKeywords(executor);
dialect.testKeywordCandidates(executor);
}
try (Connection conn = scope.getConnection())
{
SqlExecutor executor = new SqlExecutor(scope, conn).setLogLevel(Level.OFF); // We're about to generate a lot of SQLExceptions
dialect.testDialectKeywords(executor);
dialect.testKeywordCandidates(executor);
}
}

@Test
public void testLabKeyScope()
public void testDateDiff()
{
DbScope scope = getLabKeyScope();
SqlDialect dialect = scope.getSqlDialect();
Assume.assumeFalse("Datediff not supported for " + dialect.getClass().getSimpleName(), dialect instanceof SimpleSqlDialect);

testDateDiff(scope, dialect, "2/1/2000", "1/1/2000", Calendar.DATE, 31);
testDateDiff(scope, dialect, "1/1/2001", "1/1/2000", Calendar.DATE, 366);
_testDateDiff(scope, dialect, "2/1/2000", "1/1/2000", Calendar.DATE, 31);
_testDateDiff(scope, dialect, "1/1/2001", "1/1/2000", Calendar.DATE, 366);

testDateDiff(scope, dialect, "2/1/2000", "1/1/2000", Calendar.MONTH, 1);
testDateDiff(scope, dialect, "2/1/2000", "1/31/2000", Calendar.MONTH, 1);
testDateDiff(scope, dialect, "1/1/2000", "1/1/2000", Calendar.MONTH, 0);
testDateDiff(scope, dialect, "1/31/2000", "1/1/2000", Calendar.MONTH, 0);
testDateDiff(scope, dialect, "12/31/2000", "1/1/2000", Calendar.MONTH, 11);
testDateDiff(scope, dialect, "1/1/2001", "1/1/2000", Calendar.MONTH, 12);
testDateDiff(scope, dialect, "1/31/2001", "1/1/2000", Calendar.MONTH, 12);
_testDateDiff(scope, dialect, "2/1/2000", "1/1/2000", Calendar.MONTH, 1);
_testDateDiff(scope, dialect, "2/1/2000", "1/31/2000", Calendar.MONTH, 1);
_testDateDiff(scope, dialect, "1/1/2000", "1/1/2000", Calendar.MONTH, 0);
_testDateDiff(scope, dialect, "1/31/2000", "1/1/2000", Calendar.MONTH, 0);
_testDateDiff(scope, dialect, "12/31/2000", "1/1/2000", Calendar.MONTH, 11);
_testDateDiff(scope, dialect, "1/1/2001", "1/1/2000", Calendar.MONTH, 12);
_testDateDiff(scope, dialect, "1/31/2001", "1/1/2000", Calendar.MONTH, 12);

testDateDiff(scope, dialect, "1/1/2000", "12/31/2000", Calendar.YEAR, 0);
testDateDiff(scope, dialect, "1/1/2001", "1/1/2000", Calendar.YEAR, 1);
_testDateDiff(scope, dialect, "1/1/2000", "12/31/2000", Calendar.YEAR, 0);
_testDateDiff(scope, dialect, "1/1/2001", "1/1/2000", Calendar.YEAR, 1);
}

private void testDateDiff(DbScope scope, SqlDialect dialect, String date1, String date2, int part, int expected)
private void _testDateDiff(DbScope scope, SqlDialect dialect, String date1, String date2, int part, int expected)
{
SQLFragment sql = new SQLFragment("SELECT (");
sql.append(dialect.getDateDiff(part, "CAST('" + date1 + "' AS " + dialect.getDefaultDateTimeDataType() + ")", "CAST('" + date2 + "' AS " + dialect.getDefaultDateTimeDataType() + ")"));
Expand All @@ -3013,24 +3030,33 @@ private void testDateDiff(DbScope scope, SqlDialect dialect, String date1, Strin
}

@TestWhen(TestWhen.When.DBSCOPE)
@RunWith(Parameterized.class)
public static class GroupConcatTestCase extends Assert
{
@Parameterized.Parameters(name = "{1}")
public static Collection<Object[]> schemas()
{
return JunitUtil.getDbScopesTestParameters(scope -> scope.getSqlDialect().supportsGroupConcat());
}

private final DbScope scope;

public GroupConcatTestCase(DbScope scope, String displayName)
{
this.scope = scope;
}

@Test
public void testGroupConcat()
{
for (DbScope scope : getDbScopesToTest())
{
SqlDialect dialect = scope.getSqlDialect();
if (!dialect.supportsGroupConcat())
continue;
SqlDialect dialect = scope.getSqlDialect();

boolean caseInsensitiveCollation = dialect.isSqlServer();
boolean caseInsensitiveCollation = dialect.isSqlServer();

testGroupConcat(scope, dialect, false, false, "x Y z z y");
testGroupConcat(scope, dialect, true, false, caseInsensitiveCollation ? "x Y z" : "x y Y z");
testGroupConcat(scope, dialect, false, true, "x y Y z z");
testGroupConcat(scope, dialect, true, true, caseInsensitiveCollation ? "x Y z" : "x y Y z");
}
testGroupConcat(scope, dialect, false, false, "x Y z z y");
testGroupConcat(scope, dialect, true, false, caseInsensitiveCollation ? "x Y z" : "x y Y z");
testGroupConcat(scope, dialect, false, true, "x y Y z z");
testGroupConcat(scope, dialect, true, true, caseInsensitiveCollation ? "x Y z" : "x y Y z");
}

private void testGroupConcat(DbScope scope, SqlDialect dialect, boolean distinct, boolean sorted, String expected)
Expand Down
37 changes: 23 additions & 14 deletions api/src/org/labkey/api/data/dialect/SqlDialect.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.labkey.api.collections.CaseInsensitiveHashMap;
import org.labkey.api.collections.CaseInsensitiveHashSet;
import org.labkey.api.collections.CsvSet;
Expand Down Expand Up @@ -61,6 +63,7 @@
import org.labkey.api.test.TestWhen;
import org.labkey.api.util.ExceptionUtil;
import org.labkey.api.util.HtmlString;
import org.labkey.api.util.JunitUtil;
import org.labkey.api.util.MemTracker;
import org.labkey.api.util.StringUtilsLabKey;
import org.labkey.api.util.SystemMaintenance;
Expand Down Expand Up @@ -2416,21 +2419,22 @@ public SQLFragment array_element_like(SQLFragment a, String... values)
//

@TestWhen(TestWhen.When.DBSCOPE)
@RunWith(Parameterized.class)
public static class DialectTestCase
{
DbScope s;
SqlDialect d;
@Parameterized.Parameters(name = "{1}")
public static Collection<Object[]> schemas()
{
return JunitUtil.getDbScopesTestParameters();
}

@Test
public void testScopes()
private final DbScope s;
private final SqlDialect d;

public DialectTestCase(DbScope scope, String displayName)
{
DbScope.getDbScopesToTest().forEach(scope ->
{
this.s = scope;
this.d = scope.getSqlDialect();
testDialectStringHandler();
testLikeOperator();
});
this.s = scope;
this.d = scope.getSqlDialect();
}

void testEquals(String expected, SQLFragment sqlf)
Expand All @@ -2439,13 +2443,14 @@ void testEquals(String expected, SQLFragment sqlf)
{
assertEquals(expected, new SqlSelector(s, sqlf).getObject(String.class));
}
catch (AssertionError|Exception ae)
catch (AssertionError | Exception ae)
{
throw new AssertionError("Expected [" + expected + "] Failed for dialect " + d.getClass().getName() + " on scope " + s.getDatabaseUrl() + ": " + sqlf.toDebugString(), ae);
}
}

void testDialectStringHandler()
@Test
public void testDialectStringHandler()
{
// quotes backslashes etc
for (String v : Arrays.asList("", "'", "\"", "\\", "''", "\\'", "\\\\'", "'''", "><&/%\\' \"1~\\!@$&'()\"_+{}-=[],.#\u2603\u00E4\u00F6\u00FC\u00C5"))
Expand All @@ -2457,7 +2462,8 @@ void testDialectStringHandler()
testEquals(v, new SQLFragment("SELECT ").appendStringLiteral(v, d));
}

void testLikeOperator()
@Test
public 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(d));
Expand All @@ -2468,7 +2474,10 @@ void testLikeOperator()
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(d));
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(d));
}
}

public static class LabKeyScopeDialectTestCase
{
@Test
public void testAutoIncrementQuery()
{
Expand Down
44 changes: 44 additions & 0 deletions api/src/org/labkey/api/util/JunitUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import org.labkey.api.collections.CaseInsensitiveHashMap;
import org.labkey.api.data.Container;
import org.labkey.api.data.ContainerManager;
import org.labkey.api.data.DbScope;
import org.labkey.api.module.Module;
import org.labkey.api.settings.AppProps;
import org.w3c.dom.Node;
Expand All @@ -32,9 +33,11 @@
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
Expand All @@ -45,6 +48,7 @@
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import java.util.stream.Stream;


Expand Down Expand Up @@ -272,4 +276,44 @@ public static void createRaces(final Runnable runnable, final int threads, final
throw new AssumptionViolatedException(message + " Skipping test in production mode.");
}
}

/**
* Gets `DbScope.getDbScopesToTest()`, structured for use with `@RunWith(Parameterized.class)`.<br>
* Tests using these parameters should also be annotated with `@TestWhen(TestWhen.When.DBSCOPE)` to ensure they run
* in suites that configure external data sources on TeamCity.
*
* <pre>{@code
* @TestWhen(TestWhen.When.DBSCOPE)
* @RunWith(Parameterized.class)
* public static class DbScopeTestCase
* {
* @Parameterized.Parameters(name = "{1}")
* public static Collection<Object[]> schemas()
* {
* return JunitUtil.getDbScopesTestParameters();
* }
*
* private final DbScope scope;
*
* public DbScopeTestCase(DbScope scope, String displayName)
* {
* this.scope = scope;
* }
*
* // @Test cases will me multiplied across all found db scopes
* }
* }</pre>
*/
public static Collection<Object[]> getDbScopesTestParameters(Function<DbScope, Boolean> filter)
{
return DbScope.getDbScopesToTest().stream()
.filter(filter::apply)
.map(scope -> new Object[]{scope, scope.getSqlDialect().getClass().getSimpleName()})
.toList();
}

public static Collection<Object[]> getDbScopesTestParameters()
{
return getDbScopesTestParameters(_ -> true);
}
}
1 change: 1 addition & 0 deletions core/src/org/labkey/core/CoreModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -1478,6 +1478,7 @@ public TabDisplayMode getTabDisplayMode()
SecurityApiActions.TestCase.class,
SecurityController.TestCase.class,
SqlDialect.DialectTestCase.class,
SqlDialect.LabKeyScopeDialectTestCase.class,
SqlScriptController.TestCase.class,
TableViewFormTestCase.class,
UnknownSchemasTest.class,
Expand Down
1 change: 1 addition & 0 deletions query/src/org/labkey/query/QueryModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,7 @@ public Set<String> getSchemaNames()
JdbcType.TestCase.class,
MemberSet.TestCase.class,
MetadataElementBase.TestCase.class,
Method.IsDistinctFromMethodTestCase.class,
Method.TestCase.class,
ExpressionAssistantAgentAction.TestCase.class,
QNode.TestCase.class,
Expand Down
Loading