Skip to content

Commit c4bae53

Browse files
authored
CachedResultSetBuilder replaces CachedResultSets (#7325)
1 parent 897ffed commit c4bae53

File tree

9 files changed

+222
-147
lines changed

9 files changed

+222
-147
lines changed

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

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,6 @@
5050
* A partial, base implementation of {@link org.labkey.api.data.Selector}. This class manipulates result sets but doesn't
5151
* generate them. Subclasses include ExecutingSelector (which executes SQL to generate a result set) and ResultSetSelector,
5252
* which takes an externally generated ResultSet (e.g., from JDBC metadata calls) and allows Selector operations on it.
53-
* User: adam
54-
* Date: 12/11/12
5553
*/
5654

5755
public abstract class BaseSelector<SELECTOR extends BaseSelector<?>> extends JdbcCommand<SELECTOR> implements Selector
Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
/*
2+
* Copyright (c) 2013-2018 LabKey Corporation
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.labkey.api.data;
17+
18+
import org.labkey.api.collections.ResultSetRowMapFactory;
19+
import org.labkey.api.collections.RowMap;
20+
21+
import java.sql.ResultSet;
22+
import java.sql.ResultSetMetaData;
23+
import java.sql.SQLException;
24+
import java.util.ArrayList;
25+
import java.util.Collection;
26+
import java.util.List;
27+
import java.util.Map;
28+
29+
/**
30+
* Builder for CachedResultSets, given a {@code ResultSet} or a {@code List<Map<String, Object>>}
31+
*/
32+
public abstract class CachedResultSetBuilder<C extends CachedResultSetBuilder<C>>
33+
{
34+
protected boolean _requireClose = true;
35+
protected StackTraceElement[] _stackTrace = null;
36+
37+
public static FromResultSet create(ResultSet rs)
38+
{
39+
return new FromResultSet(rs);
40+
}
41+
42+
public static FromListOfMaps create(List<Map<String, Object>> maps)
43+
{
44+
return create(maps, maps.get(0).keySet());
45+
}
46+
47+
/**
48+
* Create CachedResultSetBuilder from a list of maps and collection of column names. For the most flexibility, the
49+
* maps may need to be case-insensitive. How do you tell? If the maps have data and the keys match the columnNames,
50+
* but the ResultSet rowMap values are all null.
51+
* @param maps List of row data, possibly case-insensitive maps
52+
* @param columnNames Collection of column names
53+
*
54+
* TODO: A case-insensitive option for the builder, but there may be performance impact for very large result sets
55+
* if the implementation were simply to wrap each incoming map with CaseInsensitiveHashMap. For now, onus is on the
56+
* caller to provide case insensitive maps when necessary.
57+
*/
58+
public static FromListOfMaps create(List<Map<String, Object>> maps, Collection<String> columnNames)
59+
{
60+
return new FromListOfMaps(maps, columnNames);
61+
}
62+
63+
public abstract C getThis();
64+
65+
public C setRequireClose(boolean requireClose)
66+
{
67+
_requireClose = requireClose;
68+
return getThis();
69+
}
70+
71+
public C setStackTrace(StackTraceElement[] stackTrace)
72+
{
73+
_stackTrace = stackTrace;
74+
return getThis();
75+
}
76+
77+
public final static class FromResultSet extends CachedResultSetBuilder<FromResultSet>
78+
{
79+
private final ResultSet _rs;
80+
81+
private int _maxRows = Table.ALL_ROWS;
82+
private QueryLogging _queryLogging = QueryLogging.emptyQueryLogging();
83+
84+
private FromResultSet(ResultSet rs)
85+
{
86+
_rs = rs;
87+
}
88+
89+
@Override
90+
public FromResultSet getThis()
91+
{
92+
return this;
93+
}
94+
95+
public FromResultSet setMaxRows(int maxRows)
96+
{
97+
_maxRows = maxRows;
98+
return this;
99+
}
100+
101+
public FromResultSet setQueryLogging(QueryLogging queryLogging)
102+
{
103+
_queryLogging = queryLogging;
104+
return this;
105+
}
106+
107+
public CachedResultSet build() throws SQLException
108+
{
109+
try (ResultSet rs = new LoggingResultSetWrapper(_rs, _queryLogging)) // TODO: avoid if we're passed a read-only and empty one??
110+
{
111+
// Snowflake auto-closes metadata after reading the last row, so cache that metadata first
112+
ResultSetMetaData md = new CachedResultSetMetaData(rs.getMetaData());
113+
114+
ArrayList<RowMap<Object>> list = new ArrayList<>();
115+
116+
if (_maxRows == Table.ALL_ROWS)
117+
_maxRows = Integer.MAX_VALUE;
118+
119+
ResultSetRowMapFactory factory = ResultSetRowMapFactory.create(rs);
120+
121+
// Note: we check in this order to avoid consuming the "extra" row used to detect complete vs. not
122+
while (list.size() < _maxRows && rs.next())
123+
list.add(factory.getRowMap(rs));
124+
125+
// If we have another row, then we're not complete
126+
boolean isComplete = !rs.next();
127+
128+
return new CachedResultSet(md, list, isComplete, _requireClose, _stackTrace);
129+
}
130+
}
131+
}
132+
133+
public final static class FromListOfMaps extends CachedResultSetBuilder<FromListOfMaps>
134+
{
135+
private final List<Map<String, Object>> _maps;
136+
private final Collection<String> _columnNames;
137+
138+
private ResultSetMetaData _md = null;
139+
private boolean _isComplete = true;
140+
141+
private FromListOfMaps(List<Map<String, Object>> maps, Collection<String> columnNames)
142+
{
143+
_maps = maps;
144+
_columnNames = columnNames;
145+
}
146+
147+
@Override
148+
public FromListOfMaps getThis()
149+
{
150+
return this;
151+
}
152+
153+
public FromListOfMaps setMetaData(ResultSetMetaData md)
154+
{
155+
_md = md;
156+
return this;
157+
}
158+
159+
public FromListOfMaps setComplete(boolean complete)
160+
{
161+
_isComplete = complete;
162+
return this;
163+
}
164+
165+
public CachedResultSet build()
166+
{
167+
if (_md == null)
168+
_md = createMetaData(_columnNames);
169+
170+
return new CachedResultSet(_md, convertToRowMaps(_md, _maps), _isComplete, _requireClose, _stackTrace);
171+
}
172+
}
173+
174+
private static ResultSetMetaData createMetaData(Collection<String> columnNames)
175+
{
176+
ResultSetMetaDataImpl md = new ResultSetMetaDataImpl(columnNames.size());
177+
for (String columnName : columnNames)
178+
{
179+
ResultSetMetaDataImpl.ColumnMetaData col = new ResultSetMetaDataImpl.ColumnMetaData();
180+
col.columnName = columnName;
181+
col.columnLabel = columnName;
182+
md.addColumn(col);
183+
}
184+
185+
return md;
186+
}
187+
188+
private static ArrayList<RowMap<Object>> convertToRowMaps(ResultSetMetaData md, List<Map<String, Object>> maps)
189+
{
190+
ArrayList<RowMap<Object>> list = new ArrayList<>();
191+
192+
ResultSetRowMapFactory factory;
193+
try
194+
{
195+
factory = ResultSetRowMapFactory.create(md);
196+
}
197+
catch (SQLException e)
198+
{
199+
throw new RuntimeSQLException(e);
200+
}
201+
202+
for (Map<String, Object> map : maps)
203+
{
204+
list.add(factory.getRowMap(map));
205+
}
206+
207+
return list;
208+
}
209+
}

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

Lines changed: 0 additions & 132 deletions
This file was deleted.

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1878,7 +1878,7 @@ private static void detectUnexpectedConnections(Connection conn, LabKeyDataSourc
18781878
stmt.setString(1, databaseName);
18791879
stmt.setString(2, applicationName);
18801880

1881-
try (CachedResultSet rs = CachedResultSets.create(stmt.executeQuery(), true, true, 1000))
1881+
try (CachedResultSet rs = CachedResultSetBuilder.create(stmt.executeQuery()).setMaxRows(1000).build())
18821882
{
18831883
count = rs.getSize();
18841884
if (count != 0)

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ protected TableResultSet wrapResultSet(ResultSet rs, Connection conn, boolean ca
103103
{
104104
if (cache)
105105
{
106-
return CachedResultSets.create(rs, true, requireClose, Table.ALL_ROWS);
106+
return CachedResultSetBuilder.create(rs).setRequireClose(requireClose).build();
107107
}
108108
else
109109
{

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ private <K> void test(DbScope scope, ResultSet rs, Class<K> clazz) throws SQLExc
7979
assertEquals("Non-scrollable ResultSet can't be used with ScrollToTop", e.getMessage());
8080
}
8181

82-
rs = CachedResultSets.create(rs, true, true, Table.ALL_ROWS, null, QueryLogging.emptyQueryLogging());
82+
rs = CachedResultSetBuilder.create(rs).build();
8383
}
8484

8585
ResultSetSelector selector = new ResultSetSelector(scope, rs);

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,7 @@ protected TableResultSet wrapResultSet(ResultSet rs, Connection conn, boolean ca
211211
if (cache)
212212
{
213213
// Cache ResultSet and meta data
214-
return CachedResultSets.create(rs, true, requireClose, _maxRows, _loggingStacktrace, getQueryLogging());
214+
return CachedResultSetBuilder.create(rs).setRequireClose(requireClose).setMaxRows(_maxRows).setStackTrace(_loggingStacktrace).setQueryLogging(getQueryLogging()).build();
215215
}
216216
else
217217
{

0 commit comments

Comments
 (0)