Skip to content

Commit 6ffa1a5

Browse files
committed
Soft automatic schema reload
Now client keeps actual schema metadata and sends schemaId header to be checked against current Tarantool schema version. If client version mismatches DB version client does schema reloading in the background. Client operation interface was reworked in scope of support not only number identifiers for spaces and indexes but also their string names. This also includes set of request builders that can be used as a public API to construct requests. The main idea here is to provide more natural DSL-like approach to build operations instead of current abstract types like List<?> or List<Object>. Closes: #7, #137 Affects: #212
1 parent 08e37a2 commit 6ffa1a5

File tree

58 files changed

+3420
-437
lines changed

Some content is hidden

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

58 files changed

+3420
-437
lines changed

README.md

+44
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,50 @@ all the results, you could override this:
130130
protected void complete(TarantoolPacket packet, TarantoolOp<?> future);
131131
```
132132

133+
## String space/index resolution
134+
135+
Each operation that requires space or index to be executed, can work with
136+
number ID as well as string name of a space or an index.
137+
Assume, we have `my_space` space with space ID `512` and its primary index
138+
`primary` with index ID `0`. Then, for instance, `select` operations can be
139+
performed as the following:
140+
141+
```java
142+
client.syncOps().select(512, 0, Collections.singletonList(1), 0, 1, Iterator.EQ);
143+
// or using more convenient way
144+
client.syncOps().select("my_space", "primary", Collections.singletonList(1), 0, 1, Iterator.EQ);
145+
```
146+
147+
Because _iproto_ has not yet supported string spaces and indexes, a client caches current server
148+
schema in memory. The client relies on protocol SCHEMA_ID and sends each request with respect to
149+
cached schema version. The schema is used primarily to resolve string names of spaces or indexes
150+
against its integer IDs.
151+
152+
### Schema update
153+
154+
1. Just after a (re-)connection to the Tarantool instance.
155+
The client cannot guarantee that new instance is the same and has same schema,
156+
thus, the client drops the cached schema and fetches new one;
157+
2. receiving a schema version error as a response to our request.
158+
It's possible some request can be rejected by server because of schema
159+
mismatching between client and server. In this case the schema will be
160+
reloaded and the refused request will be resent using the updated schema
161+
version;
162+
3. sending a DDL request and receiving a new version in a response;
163+
4. sending a request against a non-existent space/index name.
164+
The client cannot exactly know whether name was not found because of
165+
it does not exist or it has not the latest schema version.
166+
167+
### Schema support caveats
168+
169+
1. Each schema reloading requires at least two extra requests to fetch spaces and
170+
indexes metadata respectively. There is also a ping request followed by reloading
171+
of the schema to check whether the client has outdated version (see point 4 in
172+
Schema update section).
173+
2. In some circumstance, requests can be rejected several times until both client's
174+
and server's versions matches. It may take significant amount of time or even be
175+
a cause of request timeout.
176+
133177
## Spring NamedParameterJdbcTemplate usage example
134178

135179
The JDBC driver uses `TarantoolClient` implementation to provide a communication with server.
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,158 @@
11
package org.tarantool;
22

3+
import static org.tarantool.dsl.Requests.callRequest;
4+
import static org.tarantool.dsl.Requests.deleteRequest;
5+
import static org.tarantool.dsl.Requests.evalRequest;
6+
import static org.tarantool.dsl.Requests.insertRequest;
7+
import static org.tarantool.dsl.Requests.pingRequest;
8+
import static org.tarantool.dsl.Requests.replaceRequest;
9+
import static org.tarantool.dsl.Requests.selectRequest;
10+
import static org.tarantool.dsl.Requests.updateRequest;
11+
import static org.tarantool.dsl.Requests.upsertRequest;
312

4-
public abstract class AbstractTarantoolOps<Space, Tuple, Operation, Result>
5-
implements TarantoolClientOps<Space, Tuple, Operation, Result> {
13+
import org.tarantool.dsl.Operation;
14+
import org.tarantool.dsl.TarantoolRequestConvertible;
15+
import org.tarantool.schema.TarantoolSchemaMeta;
16+
17+
import java.util.Arrays;
18+
import java.util.List;
19+
20+
public abstract class AbstractTarantoolOps<Result>
21+
implements TarantoolClientOps<List<?>, Object, Result> {
622

723
private Code callCode = Code.CALL;
824

9-
protected abstract Result exec(Code code, Object... args);
25+
protected abstract Result exec(TarantoolRequest request);
26+
27+
protected abstract TarantoolSchemaMeta getSchemaMeta();
28+
29+
public Result select(Integer space, Integer index, List<?> key, int offset, int limit, Iterator iterator) {
30+
return execute(
31+
selectRequest(space, index)
32+
.key(key)
33+
.offset(offset).limit(limit)
34+
.iterator(iterator)
35+
);
36+
}
37+
38+
@Override
39+
public Result select(String space, String index, List<?> key, int offset, int limit, Iterator iterator) {
40+
return execute(
41+
selectRequest(space, index)
42+
.key(key)
43+
.offset(offset).limit(limit)
44+
.iterator(iterator)
45+
);
46+
}
1047

11-
public Result select(Space space, Space index, Tuple key, int offset, int limit, Iterator iterator) {
12-
return select(space, index, key, offset, limit, iterator.getValue());
48+
@Override
49+
public Result select(Integer space, Integer index, List<?> key, int offset, int limit, int iterator) {
50+
return execute(
51+
selectRequest(space, index)
52+
.key(key)
53+
.offset(offset).limit(limit)
54+
.iterator(iterator)
55+
);
1356
}
1457

15-
public Result select(Space space, Space index, Tuple key, int offset, int limit, int iterator) {
16-
return exec(
17-
Code.SELECT,
18-
Key.SPACE, space,
19-
Key.INDEX, index,
20-
Key.KEY, key,
21-
Key.ITERATOR, iterator,
22-
Key.LIMIT, limit,
23-
Key.OFFSET, offset
58+
@Override
59+
public Result select(String space, String index, List<?> key, int offset, int limit, int iterator) {
60+
return execute(
61+
selectRequest(space, index)
62+
.key(key)
63+
.offset(offset).limit(limit)
64+
.iterator(iterator)
2465
);
2566
}
2667

27-
public Result insert(Space space, Tuple tuple) {
28-
return exec(Code.INSERT, Key.SPACE, space, Key.TUPLE, tuple);
68+
@Override
69+
public Result insert(Integer space, List<?> tuple) {
70+
return execute(insertRequest(space, tuple));
71+
}
72+
73+
@Override
74+
public Result insert(String space, List<?> tuple) {
75+
return execute(insertRequest(space, tuple));
2976
}
3077

31-
public Result replace(Space space, Tuple tuple) {
32-
return exec(Code.REPLACE, Key.SPACE, space, Key.TUPLE, tuple);
78+
@Override
79+
public Result replace(Integer space, List<?> tuple) {
80+
return execute(replaceRequest(space, tuple));
3381
}
3482

35-
public Result update(Space space, Tuple key, Operation... args) {
36-
return exec(Code.UPDATE, Key.SPACE, space, Key.KEY, key, Key.TUPLE, args);
83+
@Override
84+
public Result replace(String space, List<?> tuple) {
85+
return execute(replaceRequest(space, tuple));
3786
}
3887

39-
public Result upsert(Space space, Tuple key, Tuple def, Operation... args) {
40-
return exec(Code.UPSERT, Key.SPACE, space, Key.KEY, key, Key.TUPLE, def, Key.UPSERT_OPS, args);
88+
@Override
89+
public Result update(Integer space, List<?> key, Object... operations) {
90+
Operation[] ops = Arrays.stream(operations)
91+
.map(Operation::fromArray)
92+
.toArray(org.tarantool.dsl.Operation[]::new);
93+
return execute(updateRequest(space, key, ops));
4194
}
4295

43-
public Result delete(Space space, Tuple key) {
44-
return exec(Code.DELETE, Key.SPACE, space, Key.KEY, key);
96+
@Override
97+
public Result update(String space, List<?> key, Object... operations) {
98+
Operation[] ops = Arrays.stream(operations)
99+
.map(Operation::fromArray)
100+
.toArray(org.tarantool.dsl.Operation[]::new);
101+
return execute(updateRequest(space, key, ops));
45102
}
46103

104+
@Override
105+
public Result upsert(Integer space, List<?> key, List<?> defTuple, Object... operations) {
106+
Operation[] ops = Arrays.stream(operations)
107+
.map(Operation::fromArray)
108+
.toArray(Operation[]::new);
109+
return execute(upsertRequest(space, key, defTuple, ops));
110+
}
111+
112+
@Override
113+
public Result upsert(String space, List<?> key, List<?> defTuple, Object... operations) {
114+
Operation[] ops = Arrays.stream(operations)
115+
.map(Operation::fromArray)
116+
.toArray(Operation[]::new);
117+
return execute(upsertRequest(space, key, defTuple, ops));
118+
}
119+
120+
@Override
121+
public Result delete(Integer space, List<?> key) {
122+
return execute(deleteRequest(space, key));
123+
}
124+
125+
@Override
126+
public Result delete(String space, List<?> key) {
127+
return execute(deleteRequest(space, key));
128+
}
129+
130+
@Override
47131
public Result call(String function, Object... args) {
48-
return exec(callCode, Key.FUNCTION, function, Key.TUPLE, args);
132+
return execute(
133+
callRequest(function)
134+
.arguments(args)
135+
.useCall16(callCode == Code.OLD_CALL)
136+
);
49137
}
50138

139+
@Override
51140
public Result eval(String expression, Object... args) {
52-
return exec(Code.EVAL, Key.EXPRESSION, expression, Key.TUPLE, args);
141+
return execute(evalRequest(expression).arguments(args));
53142
}
54143

144+
@Override
55145
public void ping() {
56-
exec(Code.PING);
146+
execute(pingRequest());
147+
}
148+
149+
@Override
150+
public Result execute(TarantoolRequestConvertible requestSpec) {
151+
return exec(requestSpec.toTarantoolRequest(getSchemaMeta()));
57152
}
58153

59154
public void setCallCode(Code callCode) {
60155
this.callCode = callCode;
61156
}
157+
62158
}

src/main/java/org/tarantool/Iterator.java

+10
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package org.tarantool;
22

3+
import java.util.Arrays;
4+
35
// Iterator info was taken from here https://github.com/tarantool/tarantool/blob/f66584c3bcdffe61d6d99a4868a9b72d62338a11/src/box/iterator_type.h#L62
46
public enum Iterator {
57
EQ(0), // key == x ASC order
@@ -24,4 +26,12 @@ public enum Iterator {
2426
public int getValue() {
2527
return value;
2628
}
29+
30+
public static Iterator valueOf(int value) {
31+
return Arrays.stream(Iterator.values())
32+
.filter(v -> value == v.getValue())
33+
.findFirst()
34+
.orElseThrow(IllegalArgumentException::new);
35+
}
36+
2737
}

src/main/java/org/tarantool/MsgPackLite.java

+7
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package org.tarantool;
22

3+
import org.tarantool.util.TupleTwo;
4+
35
import java.io.DataInputStream;
46
import java.io.DataOutputStream;
57
import java.io.IOException;
@@ -226,6 +228,11 @@ public void pack(Object item, OutputStream os) throws IOException {
226228
pack(kvp.getKey(), out);
227229
pack(kvp.getValue(), out);
228230
}
231+
} else if (item instanceof TupleTwo) {
232+
TupleTwo<?, ?> tuple = (TupleTwo<?, ?>) item;
233+
out.write(1 | MP_FIXMAP);
234+
pack(tuple.getFirst(), out);
235+
pack(tuple.getSecond(), out);
229236
} else {
230237
throw new IllegalArgumentException("Cannot msgpack object of type " + item.getClass().getCanonicalName());
231238
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package org.tarantool;
2+
3+
import java.util.Objects;
4+
import java.util.function.Supplier;
5+
6+
/**
7+
* Request argument factory.
8+
*
9+
* @see TarantoolRequestArgument
10+
*/
11+
public class RequestArguments {
12+
13+
private RequestArguments() {
14+
}
15+
16+
public static TarantoolRequestArgument value(Object value) {
17+
return new SimpleArgument(value);
18+
}
19+
20+
public static TarantoolRequestArgument cacheLookupValue(Supplier<Object> supplier) {
21+
return new LookupArgument(supplier);
22+
}
23+
24+
/**
25+
* Simple wrapper that holds the original value.
26+
*/
27+
private static class SimpleArgument implements TarantoolRequestArgument {
28+
29+
private Object value;
30+
31+
SimpleArgument(Object value) {
32+
Objects.requireNonNull(value);
33+
this.value = value;
34+
}
35+
36+
@Override
37+
public boolean isSerializable() {
38+
return true;
39+
}
40+
41+
@Override
42+
public Object getValue() {
43+
return value;
44+
}
45+
46+
}
47+
48+
/**
49+
* Wrapper that evaluates the value each time
50+
* it is requested.
51+
* <p>
52+
* It works like a function, where {@code argument = f(key)}.
53+
*/
54+
private static class LookupArgument implements TarantoolRequestArgument {
55+
56+
Supplier<Object> lookup;
57+
58+
LookupArgument(Supplier<Object> lookup) {
59+
this.lookup = Objects.requireNonNull(lookup);
60+
}
61+
62+
@Override
63+
public boolean isSerializable() {
64+
try {
65+
lookup.get();
66+
} catch (Exception ignored) {
67+
return false;
68+
}
69+
return true;
70+
}
71+
72+
@Override
73+
public synchronized Object getValue() {
74+
return lookup.get();
75+
}
76+
77+
}
78+
79+
}

src/main/java/org/tarantool/TarantoolBase.java

+1-12
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,9 @@
66
import java.io.IOException;
77
import java.net.Socket;
88
import java.nio.channels.SocketChannel;
9-
import java.util.List;
109
import java.util.concurrent.atomic.AtomicLong;
1110

12-
public abstract class TarantoolBase<Result> extends AbstractTarantoolOps<Integer, List<?>, Object, Result> {
11+
public abstract class TarantoolBase<Result> extends AbstractTarantoolOps<Result> {
1312
protected String serverVersion;
1413
protected MsgPackLite msgPackLite = MsgPackLite.INSTANCE;
1514
protected AtomicLong syncId = new AtomicLong();
@@ -42,16 +41,6 @@ protected void closeChannel(SocketChannel channel) {
4241
}
4342
}
4443

45-
protected void validateArgs(Object[] args) {
46-
if (args != null) {
47-
for (int i = 0; i < args.length; i += 2) {
48-
if (args[i + 1] == null) {
49-
throw new NullPointerException(((Key) args[i]).name() + " should not be null");
50-
}
51-
}
52-
}
53-
}
54-
5544
public void setInitialRequestSize(int initialRequestSize) {
5645
this.initialRequestSize = initialRequestSize;
5746
}

0 commit comments

Comments
 (0)