1. Project Clover database Fri Jul 17 2026 06:10:26 UTC
  2. Package guru.mikelue.foxglove.jdbc

File BatchWorker.java

 

Coverage histogram

../../../../img/srcFileCovDistChart8.png
70% of files have more coverage

Code metrics

8
47
11
5
217
163
19
0.4
4.27
2.2
1.73

Classes

Class Line # Actions
BatchWorker 40 4 0% 2 2
0.7142857371.4%
WorkerAssistance 68 17 0% 5 0
1.0100%
PluralBatchWorker 133 16 0% 7 0
1.0100%
SingleBatchWorker 181 10 0% 5 13
0.00%
CustomStatementSetterProvider 217 0 - 0 0
-1.0 -
 

Contributing tests

This file is covered by 49 tests. .

Source view

1    package guru.mikelue.foxglove.jdbc;
2   
3    import org.slf4j.Logger;
4    import org.slf4j.LoggerFactory;
5   
6    import java.sql.PreparedStatement;
7    import java.sql.ResultSet;
8    import java.sql.SQLException;
9    import java.util.HashMap;
10    import java.util.List;
11    import java.util.Map;
12    import java.util.Optional;
13    import java.util.function.Consumer;
14    import java.util.function.Function;
15    import java.util.regex.Pattern;
16   
17    import guru.mikelue.foxglove.ColumnMeta;
18    import guru.mikelue.foxglove.TupleAccessor;
19    import guru.mikelue.foxglove.setting.DataSettingInfo;
20   
21    /**
22    * Since {@link PreparedStatement#getGeneratedKeys()} behaves differently
23    * across different databases, this interface is used to implement different strategy.
24    *
25    * <ul>
26    * <li>Derby, SQLite - Only get generated value of latest inserted row</li>
27    * <li>MSSQL - Only able to get value by {@link PreparedStatement#executeUpdate()}</li>
28    * <li>Otherwise, gets the generated values by {@link PreparedStatement#executeBatch}</li>
29    * </ul>
30    *
31    * Microsoft JDBC Driver 11.2 for SQL Server
32    * Apache Derby Embedded JDBC Driver
33    * SQLite JDBC
34    *
35    * HSQL Database Engine Driver
36    * PostgreSQL JDBC Driver
37    * MySQL Connector/J
38    * Oracle JDBC driver
39    */
 
40    interface BatchWorker {
41    static Pattern DRIVER_FOR_SINGLE_WORKER = Pattern.compile(
42    "(?i).*(derby|sqlite|microsoft).*"
43    );
44   
 
45  109 toggle static BatchWorker newInstance(
46    String dbDriverName,
47    PreparedStatement stmt, String[] askedGeneratedColumns,
48    Consumer<List<TupleAccessor>> generatedValuesConsumer,
49    int batchSize,
50    DataSettingInfo dataSettingInfo
51    ) {
52  109 var WorkerAssistance = new WorkerAssistance(
53    stmt, new GeneratedValueLoader(askedGeneratedColumns),
54    generatedValuesConsumer, dataSettingInfo
55    );
56   
57  109 if (DRIVER_FOR_SINGLE_WORKER.matcher(dbDriverName).matches()) {
58  0 return new SingleBatchWorker(stmt, WorkerAssistance);
59    }
60   
61  109 return new PluralBatchWorker(stmt, batchSize, WorkerAssistance);
62    }
63   
64    void addBatch(Map<ColumnMeta, Object> paramSet) throws SQLException;
65    void executeBatch() throws SQLException;
66    }
67   
 
68    class WorkerAssistance {
69    private final PreparedStatement stmt;
70    private final Consumer<List<TupleAccessor>> generatedValuesConsumer;
71    private final GeneratedValueLoader generatedValueLoader;
72    private final Map<SetParameterIndex, CustomStatementSetter<?>> paramSetterCache = new HashMap<>(32);
73    private final CustomStatementSetterProvider paramSetterProvider;
74   
 
75  110 toggle WorkerAssistance(
76    PreparedStatement stmt,
77    GeneratedValueLoader generatedValueLoader,
78    Consumer<List<TupleAccessor>> generatedValuesConsumer,
79    DataSettingInfo dataSettingInfo
80    ) {
81  110 this.stmt = stmt;
82  110 this.generatedValuesConsumer = generatedValuesConsumer;
83  110 this.generatedValueLoader = generatedValueLoader;
84  110 this.paramSetterProvider = dataSettingInfo::getStatementSetter;
85    }
86   
 
87  1374 toggle final protected void setParams(Map<ColumnMeta, Object> paramSet) throws SQLException
88    {
89  1374 setParams(stmt, paramSet, paramSetterCache);
90    }
91   
 
92  131 toggle final protected void consumeValues(ResultSet rs) throws SQLException
93    {
94  131 generatedValuesConsumer.accept(
95    generatedValueLoader.toTuples(rs)
96    );
97    }
98   
 
99  1374 toggle @SuppressWarnings("unchecked")
100    private void setParams(
101    PreparedStatement stmt, Map<ColumnMeta, Object> paramSet,
102    Map<SetParameterIndex, CustomStatementSetter<?>> setterCache
103    ) throws SQLException {
104  1374 var paramIndex = 1;
105   
106  1374 for (ColumnMeta columnMeta: paramSet.keySet()) {
107  10173 var jdbcType = columnMeta.jdbcType();
108  10173 var value = paramSet.get(columnMeta);
109   
110  10173 CustomStatementSetter<Object> setParamFunc;
111   
112  10173 if (value == null) {
113  102 setParamFunc = (localStmt, localIndex, meta, localValue) ->
114    stmt.setNull(localIndex, jdbcType.getVendorTypeNumber());
115    } else {
116  10071 setParamFunc = (CustomStatementSetter<Object>)setterCache.computeIfAbsent(
117    new SetParameterIndex(columnMeta, value.getClass()),
118    index -> paramSetterProvider.apply(columnMeta)
119    .orElseGet(
120    () -> {
121  807 return ParameterSetterFactory.smartSetterImpl(index);
122    }
123    )
124    );
125    }
126   
127  10173 setParamFunc.setParameter(stmt, paramIndex, columnMeta, value);
128  10173 paramIndex++;
129    }
130    }
131    }
132   
 
133    class PluralBatchWorker implements BatchWorker {
134    private Logger logger = LoggerFactory.getLogger(PluralBatchWorker.class);
135   
136    private final WorkerAssistance assistance;
137    private final PreparedStatement stmt;
138    private final int batchSize;
139    private int unExecutedNumberOfRows = 0;
140   
 
141  109 toggle PluralBatchWorker(
142    PreparedStatement stmt, int batchSize,
143    WorkerAssistance assistance
144    ) {
145  109 this.stmt = stmt;
146  109 this.batchSize = batchSize;
147  109 this.assistance = assistance;
148    }
149   
 
150  1373 toggle @Override
151    public void addBatch(Map<ColumnMeta, Object> paramSet) throws SQLException
152    {
153  1373 assistance.setParams(paramSet);
154   
155  1373 stmt.addBatch();
156  1373 unExecutedNumberOfRows++;
157   
158  1373 if (unExecutedNumberOfRows >= batchSize) {
159  28 executeBatch();
160    }
161    }
162   
 
163  137 toggle @Override
164    public void executeBatch() throws SQLException
165    {
166  137 if (unExecutedNumberOfRows == 0) {
167  6 return;
168    }
169   
170  131 logger.debug("Executing for [{}] statements of batch[{}]", unExecutedNumberOfRows, batchSize);
171   
172  131 stmt.executeBatch();
173  131 unExecutedNumberOfRows = 0;
174   
175  131 try (var rs = stmt.getGeneratedKeys()) {
176  131 assistance.consumeValues(rs);
177    }
178    }
179    }
180   
 
181    class SingleBatchWorker implements BatchWorker {
182    private Logger logger = LoggerFactory.getLogger(SingleBatchWorker.class);
183   
184    private final WorkerAssistance assistance;
185    private final PreparedStatement stmt;
186    private int counter = 0;
187   
 
188  0 toggle SingleBatchWorker(
189    PreparedStatement stmt, WorkerAssistance assistance
190    ) {
191  0 this.stmt = stmt;
192  0 this.assistance = assistance;
193    }
194   
 
195  0 toggle @Override
196    public void addBatch(Map<ColumnMeta, Object> paramSet) throws SQLException
197    {
198  0 assistance.setParams(paramSet);
199   
200  0 stmt.executeUpdate();
201  0 counter++;
202   
203  0 try (var rs = stmt.getGeneratedKeys()) {
204  0 assistance.consumeValues(rs);
205    }
206    }
207   
 
208  0 toggle @Override
209    public void executeBatch() throws SQLException
210    {
211  0 logger.debug("Have executed [{}] statements individually", counter);
212  0 counter = 0;
213    }
214    }
215   
216    @FunctionalInterface
 
217    interface CustomStatementSetterProvider extends Function<ColumnMeta, Optional<CustomStatementSetter<?>>> {}