Skip to content

Commit 0139394

Browse files
authored
[JOOQ]: Support for unique modifying operations (#126)
2 parents 2b1a13e + 52a53b1 commit 0139394

File tree

74 files changed

+10627
-173
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

74 files changed

+10627
-173
lines changed

jooq-dialect/README.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,16 @@ Configure the JOOQ runtime to use the YDB dialect and JDBC driver:
6666
String url = "jdbc:ydb:grpc://localhost:2136/local";
6767
Connection conn = DriverManager.getConnection(url);
6868

69-
DSLContext dsl = new YdbDslContext(conn);
69+
YdbDSLContext dsl = YDB.using(conn);
70+
```
71+
72+
or
73+
74+
```java
75+
String url = "jdbc:ydb:grpc://localhost:2136/local";
76+
try (CloseableYdbDSLContext dsl = YDB.using(url)) {
77+
// ...
78+
}
7079
```
7180

7281
### XML config
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package org.jooq.impl;
2+
3+
import io.r2dbc.spi.ConnectionFactory;
4+
import org.jooq.ConnectionProvider;
5+
import org.jooq.tools.jdbc.JDBCUtils;
6+
7+
import java.sql.Connection;
8+
9+
public final class ConnectionUtils {
10+
private ConnectionUtils() {
11+
throw new UnsupportedOperationException();
12+
}
13+
14+
public static ConnectionProvider closeableProvider(Connection connection) {
15+
return new DefaultCloseableConnectionProvider(connection);
16+
}
17+
18+
public static void closeConnectionProvider(ConnectionProvider connectionProvider) {
19+
if (connectionProvider instanceof DefaultCloseableConnectionProvider dcp) {
20+
JDBCUtils.safeClose(dcp.connection);
21+
dcp.connection = null;
22+
}
23+
}
24+
25+
public static void closeConnectionFactory(ConnectionFactory connectionFactory) {
26+
if (connectionFactory instanceof DefaultConnectionFactory dcf) {
27+
if (dcf.finalize) {
28+
R2DBC.blockWrappingExceptions(dcf.connection.close());
29+
dcf.connection = null;
30+
}
31+
}
32+
}
33+
}
Lines changed: 338 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,338 @@
1+
package org.jooq.impl;
2+
3+
import org.jooq.*;
4+
import org.jooq.Record;
5+
import org.jooq.RenderContext.CastMode;
6+
7+
import java.util.AbstractMap;
8+
import java.util.AbstractSet;
9+
import java.util.ArrayList;
10+
import java.util.Collection;
11+
import java.util.Collections;
12+
import java.util.Iterator;
13+
import java.util.LinkedHashMap;
14+
import java.util.List;
15+
import java.util.Map;
16+
import java.util.Map.Entry;
17+
import java.util.Set;
18+
19+
import static org.jooq.impl.Keywords.K_VALUES;
20+
import static org.jooq.impl.Tools.BooleanDataKey.DATA_STORE_ASSIGNMENT;
21+
22+
23+
public final class FieldMapsForUpsertReplace extends AbstractQueryPart {
24+
private final Table<?> table;
25+
private final Map<Field<?>, Field<?>> empty;
26+
private final Map<Field<?>, List<Field<?>>> values;
27+
private int rows;
28+
private int nextRow = -1;
29+
30+
public FieldMapsForUpsertReplace(Table<?> table) {
31+
this.table = table;
32+
this.values = new LinkedHashMap<>();
33+
this.empty = new LinkedHashMap<>();
34+
}
35+
36+
public void clear() {
37+
empty.clear();
38+
values.clear();
39+
rows = 0;
40+
nextRow = -1;
41+
}
42+
43+
public boolean isEmpty() {
44+
return values.isEmpty();
45+
}
46+
47+
public void from(FieldMapsForUpsertReplace i) {
48+
empty.putAll(i.empty);
49+
50+
for (Entry<Field<?>, List<Field<?>>> e : i.values.entrySet()) {
51+
values.put(e.getKey(), new ArrayList<>(e.getValue()));
52+
}
53+
54+
rows = i.rows;
55+
nextRow = i.nextRow;
56+
}
57+
58+
@Override
59+
public void accept(Context<?> ctx) {
60+
toSQLValues(ctx);
61+
}
62+
63+
private void toSQLValues(Context<?> ctx) {
64+
ctx.formatSeparator()
65+
.visit(K_VALUES)
66+
.sql(' ');
67+
toSQL92Values(ctx);
68+
}
69+
70+
public static void toSQLUpsertSelect(Context<?> ctx, Select<?> select) {
71+
ctx.formatSeparator().visit(select);
72+
}
73+
74+
public Select<Record> upsertSelect() {
75+
Select<Record> select = null;
76+
77+
Map<Field<?>, List<Field<?>>> v = valuesFlattened();
78+
79+
for (int i = 0; i < rows; i++) {
80+
int row = i;
81+
Select<Record> iteration = DSL.select(Tools.map(
82+
v.entrySet(), e -> patchDefault0(e.getValue().get(row), e.getKey())
83+
));
84+
85+
if (select == null) {
86+
select = iteration;
87+
} else {
88+
select = select.unionAll(iteration);
89+
}
90+
}
91+
92+
return select;
93+
}
94+
95+
private void toSQL92Values(Context<?> ctx) {
96+
boolean indent = values.size() > 1;
97+
98+
CastMode previous = ctx.castMode();
99+
ctx.castMode(CastMode.NEVER);
100+
101+
for (int row = 0; row < rows; row++) {
102+
if (row > 0) {
103+
ctx.sql(", ");
104+
}
105+
106+
ctx.sql('(');
107+
108+
if (indent) {
109+
ctx.formatIndentStart();
110+
}
111+
112+
String separator = "";
113+
for (Entry<Field<?>, List<Field<?>>> e : valuesFlattened().entrySet()) {
114+
List<Field<?>> list = e.getValue();
115+
ctx.sql(separator);
116+
117+
if (indent) {
118+
ctx.formatNewLine();
119+
}
120+
121+
ctx.visit(patchDefault0(list.get(row), e.getKey()));
122+
separator = ", ";
123+
}
124+
125+
if (indent) {
126+
ctx.formatIndentEnd()
127+
.formatNewLine();
128+
}
129+
130+
ctx.sql(')');
131+
}
132+
133+
ctx.castMode(previous);
134+
}
135+
136+
private static Field<?> patchDefault0(Field<?> d, Field<?> f) {
137+
if (d instanceof Default) {
138+
return Tools.orElse(f.getDataType().default_(), () -> DSL.inline(null, f));
139+
}
140+
141+
return d;
142+
}
143+
144+
145+
public void addFields(Collection<?> fields) {
146+
if (rows == 0) {
147+
newRecord();
148+
}
149+
150+
initNextRow();
151+
152+
for (Object field : fields) {
153+
Field<?> f = Tools.tableField(table, field);
154+
Field<?> e = empty.computeIfAbsent(f, LazyVal::new);
155+
156+
values.computeIfAbsent(f, k -> rows > 0
157+
? new ArrayList<>(Collections.nCopies(rows, e))
158+
: new ArrayList<>());
159+
}
160+
}
161+
162+
@SuppressWarnings("unchecked")
163+
private <T> Field<T> set(Field<T> field, Field<T> value) {
164+
addFields(Collections.singletonList(field));
165+
return (Field<T>) values.get(field).set(rows - 1, value);
166+
}
167+
168+
public void set(Map<?, ?> map) {
169+
addFields(map.keySet());
170+
171+
for (Entry<?, ?> entry : map.entrySet()) {
172+
Object k = entry.getKey();
173+
Object v = entry.getValue();
174+
Field<?> field = Tools.tableField(table, k);
175+
values.get(field).set(rows - 1, Tools.field(v, field));
176+
}
177+
}
178+
179+
private void initNextRow() {
180+
if (rows == nextRow) {
181+
Iterator<List<Field<?>>> v = values.values().iterator();
182+
Iterator<Field<?>> e = empty.values().iterator();
183+
184+
while (v.hasNext() && e.hasNext()) {
185+
v.next().add(e.next());
186+
}
187+
188+
rows++;
189+
}
190+
}
191+
192+
public void newRecord() {
193+
if (nextRow < rows) {
194+
nextRow++;
195+
}
196+
}
197+
198+
private Map<Field<?>, Field<?>> map(final int index) {
199+
return new AbstractMap<>() {
200+
private transient Set<Entry<Field<?>, Field<?>>> entrySet;
201+
202+
@Override
203+
public Set<Entry<Field<?>, Field<?>>> entrySet() {
204+
if (entrySet == null) {
205+
entrySet = new EntrySet();
206+
}
207+
208+
return entrySet;
209+
}
210+
211+
@Override
212+
public boolean containsKey(Object key) {
213+
return values.containsKey(key);
214+
}
215+
216+
@Override
217+
public boolean containsValue(Object value) {
218+
return values.values().stream().anyMatch(list -> list.get(index).equals(value));
219+
}
220+
221+
@Override
222+
public Field<?> get(Object key) {
223+
List<Field<?>> list = values.get(key);
224+
return list == null ? null : list.get(index);
225+
}
226+
227+
@SuppressWarnings({"unchecked", "rawtypes"})
228+
@Override
229+
public Field<?> put(Field<?> key, Field<?> value) {
230+
return FieldMapsForUpsertReplace.this.set(key, (Field) value);
231+
}
232+
233+
@Override
234+
public Field<?> remove(Object key) {
235+
List<Field<?>> list = values.remove(key);
236+
return list == null ? null : list.get(index);
237+
}
238+
239+
@Override
240+
public Set<Field<?>> keySet() {
241+
return values.keySet();
242+
}
243+
244+
private final class EntrySet extends AbstractSet<Entry<Field<?>, Field<?>>> {
245+
@Override
246+
public int size() {
247+
return values.size();
248+
}
249+
250+
@Override
251+
public void clear() {
252+
values.clear();
253+
}
254+
255+
@Override
256+
public Iterator<Entry<Field<?>, Field<?>>> iterator() {
257+
return new Iterator<>() {
258+
final Iterator<Entry<Field<?>, List<Field<?>>>> delegate = values.entrySet().iterator();
259+
260+
@Override
261+
public boolean hasNext() {
262+
return delegate.hasNext();
263+
}
264+
265+
@Override
266+
public Entry<Field<?>, Field<?>> next() {
267+
Entry<Field<?>, List<Field<?>>> entry = delegate.next();
268+
return new SimpleImmutableEntry<>(entry.getKey(), entry.getValue().get(index));
269+
}
270+
271+
@Override
272+
public void remove() {
273+
delegate.remove();
274+
}
275+
};
276+
}
277+
}
278+
};
279+
}
280+
281+
public Map<Field<?>, Field<?>> lastMap() {
282+
return map(rows - 1);
283+
}
284+
285+
public boolean isExecutable() {
286+
return rows > 0;
287+
}
288+
289+
public Set<Field<?>> toSQLReferenceKeys(Context<?> ctx) {
290+
if (values.keySet().stream().allMatch(AbstractStoreQuery.UnknownField.class::isInstance)) {
291+
return Collections.emptySet();
292+
}
293+
294+
Set<Field<?>> fields = keysFlattened();
295+
296+
if (!fields.isEmpty()) {
297+
ctx.data(DATA_STORE_ASSIGNMENT, true, c -> c.sql(" (").visit(QueryPartCollectionView.wrap(fields).qualify(false)).sql(')'));
298+
}
299+
300+
return fields;
301+
}
302+
303+
public Set<Field<?>> keysFlattened() {
304+
return valuesFlattened().keySet();
305+
}
306+
307+
private Map<Field<?>, List<Field<?>>> valuesFlattened() {
308+
Map<Field<?>, List<Field<?>>> result = new LinkedHashMap<>();
309+
310+
for (Entry<Field<?>, List<Field<?>>> entry : values.entrySet()) {
311+
Field<?> key = entry.getKey();
312+
DataType<?> keyType = key.getDataType();
313+
List<Field<?>> value = entry.getValue();
314+
315+
if (keyType.isEmbeddable()) {
316+
List<Iterator<? extends Field<?>>> valueFlattened = new ArrayList<>(value.size());
317+
318+
for (Field<?> v : value) {
319+
valueFlattened.add(Tools.flatten(v).iterator());
320+
}
321+
322+
for (Field<?> k : Tools.flatten(key)) {
323+
List<Field<?>> list = new ArrayList<>(value.size());
324+
325+
for (Iterator<? extends Field<?>> v : valueFlattened) {
326+
list.add(v.hasNext() ? v.next() : null);
327+
}
328+
329+
result.put(k, list);
330+
}
331+
} else {
332+
result.put(key, value);
333+
}
334+
}
335+
336+
return result;
337+
}
338+
}

0 commit comments

Comments
 (0)