diff --git a/README.md b/README.md index b1e90500..659aa410 100644 --- a/README.md +++ b/README.md @@ -134,6 +134,194 @@ all the results, you could override this: protected void complete(TarantoolPacket packet, CompletableFuture future); ``` +### Supported operation types + +Given a tarantool space as: + +```lua +box.schema.space.create('cars', { format = + { {name='id', type='integer'}," + {name='name', type='string'}," + {name='max_mph', type='integer'} } +}); +box.space.cars:create_index('pk', { type='TREE', parts={'id'} }); +box.space.cars:create_index('speed_idx', { type='TREE', unique=false, parts={'max_mph', type='unsigned'} }); +``` + +and a stored function as well: + +```lua +function getVehiclesSlowerThan(max_mph, max_size) + return box.space.cars.index.speed_idx:select(max_mph, {iterator='LT', limit=max_size}); +end; +``` + +Let's have a look what sort of operations we can apply to it using a connector. +*Note*: assume Tarantool generated id equal `512` for the newly created `cars` space. + +* SELECT (find tuples matching the search pattern) + +For instance, we can get a single tuple by id like + +```java +ops.select(512, 0, Collections.singletonList(1), 0, 1, Iterator.EQ); +``` + +or using more readable lookup names + +```java +ops.select("cars", "pk", Collections.singletonList(1), 0, 1, Iterator.EQ); +``` + +or even using builder-way to construct a query part-by-part + +```java +import static org.tarantool.dsl.Requests.selectRequest; + +ops.execute( + selectRequest("cars", "pk") + .iterator(Iterator.EQ) + .limit(1) +); +``` + +* INSERT (put a tuple in the space) + +Let's insert a new tuple into the space + +```java +ops.insert(512, Arrays.asList(1, "Lada Niva", 81)); +``` + +do the same using names + +```java +ops.insert("cars", Arrays.asList(1, "Lada Niva", 81)); +``` + +or using DSL + +```java +import static org.tarantool.dsl.Requests.insertRequest; + +ops.execute( + insertRequest("cars", Arrays.asList(1, "Lada Niva", 81)) +); +``` + +* REPLACE (insert a tuple into the space or replace an existing one) + +The syntax is quite similar to insert operation + +```java +import static org.tarantool.dsl.Requests.replaceRequest; + +ops.replace(512, Arrays.asList(2, "UAZ-469", 60)); +ops.replace("cars", Arrays.asList(2, "UAZ-469", 60)); +ops.execute( + replaceRequest("cars", Arrays.asList(2, "UAZ-469", 60)) +); +``` + +* UPDATE (update a tuple) + +Let's modify one of existing tuples + +```java +ops.update(512, Collections.singletonList(1), Arrays.asList("=", 1, "Lada 4×4")); +``` + +Lookup way: + +```java +ops.update("cars", Collections.singletonList(1), Arrays.asList("=", 1, "Lada 4×4")); +``` + +or using DSL + +```java +import static org.tarantool.dsl.Operations.assign; +import static org.tarantool.dsl.Requests.updateRequest; + +ops.execute( + updateRequest("cars", Collections.singletonList(1), assign(1, "Lada 4×4")) +); +``` + +*Note*: since Tarantool 2.3.x you can refer to tuple fields by name: + +```java +ops.update(512, Collections.singletonList(1), Arrays.asList("=", "name", "Lada 4×4")); +``` + +* UPSERT (update a tuple if it exists, otherwise try to insert it as a new tuple) + +An example looks as a mix of both insert and update operations: + +```java +import static org.tarantool.dsl.Operations.assign; +import static org.tarantool.dsl.Requests.upsertRequest; + +ops.upsert(512, Collections.singletonList(3), Arrays.asList(3, "KAMAZ-65224", 65), Arrays.asList("=", 2, 65)); +ops.upsert("cars", Collections.singletonList(3), Arrays.asList(3, "KAMAZ-65224", 65), Arrays.asList("=", 2, 65)); +ops.execute( + upsertRequest("cars", Collections.singletonList(3), assign(2, 65)) +); +``` + +*Note*: since Tarantool 2.3.x you can refer to tuple fields by name: + +```java +ops.upsert("cars", Collections.singletonList(3), Arrays.asList(3, "KAMAZ-65224", 65), Arrays.asList("=", "max_mph", 65)); +``` + +* DELETE (delete a tuple) + +Remove a tuple using one of the following forms: + +```java +import static org.tarantool.dsl.Requests.deleteRequest; + +ops().delete(512, Collections.singletonList(1)); +// same via lookup +ops().delete("cars", Collections.singletonList(1)); +// same via DSL +ops.execute(deleteRequest("cars", Collections.singletonList(1))); +``` + +* CALL / CALL v1.6 (call a stored function) + +Let's invoke the predefined function to fetch slower enough vehicles: + +```java +import static org.tarantool.dsl.Requests.callRequest; + +ops().call("getVehiclesSlowerThan", 80, 10); +// same via DSL +ops.execute(callRequest("getVehiclesSlowerThan").arguments(80, 10)); +``` + +*NOTE*: to use obsolete Tarantool v1.6 operation, configure it as follows: + +```java +ops().setCallCode(Code.OLD_CALL); +ops().call("getVehiclesSlowerThan", 80, 10); +// same via DSL +ops.execute(callRequest("getVehiclesSlowerThan").arguments(80, 10).useCall16(true)); +``` \ + +* EVAL (evaluate a Lua expression) + +To evaluate expressions using Lua, you can invoke the following operation: + +```java +import static org.tarantool.dsl.Requests.evalRequest; + +ops().eval("return getVehiclesSlowerThan(...)", 90, 50); +// same via DSL +ops.execute(evalRequest("return getVehiclesSlowerThan(...)")).arguments(90, 50)); +``` + ### Client config options The client configuration options are represented through the `TarantoolClientConfig` class. diff --git a/src/main/java/org/tarantool/AbstractTarantoolOps.java b/src/main/java/org/tarantool/AbstractTarantoolOps.java index bdef668d..a61017c5 100644 --- a/src/main/java/org/tarantool/AbstractTarantoolOps.java +++ b/src/main/java/org/tarantool/AbstractTarantoolOps.java @@ -1,15 +1,29 @@ package org.tarantool; -import static org.tarantool.TarantoolRequestArgumentFactory.cacheLookupValue; -import static org.tarantool.TarantoolRequestArgumentFactory.value; - +import static org.tarantool.dsl.Requests.callRequest; +import static org.tarantool.dsl.Requests.deleteRequest; +import static org.tarantool.dsl.Requests.evalRequest; +import static org.tarantool.dsl.Requests.insertRequest; +import static org.tarantool.dsl.Requests.pingRequest; +import static org.tarantool.dsl.Requests.replaceRequest; +import static org.tarantool.dsl.Requests.selectRequest; +import static org.tarantool.dsl.Requests.updateRequest; +import static org.tarantool.dsl.Requests.upsertRequest; + +import org.tarantool.dsl.Operation; +import org.tarantool.dsl.TarantoolRequestSpec; +import org.tarantool.logging.Logger; +import org.tarantool.logging.LoggerFactory; import org.tarantool.schema.TarantoolSchemaMeta; +import java.util.Arrays; import java.util.List; public abstract class AbstractTarantoolOps implements TarantoolClientOps, Object, Result> { + private static final Logger LOGGER = LoggerFactory.getLogger(AbstractTarantoolOps.class); + private Code callCode = Code.CALL; protected abstract Result exec(TarantoolRequest request); @@ -17,184 +31,134 @@ public abstract class AbstractTarantoolOps protected abstract TarantoolSchemaMeta getSchemaMeta(); public Result select(Integer space, Integer index, List key, int offset, int limit, Iterator iterator) { - return select(space, index, key, offset, limit, iterator.getValue()); + return execute( + selectRequest(space, index) + .key(key) + .offset(offset).limit(limit) + .iterator(iterator) + ); } @Override public Result select(String space, String index, List key, int offset, int limit, Iterator iterator) { - return select(space, index, key, offset, limit, iterator.getValue()); + return execute( + selectRequest(space, index) + .key(key) + .offset(offset).limit(limit) + .iterator(iterator) + ); } @Override public Result select(Integer space, Integer index, List key, int offset, int limit, int iterator) { - return exec( - new TarantoolRequest( - Code.SELECT, - value(Key.SPACE), value(space), - value(Key.INDEX), value(index), - value(Key.KEY), value(key), - value(Key.ITERATOR), value(iterator), - value(Key.LIMIT), value(limit), - value(Key.OFFSET), value(offset) - ) + return execute( + selectRequest(space, index) + .key(key) + .offset(offset).limit(limit) + .iterator(iterator) ); } @Override public Result select(String space, String index, List key, int offset, int limit, int iterator) { - return exec( - new TarantoolRequest( - Code.SELECT, - value(Key.SPACE), cacheLookupValue(() -> getSchemaMeta().getSpace(space).getId()), - value(Key.INDEX), cacheLookupValue(() -> getSchemaMeta().getSpaceIndex(space, index).getId()), - value(Key.KEY), value(key), - value(Key.ITERATOR), value(iterator), - value(Key.LIMIT), value(limit), - value(Key.OFFSET), value(offset) - ) + return execute( + selectRequest(space, index) + .key(key) + .offset(offset).limit(limit) + .iterator(iterator) ); } @Override public Result insert(Integer space, List tuple) { - return exec(new TarantoolRequest( - Code.INSERT, - value(Key.SPACE), value(space), - value(Key.TUPLE), value(tuple) - ) - ); + return execute(insertRequest(space, tuple)); } @Override public Result insert(String space, List tuple) { - return exec( - new TarantoolRequest( - Code.INSERT, - value(Key.SPACE), cacheLookupValue(() -> getSchemaMeta().getSpace(space).getId()), - value(Key.TUPLE), value(tuple) - ) - ); + return execute(insertRequest(space, tuple)); } @Override public Result replace(Integer space, List tuple) { - return exec( - new TarantoolRequest( - Code.REPLACE, - value(Key.SPACE), value(space), - value(Key.TUPLE), value(tuple) - ) - ); + return execute(replaceRequest(space, tuple)); } @Override public Result replace(String space, List tuple) { - return exec( - new TarantoolRequest( - Code.REPLACE, - value(Key.SPACE), cacheLookupValue(() -> getSchemaMeta().getSpace(space).getId()), - value(Key.TUPLE), value(tuple) - ) - ); + return execute(replaceRequest(space, tuple)); } @Override public Result update(Integer space, List key, Object... operations) { - return exec( - new TarantoolRequest( - Code.UPDATE, - value(Key.SPACE), value(space), - value(Key.KEY), value(key), - value(Key.TUPLE), value(operations) - ) - ); + Operation[] ops = Arrays.stream(operations) + .map(Operation::fromArray) + .toArray(org.tarantool.dsl.Operation[]::new); + return execute(updateRequest(space, key, ops)); } @Override public Result update(String space, List key, Object... operations) { - return exec( - new TarantoolRequest( - Code.UPDATE, - value(Key.SPACE), cacheLookupValue(() -> getSchemaMeta().getSpace(space).getId()), - value(Key.KEY), value(key), - value(Key.TUPLE), value(operations) - ) - ); + Operation[] ops = Arrays.stream(operations) + .map(Operation::fromArray) + .toArray(org.tarantool.dsl.Operation[]::new); + return execute(updateRequest(space, key, ops)); } @Override public Result upsert(Integer space, List key, List defTuple, Object... operations) { - return exec( - new TarantoolRequest( - Code.UPSERT, - value(Key.SPACE), value(space), - value(Key.KEY), value(key), - value(Key.TUPLE), value(defTuple), - value(Key.UPSERT_OPS), value(operations) - ) - ); + Operation[] ops = Arrays.stream(operations) + .map(Operation::fromArray) + .toArray(Operation[]::new); + return execute(upsertRequest(space, key, defTuple, ops)); } @Override public Result upsert(String space, List key, List defTuple, Object... operations) { - return exec( - new TarantoolRequest( - Code.UPSERT, - value(Key.SPACE), cacheLookupValue(() -> getSchemaMeta().getSpace(space).getId()), - value(Key.KEY), value(key), - value(Key.TUPLE), value(defTuple), - value(Key.UPSERT_OPS), value(operations) - ) - ); + Operation[] ops = Arrays.stream(operations) + .map(Operation::fromArray) + .toArray(Operation[]::new); + return execute(upsertRequest(space, key, defTuple, ops)); } @Override public Result delete(Integer space, List key) { - return exec( - new TarantoolRequest( - Code.DELETE, - value(Key.SPACE), value(space), - value(Key.KEY), value(key) - ) - ); + return execute(deleteRequest(space, key)); } @Override public Result delete(String space, List key) { - return exec( - new TarantoolRequest( - Code.DELETE, - value(Key.SPACE), cacheLookupValue(() -> getSchemaMeta().getSpace(space).getId()), - value(Key.KEY), value(key) - ) - ); + return execute(deleteRequest(space, key)); } @Override public Result call(String function, Object... args) { - return exec( - new TarantoolRequest( - callCode, - value(Key.FUNCTION), value(function), - value(Key.TUPLE), value(args) - ) + return execute( + callRequest(function) + .arguments(args) + .useCall16(callCode == Code.OLD_CALL) ); } @Override public Result eval(String expression, Object... args) { - return exec( - new TarantoolRequest( - Code.EVAL, - value(Key.EXPRESSION), value(expression), - value(Key.TUPLE), value(args) - ) - ); + return execute(evalRequest(expression).arguments(args)); } @Override public void ping() { - exec(new TarantoolRequest(Code.PING)); + execute(pingRequest()); + } + + @Override + public Result execute(TarantoolRequestSpec requestSpec) { + TarantoolSchemaMeta schemaMeta = null; + try { + schemaMeta = getSchemaMeta(); + } catch (Exception cause) { + LOGGER.warn(() -> "Could not get Tarantool schema meta-info", cause); + } + return exec(requestSpec.toTarantoolRequest(schemaMeta)); } public void setCallCode(Code callCode) { diff --git a/src/main/java/org/tarantool/TarantoolClientOps.java b/src/main/java/org/tarantool/TarantoolClientOps.java index 9a466c44..9f4a00de 100644 --- a/src/main/java/org/tarantool/TarantoolClientOps.java +++ b/src/main/java/org/tarantool/TarantoolClientOps.java @@ -1,5 +1,7 @@ package org.tarantool; +import org.tarantool.dsl.TarantoolRequestSpec; + /** * Provides a set of typical operations with data in Tarantool. * @@ -41,6 +43,8 @@ public interface TarantoolClientOps { R eval(String expression, Object... args); + R execute(TarantoolRequestSpec requestSpec); + void ping(); void close(); diff --git a/src/main/java/org/tarantool/dsl/AbstractRequestSpec.java b/src/main/java/org/tarantool/dsl/AbstractRequestSpec.java new file mode 100644 index 00000000..877bb379 --- /dev/null +++ b/src/main/java/org/tarantool/dsl/AbstractRequestSpec.java @@ -0,0 +1,46 @@ +package org.tarantool.dsl; + +import org.tarantool.Code; +import org.tarantool.TarantoolRequest; +import org.tarantool.schema.TarantoolSchemaMeta; + +import java.time.Duration; + +public abstract class AbstractRequestSpec> + implements TarantoolRequestSpec { + + final Code code; + Duration duration = Duration.ZERO; + boolean useDefaultTimeout = true; + + AbstractRequestSpec(Code code) { + this.code = code; + } + + AbstractRequestSpec(Code code, Duration duration) { + this.code = code; + this.duration = duration; + } + + @SuppressWarnings("unchecked") + public B timeout(Duration duration) { + this.duration = duration; + this.useDefaultTimeout = false; + return (B) this; + } + + @SuppressWarnings("unchecked") + public B useDefaultTimeout() { + this.duration = Duration.ZERO; + this.useDefaultTimeout = true; + return (B) this; + } + + @Override + public TarantoolRequest toTarantoolRequest(TarantoolSchemaMeta schemaMeta) { + TarantoolRequest request = new TarantoolRequest(code); + request.setTimeout(useDefaultTimeout ? null : duration); + return request; + } + +} diff --git a/src/main/java/org/tarantool/dsl/CallRequestSpec.java b/src/main/java/org/tarantool/dsl/CallRequestSpec.java new file mode 100644 index 00000000..741e85d3 --- /dev/null +++ b/src/main/java/org/tarantool/dsl/CallRequestSpec.java @@ -0,0 +1,63 @@ +package org.tarantool.dsl; + +import static org.tarantool.TarantoolRequestArgumentFactory.value; + +import org.tarantool.Code; +import org.tarantool.Key; +import org.tarantool.TarantoolRequest; +import org.tarantool.schema.TarantoolSchemaMeta; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +public class CallRequestSpec extends AbstractRequestSpec { + + private String functionName; + private List arguments = new ArrayList<>(); + private boolean useCall16 = false; + + CallRequestSpec(String functionName) { + super(Code.CALL); + this.functionName = Objects.requireNonNull(functionName); + } + + public CallRequestSpec function(String functionName) { + Objects.requireNonNull(functionName); + this.functionName = functionName; + return this; + } + + public CallRequestSpec arguments(Object... arguments) { + this.arguments.clear(); + Collections.addAll(this.arguments, arguments); + return this; + } + + public CallRequestSpec arguments(Collection arguments) { + this.arguments.clear(); + this.arguments.addAll(arguments); + return this; + } + + public CallRequestSpec useCall16(boolean flag) { + this.useCall16 = flag; + return this; + } + + @Override + public TarantoolRequest toTarantoolRequest(TarantoolSchemaMeta schemaMeta) { + TarantoolRequest request = super.toTarantoolRequest(schemaMeta); + if (useCall16) { + request.setCode(Code.OLD_CALL); + } + request.addArguments( + value(Key.FUNCTION), value(functionName), + value(Key.TUPLE), value(arguments) + ); + return request; + } + +} diff --git a/src/main/java/org/tarantool/dsl/DeleteRequestSpec.java b/src/main/java/org/tarantool/dsl/DeleteRequestSpec.java new file mode 100644 index 00000000..78de41f3 --- /dev/null +++ b/src/main/java/org/tarantool/dsl/DeleteRequestSpec.java @@ -0,0 +1,66 @@ +package org.tarantool.dsl; + +import static org.tarantool.TarantoolRequestArgumentFactory.cacheLookupValue; +import static org.tarantool.TarantoolRequestArgumentFactory.value; + +import org.tarantool.Code; +import org.tarantool.Key; +import org.tarantool.TarantoolRequest; +import org.tarantool.schema.TarantoolSchemaMeta; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +public class DeleteRequestSpec extends SpaceRequestSpec { + + private List key; + + DeleteRequestSpec(int spaceId, List key) { + super(Code.DELETE, spaceId); + this.key = new ArrayList<>(key); + } + + DeleteRequestSpec(int spaceId, Object... keyParts) { + super(Code.DELETE, spaceId); + this.key = Arrays.asList(keyParts); + } + + DeleteRequestSpec(String spaceName, List key) { + super(Code.DELETE, spaceName); + this.key = new ArrayList<>(key); + } + + DeleteRequestSpec(String spaceName, Object... keyParts) { + super(Code.DELETE, spaceName); + this.key = Arrays.asList(keyParts); + } + + public DeleteRequestSpec primaryKey(Object... keyParts) { + this.key.clear(); + Collections.addAll(this.key, keyParts); + return this; + } + + public DeleteRequestSpec primaryKey(Collection key) { + this.key.clear(); + this.key.addAll(key); + return this; + } + + @Override + public TarantoolRequest toTarantoolRequest(TarantoolSchemaMeta schemaMeta) { + TarantoolRequest request = super.toTarantoolRequest(schemaMeta); + request.addArguments( + value(Key.SPACE), + spaceId == null + ? cacheLookupValue(() -> schemaMeta.getSpace(spaceName).getId()) + : value(spaceId), + value(Key.KEY), value(key) + ); + return request; + } + +} diff --git a/src/main/java/org/tarantool/dsl/EvalRequestSpec.java b/src/main/java/org/tarantool/dsl/EvalRequestSpec.java new file mode 100644 index 00000000..b5a080b3 --- /dev/null +++ b/src/main/java/org/tarantool/dsl/EvalRequestSpec.java @@ -0,0 +1,54 @@ +package org.tarantool.dsl; + +import static org.tarantool.TarantoolRequestArgumentFactory.value; + +import org.tarantool.Code; +import org.tarantool.Key; +import org.tarantool.TarantoolRequest; +import org.tarantool.schema.TarantoolSchemaMeta; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +public class EvalRequestSpec extends AbstractRequestSpec { + + private String expression; + private List arguments = new ArrayList<>(); + + EvalRequestSpec(String expression) { + super(Code.EVAL); + this.expression = Objects.requireNonNull(expression); + } + + public EvalRequestSpec expression(String expression) { + Objects.requireNonNull(expression); + this.expression = expression; + return this; + } + + public EvalRequestSpec arguments(Object... arguments) { + this.arguments.clear(); + Collections.addAll(this.arguments, arguments); + return this; + } + + public EvalRequestSpec arguments(Collection arguments) { + this.arguments.clear(); + this.arguments.addAll(arguments); + return this; + } + + @Override + public TarantoolRequest toTarantoolRequest(TarantoolSchemaMeta schemaMeta) { + TarantoolRequest request = super.toTarantoolRequest(schemaMeta); + request.addArguments( + value(Key.EXPRESSION), value(expression), + value(Key.TUPLE), value(arguments) + ); + return request; + } + +} diff --git a/src/main/java/org/tarantool/dsl/ExecuteRequestSpec.java b/src/main/java/org/tarantool/dsl/ExecuteRequestSpec.java new file mode 100644 index 00000000..c54ad34c --- /dev/null +++ b/src/main/java/org/tarantool/dsl/ExecuteRequestSpec.java @@ -0,0 +1,92 @@ +package org.tarantool.dsl; + +import static org.tarantool.TarantoolRequestArgumentFactory.value; + +import org.tarantool.Code; +import org.tarantool.Key; +import org.tarantool.TarantoolRequest; +import org.tarantool.schema.TarantoolSchemaMeta; +import org.tarantool.util.TupleTwo; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; + +public class ExecuteRequestSpec extends AbstractRequestSpec { + + private String sqlText; + private List ordinalBindings = new ArrayList<>(); + private List> namedBindings = new ArrayList<>(); + + ExecuteRequestSpec(String sqlText) { + super(Code.EXECUTE); + this.sqlText = Objects.requireNonNull(sqlText); + } + + public ExecuteRequestSpec sql(String text) { + Objects.requireNonNull(text); + this.sqlText = text; + return this; + } + + public ExecuteRequestSpec ordinalParameters(Object... bindings) { + this.ordinalBindings.clear(); + Collections.addAll(this.ordinalBindings, bindings); + this.namedBindings.clear(); + return this; + } + + public ExecuteRequestSpec ordinalParameters(Collection bindings) { + this.ordinalBindings.clear(); + this.ordinalBindings.addAll(bindings); + this.namedBindings.clear(); + return this; + } + + public ExecuteRequestSpec namedParameters(Map bindings) { + this.namedBindings.clear(); + this.namedBindings.addAll( + bindings.entrySet().stream() + .map(e -> TupleTwo.of(e.getKey(), e.getValue())) + .collect(Collectors.toList()) + ); + this.ordinalBindings.clear(); + return this; + } + + public ExecuteRequestSpec namedParameters(TupleTwo[] bindings) { + this.namedBindings.clear(); + for (TupleTwo binding : bindings) { + this.namedBindings.add(TupleTwo.of(binding.getFirst(), binding.getSecond())); + } + this.ordinalBindings.clear(); + return this; + } + + @Override + public TarantoolRequest toTarantoolRequest(TarantoolSchemaMeta schemaMeta) { + TarantoolRequest request = super.toTarantoolRequest(schemaMeta); + request.addArguments( + value(Key.SQL_TEXT), + value(sqlText) + ); + if (!ordinalBindings.isEmpty()) { + request.addArguments( + value(Key.SQL_BIND), + value(ordinalBindings) + ); + } + if (!namedBindings.isEmpty()) { + request.addArguments( + value(Key.SQL_BIND), + value(namedBindings) + ); + } + return request; + } + +} diff --git a/src/main/java/org/tarantool/dsl/InsertOrReplaceRequestSpec.java b/src/main/java/org/tarantool/dsl/InsertOrReplaceRequestSpec.java new file mode 100644 index 00000000..03890072 --- /dev/null +++ b/src/main/java/org/tarantool/dsl/InsertOrReplaceRequestSpec.java @@ -0,0 +1,81 @@ +package org.tarantool.dsl; + +import static org.tarantool.TarantoolRequestArgumentFactory.cacheLookupValue; +import static org.tarantool.TarantoolRequestArgumentFactory.value; + +import org.tarantool.Code; +import org.tarantool.Key; +import org.tarantool.TarantoolRequest; +import org.tarantool.schema.TarantoolSchemaMeta; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +public class InsertOrReplaceRequestSpec extends SpaceRequestSpec { + + public enum Mode { + INSERT(Code.INSERT), + REPLACE(Code.REPLACE); + + final Code code; + + Mode(Code code) { + this.code = code; + } + + public Code getCode() { + return code; + } + } + + private List tuple; + + InsertOrReplaceRequestSpec(Mode mode, int spaceId, List tuple) { + super(mode.getCode(), spaceId); + this.tuple = new ArrayList<>(tuple); + } + + InsertOrReplaceRequestSpec(Mode mode, String spaceName, List tuple) { + super(mode.getCode(), spaceName); + this.tuple = new ArrayList<>(tuple); + } + + InsertOrReplaceRequestSpec(Mode mode, int spaceId, Object... tupleItems) { + super(mode.getCode(), spaceId); + this.tuple = Arrays.asList(tupleItems); + } + + InsertOrReplaceRequestSpec(Mode mode, String spaceName, Object... tupleItems) { + super(mode.getCode(), spaceName); + this.tuple = Arrays.asList(tupleItems); + } + + public InsertOrReplaceRequestSpec tuple(Object... tupleItems) { + this.tuple.clear(); + Collections.addAll(this.tuple, tupleItems); + return this; + } + + public InsertOrReplaceRequestSpec tuple(Collection tuple) { + this.tuple.clear(); + this.tuple.addAll(tuple); + return this; + } + + @Override + public TarantoolRequest toTarantoolRequest(TarantoolSchemaMeta schemaMeta) { + TarantoolRequest request = super.toTarantoolRequest(schemaMeta); + request.addArguments( + value(Key.SPACE), + spaceId == null + ? cacheLookupValue(() -> schemaMeta.getSpace(spaceName).getId()) + : value(spaceId), + value(Key.TUPLE), value(tuple) + ); + return request; + } + +} diff --git a/src/main/java/org/tarantool/dsl/Operation.java b/src/main/java/org/tarantool/dsl/Operation.java new file mode 100644 index 00000000..aae09a6f --- /dev/null +++ b/src/main/java/org/tarantool/dsl/Operation.java @@ -0,0 +1,65 @@ +package org.tarantool.dsl; + +import java.util.Arrays; +import java.util.List; + +public class Operation { + + private final Operator operator; + private final List operands; + + public Operation(Operator operator, Object... operands) { + this.operator = operator; + this.operands = Arrays.asList(operands); + } + + public Operator getOperator() { + return operator; + } + + public Object[] toArray() { + Object[] array = new Object[operands.size() + 1]; + array[0] = operator.getOpCode(); + for (int i = 1; i < array.length; i++) { + array[i] = operands.get(i - 1); + } + return array; + } + + /** + * It's used to perform a transformation between raw type + * and type safe DSL Operation class. This is required + * because of being compatible with old operations interface + * and a new DSL approach. + * + * This client expects an operation in format of simple + * array or list like {opCode, args...}. For instance, + * addition 3 to second field will be {"+", 2, 3} + * + * @param operation raw operation + * + * @return type safe operation + */ + public static Operation fromArray(Object operation) { + try { + if (operation instanceof Object[]) { + Object[] opArray = (Object[]) operation; + String code = opArray[0].toString(); + Object[] args = new Object[opArray.length - 1]; + System.arraycopy(opArray, 1, args, 0, args.length); + return new Operation(Operator.byOpCode(code), args); + } + List opList = (List) operation; + String code = opList.get(0).toString(); + Object[] args = opList.subList(1, opList.size()).toArray(); + return new Operation(Operator.byOpCode(code), args); + } catch (Exception cause) { + throw new IllegalArgumentException( + "Operation is invalid. Use an array or list as {\"opCode\", args...}. " + + "Or use request DSL to build type safe operation.", + cause + ); + } + } + +} diff --git a/src/main/java/org/tarantool/dsl/Operations.java b/src/main/java/org/tarantool/dsl/Operations.java new file mode 100644 index 00000000..e8ae5b1c --- /dev/null +++ b/src/main/java/org/tarantool/dsl/Operations.java @@ -0,0 +1,84 @@ +package org.tarantool.dsl; + +import java.util.Objects; + +public class Operations { + + public static Operation add(int fieldNumber, long value) { + return new Operation(Operator.ADDITION, fieldNumber, value); + } + + public static Operation add(String fieldName, long value) { + return new Operation(Operator.ADDITION, fieldName, value); + } + + public static Operation subtract(int fieldNumber, long value) { + return new Operation(Operator.SUBTRACTION, fieldNumber, value); + } + + public static Operation subtract(String fieldName, long value) { + return new Operation(Operator.SUBTRACTION, fieldName, value); + } + + public static Operation bitwiseAnd(int fieldNumber, long value) { + return new Operation(Operator.BITWISE_AND, fieldNumber, value); + } + + public static Operation bitwiseAnd(String fieldName, long value) { + return new Operation(Operator.BITWISE_AND, fieldName, value); + } + + public static Operation bitwiseOr(int fieldNumber, long value) { + return new Operation(Operator.BITWISE_OR, fieldNumber, value); + } + + public static Operation bitwiseOr(String fieldName, long value) { + return new Operation(Operator.BITWISE_OR, fieldName, value); + } + + public static Operation bitwiseXor(int fieldNumber, long value) { + return new Operation(Operator.BITWISE_XOR, fieldNumber, value); + } + + public static Operation bitwiseXor(String fieldName, long value) { + return new Operation(Operator.BITWISE_XOR, fieldName, value); + } + + public static Operation splice(int fieldNumber, int position, int offset, String substitution) { + return new Operation(Operator.SPLICE, fieldNumber, position, offset, substitution); + } + + public static Operation splice(String fieldName, int position, int offset, String substitution) { + return new Operation(Operator.SPLICE, fieldName, position, offset, substitution); + } + + public static Operation insert(int fieldNumber, Object value) { + return new Operation(Operator.INSERT, fieldNumber, value); + } + + public static Operation insert(String fieldName, Object value) { + return new Operation(Operator.INSERT, fieldName, value); + } + + public static Operation delete(int fromField, int length) { + return new Operation(Operator.DELETE, fromField, length); + } + + public static Operation delete(String fromField, int length) { + return new Operation(Operator.DELETE, fromField, length); + } + + public static Operation assign(int fieldNumber, Object value) { + return new Operation(Operator.ASSIGN, fieldNumber, value); + } + + public static Operation assign(String fieldName, Object value) { + return new Operation(Operator.ASSIGN, fieldName, value); + } + + private static Operation createOperation(Operator operator, int fieldNumber, Object value) { + Objects.requireNonNull(value); + return new Operation(operator, fieldNumber, value); + } + +} diff --git a/src/main/java/org/tarantool/dsl/Operator.java b/src/main/java/org/tarantool/dsl/Operator.java new file mode 100644 index 00000000..ae69e0c9 --- /dev/null +++ b/src/main/java/org/tarantool/dsl/Operator.java @@ -0,0 +1,33 @@ +package org.tarantool.dsl; + +import java.util.stream.Stream; + +public enum Operator { + ADDITION("+"), + SUBTRACTION("-"), + BITWISE_AND("&"), + BITWISE_OR("|"), + BITWISE_XOR("^"), + SPLICE(":"), + INSERT("!"), + DELETE("#"), + ASSIGN("="); + + private final String opCode; + + Operator(String opCode) { + this.opCode = opCode; + } + + public String getOpCode() { + return opCode; + } + + public static Operator byOpCode(String opCode) { + return Stream.of(Operator.values()) + .filter(s -> s.getOpCode().equals(opCode)) + .findFirst() + .orElseThrow(IllegalArgumentException::new); + } + +} diff --git a/src/main/java/org/tarantool/dsl/PingRequestSpec.java b/src/main/java/org/tarantool/dsl/PingRequestSpec.java new file mode 100644 index 00000000..3449edad --- /dev/null +++ b/src/main/java/org/tarantool/dsl/PingRequestSpec.java @@ -0,0 +1,11 @@ +package org.tarantool.dsl; + +import org.tarantool.Code; + +public class PingRequestSpec extends AbstractRequestSpec { + + PingRequestSpec() { + super(Code.PING); + } + +} diff --git a/src/main/java/org/tarantool/dsl/Requests.java b/src/main/java/org/tarantool/dsl/Requests.java new file mode 100644 index 00000000..2b0ca763 --- /dev/null +++ b/src/main/java/org/tarantool/dsl/Requests.java @@ -0,0 +1,109 @@ +package org.tarantool.dsl; + +import org.tarantool.dsl.InsertOrReplaceRequestSpec.Mode; + +import java.util.List; + +/** + * Entry point to build requests + * using DSL approach a.k.a Request DSL. + */ +public class Requests { + + public static SelectRequestSpec selectRequest(int space, int index) { + return new SelectRequestSpec(space, index); + } + + public static SelectRequestSpec selectRequest(String space, int index) { + return new SelectRequestSpec(space, index); + } + + public static SelectRequestSpec selectRequest(int space, String index) { + return new SelectRequestSpec(space, index); + } + + public static SelectRequestSpec selectRequest(String space, String index) { + return new SelectRequestSpec(space, index); + } + + public static InsertOrReplaceRequestSpec insertRequest(int space, List tuple) { + return new InsertOrReplaceRequestSpec(Mode.INSERT, space, tuple); + } + + public static InsertOrReplaceRequestSpec insertRequest(int space, Object... tupleItems) { + return new InsertOrReplaceRequestSpec(Mode.INSERT, space, tupleItems); + } + + public static InsertOrReplaceRequestSpec insertRequest(String space, List tuple) { + return new InsertOrReplaceRequestSpec(Mode.INSERT, space, tuple); + } + + public static InsertOrReplaceRequestSpec insertRequest(String space, Object... tupleItems) { + return new InsertOrReplaceRequestSpec(Mode.INSERT, space, tupleItems); + } + + public static InsertOrReplaceRequestSpec replaceRequest(int space, List tuple) { + return new InsertOrReplaceRequestSpec(Mode.REPLACE, space, tuple); + } + + public static InsertOrReplaceRequestSpec replaceRequest(int space, Object... tupleItems) { + return new InsertOrReplaceRequestSpec(Mode.REPLACE, space, tupleItems); + } + + public static InsertOrReplaceRequestSpec replaceRequest(String space, List tuple) { + return new InsertOrReplaceRequestSpec(Mode.REPLACE, space, tuple); + } + + public static InsertOrReplaceRequestSpec replaceRequest(String space, Object... tupleItems) { + return new InsertOrReplaceRequestSpec(Mode.REPLACE, space, tupleItems); + } + + public static UpdateRequestSpec updateRequest(int space, List key, Operation... operations) { + return new UpdateRequestSpec(space, key, operations); + } + + public static UpdateRequestSpec updateRequest(String space, List key, Operation... operations) { + return new UpdateRequestSpec(space, key, operations); + } + + public static UpsertRequestSpec upsertRequest(int space, List key, List tuple, Operation... operations) { + return new UpsertRequestSpec(space, key, tuple, operations); + } + + public static UpsertRequestSpec upsertRequest(String space, List key, List tuple, Operation... operations) { + return new UpsertRequestSpec(space, key, tuple, operations); + } + + public static DeleteRequestSpec deleteRequest(int space, List key) { + return new DeleteRequestSpec(space, key); + } + + public static DeleteRequestSpec deleteRequest(int space, Object... keyParts) { + return new DeleteRequestSpec(space, keyParts); + } + + public static DeleteRequestSpec deleteRequest(String space, List key) { + return new DeleteRequestSpec(space, key); + } + + public static DeleteRequestSpec deleteRequest(String space, Object... keyParts) { + return new DeleteRequestSpec(space, keyParts); + } + + public static CallRequestSpec callRequest(String function) { + return new CallRequestSpec(function); + } + + public static EvalRequestSpec evalRequest(String expression) { + return new EvalRequestSpec(expression); + } + + public static PingRequestSpec pingRequest() { + return new PingRequestSpec(); + } + + public static ExecuteRequestSpec executeRequest(String sql) { + return new ExecuteRequestSpec(sql); + } + +} diff --git a/src/main/java/org/tarantool/dsl/SelectRequestSpec.java b/src/main/java/org/tarantool/dsl/SelectRequestSpec.java new file mode 100644 index 00000000..bac73e45 --- /dev/null +++ b/src/main/java/org/tarantool/dsl/SelectRequestSpec.java @@ -0,0 +1,111 @@ +package org.tarantool.dsl; + +import static org.tarantool.TarantoolRequestArgumentFactory.cacheLookupValue; +import static org.tarantool.TarantoolRequestArgumentFactory.value; + +import org.tarantool.Code; +import org.tarantool.Iterator; +import org.tarantool.Key; +import org.tarantool.TarantoolRequest; +import org.tarantool.schema.TarantoolSchemaMeta; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +public class SelectRequestSpec extends SpaceRequestSpec { + + private Integer indexId; + private String indexName; + private List key = new ArrayList<>(); + private Iterator iterator = Iterator.ALL; + private int offset = 0; + private int limit = Integer.MAX_VALUE; + + public SelectRequestSpec(int spaceId, int indexId) { + super(Code.SELECT, spaceId); + this.indexId = indexId; + } + + public SelectRequestSpec(int spaceId, String indexName) { + super(Code.SELECT, spaceId); + this.indexName = Objects.requireNonNull(indexName); + } + + public SelectRequestSpec(String spaceName, int indexId) { + super(Code.SELECT, spaceName); + this.indexId = indexId; + } + + public SelectRequestSpec(String spaceName, String indexName) { + super(Code.SELECT, spaceName); + this.indexName = Objects.requireNonNull(indexName); + } + + public SelectRequestSpec index(int indexId) { + this.indexId = indexId; + this.indexName = null; + return this; + } + + public SelectRequestSpec index(String indexName) { + this.indexName = Objects.requireNonNull(indexName); + this.indexId = null; + return this; + } + + public SelectRequestSpec key(Object... keyParts) { + this.key.clear(); + Collections.addAll(this.key, keyParts); + return this; + } + + public SelectRequestSpec key(Collection key) { + this.key.clear(); + this.key.addAll(key); + return this; + } + + public SelectRequestSpec iterator(Iterator iterator) { + this.iterator = iterator; + return this; + } + + public SelectRequestSpec iterator(int iterator) { + this.iterator = Iterator.valueOf(iterator); + return this; + } + + public SelectRequestSpec offset(int offset) { + this.offset = offset; + return this; + } + + public SelectRequestSpec limit(int limit) { + this.limit = limit; + return this; + } + + @Override + public TarantoolRequest toTarantoolRequest(TarantoolSchemaMeta schemaMeta) { + TarantoolRequest request = super.toTarantoolRequest(schemaMeta); + request.addArguments( + value(Key.SPACE), + spaceId == null + ? cacheLookupValue(() -> schemaMeta.getSpace(spaceName).getId()) + : value(spaceId), + value(Key.INDEX), + indexId == null + ? cacheLookupValue(() -> schemaMeta.getSpaceIndex(spaceName, indexName).getId()) + : value(indexId), + value(Key.KEY), value(key), + value(Key.ITERATOR), value(iterator.getValue()), + value(Key.LIMIT), value(limit), + value(Key.OFFSET), value(offset) + ); + return request; + } + +} diff --git a/src/main/java/org/tarantool/dsl/SpaceRequestSpec.java b/src/main/java/org/tarantool/dsl/SpaceRequestSpec.java new file mode 100644 index 00000000..fb03152d --- /dev/null +++ b/src/main/java/org/tarantool/dsl/SpaceRequestSpec.java @@ -0,0 +1,46 @@ +package org.tarantool.dsl; + +import org.tarantool.Code; + +import java.util.Objects; + +/** + * Supports space related DSL builders. + * + * @param current build type + */ +public abstract class SpaceRequestSpec> + extends AbstractRequestSpec { + + Integer spaceId; + String spaceName; + + public SpaceRequestSpec(Code code) { + super(code); + } + + public SpaceRequestSpec(Code code, int spaceId) { + this(code); + this.spaceId = spaceId; + } + + public SpaceRequestSpec(Code code, String spaceName) { + this(code); + this.spaceName = Objects.requireNonNull(spaceName); + } + + @SuppressWarnings("unchecked") + public B space(int spaceId) { + this.spaceId = spaceId; + this.spaceName = null; + return (B) this; + } + + @SuppressWarnings("unchecked") + public B space(String spaceName) { + this.spaceName = Objects.requireNonNull(spaceName); + this.spaceId = null; + return (B) this; + } + +} diff --git a/src/main/java/org/tarantool/dsl/TarantoolRequestSpec.java b/src/main/java/org/tarantool/dsl/TarantoolRequestSpec.java new file mode 100644 index 00000000..6fb4b96e --- /dev/null +++ b/src/main/java/org/tarantool/dsl/TarantoolRequestSpec.java @@ -0,0 +1,21 @@ +package org.tarantool.dsl; + +import org.tarantool.TarantoolRequest; +import org.tarantool.schema.TarantoolSchemaMeta; + +/** + * Used to convert DSL builder to appropriate + * Tarantool requests. + * + * This interface is not a part of public API. + */ +public interface TarantoolRequestSpec { + + /** + * Converts the target to {@link TarantoolRequest}. + * + * @return converted request + */ + TarantoolRequest toTarantoolRequest(TarantoolSchemaMeta schemaMeta); + +} diff --git a/src/main/java/org/tarantool/dsl/UpdateRequestSpec.java b/src/main/java/org/tarantool/dsl/UpdateRequestSpec.java new file mode 100644 index 00000000..d2b0105b --- /dev/null +++ b/src/main/java/org/tarantool/dsl/UpdateRequestSpec.java @@ -0,0 +1,73 @@ +package org.tarantool.dsl; + +import static org.tarantool.TarantoolRequestArgumentFactory.cacheLookupValue; +import static org.tarantool.TarantoolRequestArgumentFactory.value; + +import org.tarantool.Code; +import org.tarantool.Key; +import org.tarantool.TarantoolRequest; +import org.tarantool.schema.TarantoolSchemaMeta; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +public class UpdateRequestSpec extends SpaceRequestSpec { + + private List key; + private List operations; + + public UpdateRequestSpec(int spaceId, List key, Operation... operations) { + super(Code.UPDATE, spaceId); + this.key = new ArrayList<>(key); + this.operations = Arrays.asList(operations); + } + + public UpdateRequestSpec(String spaceName, List key, Operation... operations) { + super(Code.UPDATE, spaceName); + this.key = new ArrayList<>(key); + this.operations = Arrays.asList(operations); + } + + public UpdateRequestSpec primaryKey(Object... keyParts) { + this.key.clear(); + Collections.addAll(this.key, keyParts); + return this; + } + + public UpdateRequestSpec primaryKey(Collection key) { + this.key.clear(); + this.key.addAll(key); + return this; + } + + public UpdateRequestSpec operations(Operation... operations) { + this.operations.clear(); + Collections.addAll(this.operations, operations); + return this; + } + + public UpdateRequestSpec operations(Collection operations) { + this.operations.clear(); + this.operations.addAll(operations); + return this; + } + + @Override + public TarantoolRequest toTarantoolRequest(TarantoolSchemaMeta schemaMeta) { + TarantoolRequest request = super.toTarantoolRequest(schemaMeta); + request.addArguments( + value(Key.SPACE), + spaceId == null + ? cacheLookupValue(() -> schemaMeta.getSpace(spaceName).getId()) + : value(spaceId), + value(Key.KEY), value(key), + value(Key.TUPLE), value(operations.stream().map(Operation::toArray).collect(Collectors.toList())) + ); + return request; + } + +} diff --git a/src/main/java/org/tarantool/dsl/UpsertRequestSpec.java b/src/main/java/org/tarantool/dsl/UpsertRequestSpec.java new file mode 100644 index 00000000..c1e01023 --- /dev/null +++ b/src/main/java/org/tarantool/dsl/UpsertRequestSpec.java @@ -0,0 +1,88 @@ +package org.tarantool.dsl; + +import static org.tarantool.TarantoolRequestArgumentFactory.cacheLookupValue; +import static org.tarantool.TarantoolRequestArgumentFactory.value; + +import org.tarantool.Code; +import org.tarantool.Key; +import org.tarantool.TarantoolRequest; +import org.tarantool.schema.TarantoolSchemaMeta; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +public class UpsertRequestSpec extends SpaceRequestSpec { + + private List key; + private List tuple; + private List operations; + + public UpsertRequestSpec(int spaceId, List key, List tuple, Operation... operations) { + super(Code.UPSERT, spaceId); + this.key = new ArrayList<>(key); + this.tuple = new ArrayList<>(tuple); + this.operations = Arrays.asList(operations); + } + + public UpsertRequestSpec(String spaceName, List key, List tuple, Operation... operations) { + super(Code.UPSERT, spaceName); + this.key = new ArrayList<>(key); + this.tuple = new ArrayList<>(tuple); + this.operations = Arrays.asList(operations); + } + + public UpsertRequestSpec primaryKey(Object... keyParts) { + this.key.clear(); + Collections.addAll(this.key, keyParts); + return this; + } + + public UpsertRequestSpec primaryKey(Collection key) { + this.key.clear(); + this.key.addAll(key); + return this; + } + + public UpsertRequestSpec tuple(Collection tuple) { + this.tuple.clear(); + this.tuple.addAll(tuple); + return this; + } + + public UpsertRequestSpec tuple(Object... tupleItems) { + this.tuple.clear(); + Collections.addAll(this.tuple, tupleItems); + return this; + } + + public UpsertRequestSpec operations(Collection operations) { + this.operations.clear(); + this.operations.addAll(operations); + return this; + } + + public UpsertRequestSpec operations(Operation... operations) { + this.operations.clear(); + Collections.addAll(this.operations, operations); + return this; + } + + @Override + public TarantoolRequest toTarantoolRequest(TarantoolSchemaMeta schemaMeta) { + TarantoolRequest request = super.toTarantoolRequest(schemaMeta); + request.addArguments( + value(Key.SPACE), + spaceId == null + ? cacheLookupValue(() -> schemaMeta.getSpace(spaceName).getId()) + : value(spaceId), + value(Key.KEY), value(key), + value(Key.TUPLE), value(tuple), + value(Key.UPSERT_OPS), value(operations.stream().map(Operation::toArray).collect(Collectors.toList())) + ); + return request; + } +} diff --git a/src/test/java/org/tarantool/ClientAsyncOperationsIT.java b/src/test/java/org/tarantool/ClientAsyncOperationsIT.java index 20ead6a3..61d3356f 100644 --- a/src/test/java/org/tarantool/ClientAsyncOperationsIT.java +++ b/src/test/java/org/tarantool/ClientAsyncOperationsIT.java @@ -7,6 +7,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static org.tarantool.TestAssertions.checkRawTupleResult; +import org.tarantool.dsl.TarantoolRequestSpec; import org.tarantool.schema.TarantoolIndexNotFoundException; import org.tarantool.schema.TarantoolSpaceNotFoundException; @@ -579,6 +580,11 @@ public Future> eval(String expression, Object... args) { return originOps.eval(expression, args).toCompletableFuture(); } + @Override + public Future> execute(TarantoolRequestSpec requestSpec) { + return originOps.execute(requestSpec).toCompletableFuture(); + } + @Override public void ping() { originOps.ping();