Skip to content

Commit d2edbe9

Browse files
authored
Standardize IN clause generation (#7221)
1 parent 8bf8fe9 commit d2edbe9

16 files changed

Lines changed: 250 additions & 218 deletions

api/src/org/labkey/api/attachments/LookAndFeelResourceType.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ private LookAndFeelResourceType()
4141
@Override
4242
public void addWhereSql(SQLFragment sql, String parentColumn, String documentNameColumn)
4343
{
44+
// Keep in sync with CoreMigrationSchemaHandler.copyAttachments()
4445
sql.append(parentColumn).append(" IN (SELECT EntityId FROM ").append(CoreSchema.getInstance().getTableInfoContainers(), "c").append(") AND (");
4546
sql.append(documentNameColumn).append(" IN (?, ?) OR ");
4647
sql.add(AttachmentCache.FAVICON_FILE_NAME);

api/src/org/labkey/api/data/SimpleFilter.java

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1865,6 +1865,41 @@ private int howManyLessThan(List<Integer> userIdsDesc, int max)
18651865
}
18661866
return howMany;
18671867
}
1868+
1869+
/**
1870+
* This used to be a PostgreSQL-specific test, but it should run and pass on SQL Server as well. It's largely
1871+
* redundant with testLargeInClause() above, but causes no harm.
1872+
*/
1873+
@Test
1874+
public void testTempTableInClause()
1875+
{
1876+
DbSchema core = CoreSchema.getInstance().getSchema();
1877+
SqlDialect d = core.getSqlDialect();
1878+
1879+
Collection<Integer> allUserIds = new TableSelector(CoreSchema.getInstance().getTableInfoUsersData(), Collections.singleton("UserId")).getCollection(Integer.class);
1880+
SQLFragment shortSql = new SQLFragment("SELECT * FROM core.UsersData WHERE UserId");
1881+
d.appendInClauseSql(shortSql, allUserIds);
1882+
assertEquals(allUserIds.size(), new SqlSelector(core, shortSql).getRowCount());
1883+
1884+
ArrayList<Object> l = new ArrayList<>(allUserIds);
1885+
while (l.size() < SqlDialect.TEMP_TABLE_GENERATOR_MIN_SIZE)
1886+
l.addAll(allUserIds);
1887+
SQLFragment longSql = new SQLFragment("SELECT * FROM core.UsersData WHERE UserId");
1888+
d.appendInClauseSql(longSql, l);
1889+
assertEquals(allUserIds.size(), new SqlSelector(core, longSql).getRowCount());
1890+
1891+
Collection<String> allDisplayNames = new TableSelector(CoreSchema.getInstance().getTableInfoUsersData(), Collections.singleton("DisplayName")).getCollection(String.class);
1892+
SQLFragment shortSqlStr = new SQLFragment("SELECT * FROM core.UsersData WHERE DisplayName");
1893+
d.appendInClauseSql(shortSqlStr, allDisplayNames);
1894+
assertEquals(allDisplayNames.size(), new SqlSelector(core, shortSqlStr).getRowCount());
1895+
1896+
l = new ArrayList<>(allDisplayNames);
1897+
while (l.size() < SqlDialect.TEMP_TABLE_GENERATOR_MIN_SIZE)
1898+
l.addAll(allDisplayNames);
1899+
SQLFragment longSqlStr = new SQLFragment("SELECT * FROM core.UsersData WHERE DisplayName");
1900+
d.appendInClauseSql(longSqlStr, l);
1901+
assertEquals(allDisplayNames.size(), new SqlSelector(core, longSqlStr).getRowCount());
1902+
}
18681903
}
18691904

18701905
public static class BetweenClauseTestCase extends ClauseTestCase

api/src/org/labkey/api/data/TempTableInClauseGenerator.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,9 @@ else if (jdbcType == JdbcType.INTEGER)
142142
try (var ignored = SpringActionController.ignoreSqlUpdates())
143143
{
144144
new SqlExecutor(tempSchema).execute(indexSql);
145+
tempSchema.getSqlDialect().updateStatistics(tempTableInfo); // Immediately analyze the newly populated & indexed table
145146
}
147+
146148
TempTableInfo cacheEntry = tempTableInfo;
147149

148150
// Don't bother caching if we're in a transaction

api/src/org/labkey/api/data/dialect/BasePostgreSqlDialect.java

Lines changed: 0 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@
3434
import org.labkey.api.data.DbSchemaType;
3535
import org.labkey.api.data.DbScope;
3636
import org.labkey.api.data.DbScope.LabKeyDataSource;
37-
import org.labkey.api.data.InClauseGenerator;
3837
import org.labkey.api.data.JdbcType;
3938
import org.labkey.api.data.MetadataSqlSelector;
4039
import org.labkey.api.data.PropertyStorageSpec;
@@ -49,7 +48,6 @@
4948
import org.labkey.api.data.Table;
5049
import org.labkey.api.data.TableChange;
5150
import org.labkey.api.data.TableInfo;
52-
import org.labkey.api.data.TempTableInClauseGenerator;
5351
import org.labkey.api.data.TempTableTracker;
5452
import org.labkey.api.data.dialect.LimitRowsSqlGenerator.LimitRowsCustomizer;
5553
import org.labkey.api.data.dialect.LimitRowsSqlGenerator.StandardLimitRowsCustomizer;
@@ -92,19 +90,15 @@ public abstract class BasePostgreSqlDialect extends SqlDialect
9290
{
9391
// Issue 52190: Expose troubleshooting data that supports postgreSQL-specific analysis
9492
public static final String POSTGRES_SCHEMA_NAME = "postgres";
95-
public static final int TEMPTABLE_GENERATOR_MINSIZE = 1000;
9693

9794
private final Map<String, Integer> _domainScaleMap = new CopyOnWriteHashMap<>();
9895
private final AtomicBoolean _arraySortFunctionExists = new AtomicBoolean(false);
99-
private final InClauseGenerator _tempTableInClauseGenerator = new TempTableInClauseGenerator();
10096

10197
private HtmlString _adminWarning = null;
10298

10399
// Default to 9 and let newer versions be refreshed later
104100
private int _majorVersion = 9;
105101

106-
protected InClauseGenerator _inClauseGenerator = null;
107-
108102
// Specifies if this PostgreSQL server treats backslashes in string literals as normal characters (as per the SQL
109103
// standard) or as escape characters (old, non-standard behavior). As of PostgreSQL 9.1, the setting
110104
// standard_conforming_strings is on by default; before 9.1, it was off by default. We check the server setting
@@ -289,24 +283,6 @@ public String addReselect(SQLFragment sql, ColumnInfo column, @Nullable String p
289283
return proposedVariable;
290284
}
291285

292-
@Override
293-
public SQLFragment appendInClauseSql(SQLFragment sql, @NotNull Collection<?> params)
294-
{
295-
return appendInClauseSqlWithCustomInClauseGenerator(sql, params, _tempTableInClauseGenerator);
296-
}
297-
298-
@Override
299-
public SQLFragment appendInClauseSqlWithCustomInClauseGenerator(SQLFragment sql, @NotNull Collection<?> params, InClauseGenerator tempTableGenerator)
300-
{
301-
if (params.size() >= TEMPTABLE_GENERATOR_MINSIZE)
302-
{
303-
SQLFragment ret = tempTableGenerator.appendInClauseSql(sql, params);
304-
if (null != ret)
305-
return ret;
306-
}
307-
return _inClauseGenerator.appendInClauseSql(sql, params);
308-
}
309-
310286
@Override
311287
public @NotNull ResultSet executeWithResults(@NotNull PreparedStatement stmt) throws SQLException
312288
{
@@ -543,12 +519,6 @@ public boolean isSystemSchema(String schemaName)
543519
schemaName.startsWith("pg_toast_temp_");
544520
}
545521

546-
@Override
547-
public String getAnalyzeCommandForTable(String tableName)
548-
{
549-
return "ANALYZE " + tableName;
550-
}
551-
552522
@Override
553523
protected String getSIDQuery()
554524
{
@@ -783,7 +753,6 @@ public void prepare(LabKeyDataSource dataSource)
783753
public String prepare(DbScope scope)
784754
{
785755
initializeUserDefinedTypes(scope);
786-
initializeInClauseGenerator(scope);
787756
determineSettings(scope);
788757
determineIfArraySortFunctionExists(scope);
789758
return super.prepare(scope);
@@ -835,10 +804,6 @@ private String getDomainKey(String schemaName, String domainName)
835804
return ("public".equals(schemaName) ? domainName : "\"" + schemaName + "\".\"" + domainName + "\"");
836805
}
837806

838-
839-
abstract protected void initializeInClauseGenerator(DbScope scope);
840-
841-
842807
// Query any settings that may affect dialect behavior. Right now, only "standard_conforming_strings".
843808
protected void determineSettings(DbScope scope)
844809
{

api/src/org/labkey/api/data/dialect/DatabaseMaintenanceTask.java

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
import org.labkey.api.util.SystemMaintenance.MaintenanceTask;
2525
import org.springframework.jdbc.BadSqlGrammarException;
2626

27-
class DatabaseMaintenanceTask implements MaintenanceTask
27+
public class DatabaseMaintenanceTask implements MaintenanceTask
2828
{
2929
@Override
3030
public String getDescription()
@@ -41,13 +41,17 @@ public String getName()
4141
@Override
4242
public void run(Logger log)
4343
{
44-
DbScope scope = DbScope.getLabKeyScope();
44+
run(DbScope.getLabKeyScope(), log);
45+
}
46+
47+
public static void run(DbScope scope, Logger log)
48+
{
4549
String url = null;
4650

4751
try
4852
{
4953
url = scope.getDataSourceProperties().getUrl();
50-
log.info("Database maintenance on " + url + " started");
54+
log.info("Database maintenance on {} started", url);
5155
}
5256
catch (Exception e)
5357
{
@@ -69,6 +73,6 @@ public void run(Logger log)
6973
}
7074

7175
if (null != url)
72-
log.info("Database maintenance on " + url + " complete");
76+
log.info("Database maintenance on {} complete", url);
7377
}
7478
}

api/src/org/labkey/api/data/dialect/SimpleSqlDialect.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -382,7 +382,7 @@ public void overrideAutoIncrement(StringBuilder statements, TableInfo tinfo)
382382
}
383383

384384
@Override
385-
public String getAnalyzeCommandForTable(String tableName)
385+
public SQLFragment getAnalyzeCommandForTable(String tableName)
386386
{
387387
throw new UnsupportedOperationException(getClass().getSimpleName() + " does not implement");
388388
}

api/src/org/labkey/api/data/dialect/SqlDialect.java

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ public abstract class SqlDialect
102102
{
103103
public static final String GENERIC_ERROR_MESSAGE = "The database experienced an unexpected problem. Please check your input and try again.";
104104
public static final String CUSTOM_UNIQUE_ERROR_MESSAGE = "Constraint violation: cannot insert duplicate value for column";
105+
public static final int TEMP_TABLE_GENERATOR_MIN_SIZE = 1000;
105106

106107
protected static final Logger LOG = LogHelper.getLogger(SqlDialect.class, "Database warnings and errors");
107108
protected static final int MAX_VARCHAR_SIZE = 4000; //Any length over this will be set to nvarchar(max)/text
@@ -527,16 +528,33 @@ protected Set<String> getJdbcKeywords(SqlExecutor executor) throws SQLException,
527528

528529
private static final InClauseGenerator DEFAULT_GENERATOR = new ParameterMarkerInClauseGenerator();
529530

531+
public InClauseGenerator getDefaultInClauseGenerator()
532+
{
533+
return DEFAULT_GENERATOR;
534+
}
535+
536+
// Dialects that support temp-table IN clauses must override this method
537+
public InClauseGenerator getTempTableInClauseGenerator()
538+
{
539+
return null;
540+
}
541+
530542
// Most callers should use this method
531-
public SQLFragment appendInClauseSql(SQLFragment sql, @NotNull Collection<?> params)
543+
public final SQLFragment appendInClauseSql(SQLFragment sql, @NotNull Collection<?> params)
532544
{
533-
return appendInClauseSqlWithCustomInClauseGenerator(sql, params, null);
545+
return appendInClauseSqlWithCustomInClauseGenerator(sql, params, getTempTableInClauseGenerator());
534546
}
535547

536-
// Use only in cases where the default temp-table generator won't do, e.g., you need to apply a large IN clause in an external data source
537-
public SQLFragment appendInClauseSqlWithCustomInClauseGenerator(SQLFragment sql, @NotNull Collection<?> params, InClauseGenerator tempTableGenerator)
548+
// Call directly only in cases where the default temp-table generator won't do, e.g., you need to apply a large IN clause in an external data source
549+
public final SQLFragment appendInClauseSqlWithCustomInClauseGenerator(SQLFragment sql, @NotNull Collection<?> params, @Nullable InClauseGenerator largeInClauseGenerator)
538550
{
539-
return DEFAULT_GENERATOR.appendInClauseSql(sql, params);
551+
if (params.size() >= TEMP_TABLE_GENERATOR_MIN_SIZE && largeInClauseGenerator != null)
552+
{
553+
SQLFragment ret = largeInClauseGenerator.appendInClauseSql(sql, params);
554+
if (null != ret)
555+
return ret;
556+
}
557+
return getDefaultInClauseGenerator().appendInClauseSql(sql, params);
540558
}
541559

542560
public SQLFragment appendCaseInsensitiveLikeClause(SQLFragment sql, @NotNull String matchStr, @Nullable String wildcardPrefix, @Nullable String wildcardSuffix, char escapeChar)
@@ -1352,7 +1370,7 @@ public String getMessage()
13521370
}
13531371
}
13541372

1355-
public abstract String getAnalyzeCommandForTable(String tableName);
1373+
public abstract SQLFragment getAnalyzeCommandForTable(String tableName);
13561374

13571375
protected abstract String getSIDQuery();
13581376

@@ -1370,7 +1388,7 @@ public Integer getSPID(Connection conn) throws SQLException
13701388

13711389
public boolean updateStatistics(TableInfo table)
13721390
{
1373-
String sql = getAnalyzeCommandForTable(table.getSelectName());
1391+
SQLFragment sql = getAnalyzeCommandForTable(table.getSelectName());
13741392
if (sql != null)
13751393
{
13761394
new SqlExecutor(table.getSchema()).execute(sql);

api/src/org/labkey/api/migration/DatabaseMigrationService.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ static void addDataFilter(String filterName, List<DataFilter> dataFilters, Set<G
7474
String value = filterParts[1];
7575
FilterClause clause = CompareType.EQUAL.createFilterClause(new FieldKey(null, column), value);
7676
// If another container is already using this filter clause, then simply add this guid to that filter.
77-
// Otherwise, add a new domain filter to the list.
77+
// Otherwise, add a new data filter to the list.
7878
dataFilters.stream()
7979
.filter(df -> df.column().equals(column) && df.condition().equals(clause))
8080
.findFirst()

api/src/org/labkey/api/migration/DefaultMigrationSchemaHandler.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -275,7 +275,7 @@ protected InClauseGenerator getTempTableInClauseGenerator(DbScope sourceScope)
275275
}
276276

277277
private static final Set<AttachmentType> SEEN = new HashSet<>();
278-
private static final JobRunner ATTACHMENT_JOB_RUNNER = new JobRunner("Attachment JobRunner", 1);
278+
private static final JobRunner ATTACHMENT_JOB_RUNNER = new JobRunner("Attachment JobRunner", 1, () -> "Attachments");
279279

280280
// Copy all core.Documents rows that match the provided filter clause
281281
protected final void copyAttachments(DatabaseMigrationConfiguration configuration, DbSchema sourceSchema, FilterClause filterClause, AttachmentType... type)
@@ -309,12 +309,12 @@ public static void afterMigration() throws InterruptedException
309309
throw new ConfigurationException("These AttachmentTypes have not been seen: " + unseen.stream().map(type -> type.getClass().getSimpleName()).collect(Collectors.joining(", ")));
310310

311311
// Shut down the attachment JobRunner
312-
LOG.info("Waiting for core.Documents background transfer to complete");
312+
LOG.info("Waiting for attachments background transfer to complete");
313313
ATTACHMENT_JOB_RUNNER.shutdown();
314314
if (ATTACHMENT_JOB_RUNNER.awaitTermination(1, TimeUnit.HOURS))
315-
LOG.info("core.Documents background transfer is complete");
315+
LOG.info("Attachments background transfer is complete");
316316
else
317-
LOG.error("core.Documents background transfer did not complete after one hour! Giving up.");
317+
LOG.error("Attachments background transfer did not complete after one hour! Giving up.");
318318
}
319319

320320
@Override

api/src/org/labkey/api/util/JobRunner.java

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,11 @@
1616

1717
package org.labkey.api.util;
1818

19-
import org.apache.logging.log4j.LogManager;
2019
import org.apache.logging.log4j.Logger;
2120
import org.jetbrains.annotations.NotNull;
21+
import org.jetbrains.annotations.Nullable;
2222
import org.labkey.api.data.DbScope;
23+
import org.labkey.api.util.logging.LogHelper;
2324

2425
import java.util.HashMap;
2526
import java.util.Map;
@@ -30,6 +31,7 @@
3031
import java.util.concurrent.ThreadFactory;
3132
import java.util.concurrent.TimeUnit;
3233
import java.util.concurrent.atomic.AtomicInteger;
34+
import java.util.function.Supplier;
3335

3436
/**
3537
* This is a simple Executor, that can be used to implement more advanced services
@@ -48,7 +50,7 @@
4850
*/
4951
public class JobRunner implements Executor
5052
{
51-
static final Logger _log = LogManager.getLogger(JobRunner.class);
53+
static final Logger _log = LogHelper.getLogger(JobRunner.class, "JobRunner status and errors");
5254

5355
private static final JobRunner _defaultJobRunner = new JobRunner("Default", 1);
5456

@@ -58,14 +60,18 @@ public class JobRunner implements Executor
5860

5961
public JobRunner(String name, int max)
6062
{
61-
this(name, max, Thread.MIN_PRIORITY);
63+
this(name, max, null);
6264
}
6365

66+
public JobRunner(String name, int max, @Nullable Supplier<String> threadNameFactory)
67+
{
68+
this(name, max, threadNameFactory, Thread.MIN_PRIORITY);
69+
}
6470

65-
private JobRunner(String name, int max, int priority)
71+
private JobRunner(String name, int max, @Nullable Supplier<String> threadNameFactory, int priority)
6672
{
6773
_executor = new JobThreadPoolExecutor(max);
68-
_executor.setThreadFactory(new JobThreadFactory(priority));
74+
_executor.setThreadFactory(new JobThreadFactory(threadNameFactory, priority));
6975
ContextListener.addShutdownListener(new ShutdownListener()
7076
{
7177
@Override
@@ -263,25 +269,26 @@ protected void afterExecute(Runnable r, Throwable t)
263269
}
264270

265271

266-
static class JobThreadFactory implements ThreadFactory
272+
private static class JobThreadFactory implements ThreadFactory
267273
{
268-
static final AtomicInteger poolNumber = new AtomicInteger(1);
269-
final ThreadGroup group;
270-
final AtomicInteger threadNumber = new AtomicInteger(1);
271-
final String namePrefix;
272-
final int priority;
274+
private static final AtomicInteger POOL_NUMBER = new AtomicInteger(1);
275+
276+
private final ThreadGroup _group;
277+
private final AtomicInteger _threadNumber = new AtomicInteger(1);
278+
private final Supplier<String> _threadNameFactory;
279+
private final int priority;
273280

274-
JobThreadFactory(int priority)
281+
JobThreadFactory(@Nullable Supplier<String> threadNameFactory, int priority)
275282
{
276-
group = Thread.currentThread().getThreadGroup();
277-
namePrefix = "JobThread-" + poolNumber.getAndIncrement() + ".";
283+
_group = Thread.currentThread().getThreadGroup();
284+
_threadNameFactory = threadNameFactory != null ? threadNameFactory : () -> "JobThread-" + POOL_NUMBER.getAndIncrement() + "." + _threadNumber.getAndIncrement();
278285
this.priority = priority;
279286
}
280287

281288
@Override
282289
public Thread newThread(@NotNull Runnable r)
283290
{
284-
Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement(), 0);
291+
Thread t = new Thread(_group, r, _threadNameFactory.get(), 0);
285292
if (t.isDaemon())
286293
t.setDaemon(false);
287294
if (t.getPriority() != priority)

0 commit comments

Comments
 (0)