Functional DSL
The functional DSL exists to allow users to express arguments passed to TCStore operations in a form that is both portable between clients and servers (over the network), and whose underlying behavior can be introspected and understood by the TCStore software. DSL expressions are the preferred form for all functional arguments passed to TCStore.
Cell Operations
Functional operations on cells and their associated values can be created via references to the associated cell definition objects.
BoolCellDefinition definition = defineBool("cell");
Predicate<Record<?>> exists = definition.exists(); // <1>
Predicate<Record<?>> isTrue = definition.isTrue(); // <2>
| 1 |
A cell existence predicate. The predicate returns true if the passed record contains a cell of this definition. This is available for all definition types. |
| 2 |
A boolean cell value predicate. The predicate returns true if the passed record contains a cell of this definition with a true value (this means an absence of the cell results in a false value). |
The types returned from the DSL are fluent builders so you can derive functions from existing functions.
StringCellDefinition definition = defineString("cell");
BuildableComparableOptionalFunction<Record<?>, String>
value = definition.value(); // <1>
Predicate<Record<?>> isFoo = value.is("foo"); // <2>
Predicate<Record<?>> isAfterBar = value.isGreaterThan("bar"); // <3>
| 1 |
A cell value extraction function. This is a subtype of
|
| 2 |
A value equality predicate. The predicate returns true if the passed record contains a cell of this definition whose value is "foo". |
| 3 |
An open range predicate. The predicate returns true if the passed records contains a cell of this definition whose value is strictly greater than "bar". |
The available build methods are specialized to the type of the cell in question. Numerically typed cells can be used to do numeric manipulations.
IntCellDefinition definition = defineInt("cell");
BuildableToIntFunction<Record<?>> intValue = definition.intValueOr(0); // <1>
BuildablePredicate<Record<?>> isGreaterThanOrEqualTo4 =
intValue.isGreaterThanOrEqualTo(4); // <2>
ToIntFunction<Record<?>> incremented = intValue.increment(); // <3>
Comparator<Record<?>> comparator = intValue.asComparator(); // <4>
| 1 |
An integer extracting function that returns a specialized builder type, that is also a primitive int bearing function. |
| 2 |
A numeric open range predicate. Ranges are available for all comparable cell types. |
| 3 |
An integer extracting function that outputs the value incremented (+1). |
| 4 |
A comparator over extracted values. |
Cell derived expressions will be frequently used as:
- Predicates for streams and CRUD operations.
- Mappers for streams and read operations.
- Input for functional update operations.
Complex Data Types (CDTs)
LIST and MAP typed cells have additional DSL concepts. Lists
and Maps are not scalar values, they contain multitudes. This affects their interaction with
the DSL in a couple of ways. First, they have three common DSL predicates that generate scalar
values and thus integrate directly with the existing DSL: isEmpty(),
notEmpty(), size().
For example, this fragment shows conditional filtering on a LIST cell.
ListCellDefinition listDef=...
...stream().filter(listDef.value().size().greaterThan(10)).
The LIST cell definition's value() family of methods use the
BuildableListFunction hierarchy and add contains() to test if the
list contains a specified TypedValue. The MAP cell definition's
value() family of methods add containsKey() and
containsValue().
Complex Data Type Content Selection
Querying inside CDTs requires using the find() method on all CDTs. This is
accessible in the DSL via the corresponding find() family of functions on
LIST or MAP cell definitions, or Record.find() if
you wish to query on the entire Record.
These find() methods return variants of
BuildableTypedValueSelectionFunction. This hierarchy allows further refinement of
the resulting Selection via types, etc.
If at any point the DSL is used to restrict the subset to a typed scalar value, then that builder can be used with any of the existing DSL to construct possibly portable filters.
Update Operations
Update operation instances are used to express mutation used in either single-key update
operations, or against stream contents via a MutableRecordStream operation. Update
operations are created via static accessor methods on the UpdateOperation class
IntCellDefinition defnA = defineInt("cell-a");
IntCellDefinition defnB = defineInt("cell-b");
UpdateOperation<Long> install =
UpdateOperation.install(defnA.newCell(42), defnB.newCell(42)); // 1
UpdateOperation.CellUpdateOperation<Long, Integer> write =
UpdateOperation.write(defnA).value(42); // 2
UpdateOperation.CellUpdateOperation<Long, Integer> increment = // 3
UpdateOperation.write(defnA)
.intResultOf(defnA.intValueOr(0).increment());
UpdateOperation.CellUpdateOperation<Long, Integer> copy =
UpdateOperation.write(defnB).intResultOf(defnA.intValueOr(42));
UpdateOperation<Long> aggregate =
UpdateOperation.allOf(increment, copy); // 4
| 1 |
Install a specific list of cells. An install operation replaces all existing cells. |
| 2 |
Write an individual cell. This will overwrite an existing cell or create a new one as necessary. |
| 3 |
Write an individual cell with a value given by executing the given function against the current record. |
| 4 |
Perform a list of individual cell updates as a single unit. |
Update Output
Update operations output a pair of values representing the state before and after the mutation application. This is either in the form of a pair of values passed to a bi-function or as a tuple of records.
BiFunction<Record<?>, Record<?>, Integer> inputBiFunction =
UpdateOperation.input(defnA.valueOr(42)); // <1>
BiFunction<Record<?>, Record<?>, Integer> outputBiFunction =
UpdateOperation.output(defnA.valueOr(42)); // <2>
Function<Tuple<Record<?>, ?>, Integer> inputTupleFunction =
Tuple.<Record<?>>first().andThen(defnA.valueOr(42)); // <3>
Function<Tuple<?, Record<?>>, Integer> outputTupleFunction =
Tuple.<Record<?>>second().andThen(defnA.valueOr(42)); // <4>
| 1 |
Extract the input value of |
| 2 |
Extract the output value of |
| 3 |
Extract the value of |
| 4 |
Extract the value of |
Both tuple and bi-function forms follow the convention that input records are the first argument or tuple member, and output records are the second argument or tuple member.
Collectors
To support stream collection operations a mirror of the JDK
java.util.stream.Collectors class that creates
collectors transparent to TCStore at
com.terracottatech.store.function.Collectors.