- Type Parameters:
OPTYPE- The parameter type of the actual operation which will be used to hold all the details for executing an operation, generally something that implementsRunnable.
- All Known Implementing Classes:
AmqpOpMapper,AzureAISearchOpMapper,Cqld4BaseOpMapper,CqlD4BatchStmtMapper,Cqld4CoreOpMapper,Cqld4CqlBaseOpMapper,Cqld4CqlOpMapper,CqlD4CqlSimpleStmtMapper,Cqld4FluentGraphOpMapper,Cqld4GremlinOpMapper,CqlD4PreparedStmtMapper,CqlD4RainbowTableMapper,CqlD4RawStmtMapper,DataApiOpMapper,DiagOpMapper,DynamoDBOpMapper,ExampleOpMapper,GCPSpannerOpMapper,HttpOpMapper,KafkaOpMapper,MongoOpMapper,Neo4JOpMapper,PulsarOpMapper,QdrantOpMapper,S4JOpMapper,StdoutOpMapper,TcpClientOpMapper,TcpServerOpMapper,WeaviateOpMapper
Synopsis
An OpMapper is responsible for converting parsed op templates into dispensers of operations based on the intention of the user.Op Templates as expressed as a set of field values, some literal, and some dynamic, to be generated based on a specific cycle value.
Concepts
The OpMapper is basically a function that returns another function. The responsibility for creating executable operations is shared between theOpMapper and the
OpDispenser. The logic needed to determine the type of an operation intended
by the user (mapping) is different from the logic you use to construct that specific
type of operation once you know the intent (dispensing). If you look at a example
of doing these together in code, there is always a point at which you know what is
needed to construct an operation. If you draw a line at this point, it represents
the separation of responsibilities between op mappers and op dispensers.
This separation of responsibilities serves as both a conceptual clarification as well as a way to optimize runtime behavior. In the NoSQLBench model, all of the first step (mapping, the responsibility of this class) occurs at initialization time of an activity. This means that mapping logic can be as clear, readable, type-safe and obvious as possible without any negative effect on the later phase. In fact, clarity and obviousness at this level serves to keep implementations of the next phase much more straight-forward and streamlined, since all that is left to do is assemble the known elements together into an executable operation.
Implementation Strategy
A view of an op template is provided in the ParsedOp API. This allows
you to examine the fields provided by users. It also lets you see which
of these fields are defined as dynamic and which are simply static values.
When multiple types of operations are supported for a driver adapter, you must decide
on a distinct signature
Documentation Requirements
The logic which is implemented in the OpMapper must follow closely with the op construction rules provided to the user. Conversely, the driver maintainer should take care to provide rules of construction and examples in the documentation. EachDriverAdapter has a unique
name. The documentation
for each of these should be kept in the bundled resources in a top-level markdown file that
matches the driver name.
-
Method Summary
Modifier and TypeMethodDescriptionOpDispenser<? extends OPTYPE> apply(NBComponent adapterC, ParsedOp pop, LongFunction<SPACETYPE> spaceF) This method is responsible for interrogating the fields in the providedParsedOptemplate object, determining what adapter-specific operation it maps to, and creating the associatedOpDispenserfor that type.
-
Method Details
-
apply
OpDispenser<? extends OPTYPE> apply(NBComponent adapterC, ParsedOp pop, LongFunction<SPACETYPE> spaceF) This method is responsible for interrogating the fields in the providedParsedOptemplate object, determining what adapter-specific operation it maps to, and creating the associatedOpDispenserfor that type.Implementation Notes
It is important to be familiar with the structure of the
ParsedOp, since this is the runtime model API for an op template. It provides everything you need to turn various op fields into proper lambdas, which can then be composed together to make higher-order lambdas. The returnedOpDispenseris essentially a singularLongFunctionwhich captures all of the just-in-time construction patterns needed within.Op Mapping
Generally speaking, implementations of this method should interrogate the op fields in the ParsedOp to determine the specific op that matches the user's intentions. It is Highly reccommended that each of the valid op types is presented as an example in the associated adapter documentation. (Each adapter must have a self-named markdown help file in it's source tree.) Good op mappers are based on specific examples which are documented, as this is the only way a user knows what op types are available.
What determines the type of op can be based on something explicit, like the value of a
typefield, or it can be based on whether certain fields are present or not. Advanced implementations might take into account which fields are provided as static values and which are specified as (dynamic) bindings. The op mapping phase is meant to qualify and pre-check that the fields provided are valid and specific for a given type of operation.All of the effective logic for op mapping must be contained within the
apply(NBComponent, ParsedOp, LongFunction)method. This includes what happens within the constructor of anyOpDispenser. What happens withinOpDispenserimplementations (the second phase), however, should do as little qualification of field values as possible, focusing simply on constructing the type of operation for which they are designed. This suggest the following conventions:- Type-mapping logic (determine which op type) is done in the main body of
apply(NBComponent, ParsedOp, LongFunction), and nothing else. Once the desired op dispenser (based on the intended op type) is determined, it is immediately constructed and returned. - Lambda-construction logic is contained in the constructor of the returned op dispenser. This pre-bakes as much of the op construction behavior as possible, building only a single lambda to do the heavy lifting later.
- When
LongFunction.apply(long) is called with a cycle value, it only needs to call the lambda to
return a fully-formed op, ready to be executed via its
CycleOp.apply(long)method.- Parameters:
adapterC- The adapter component. This is passed as anNBComponentbecause it is not valid to rely on the driver adapter instance directly for anything. All logic for an op should be captured in its mapper and dispenser, and all (other) state for it should be captured within the space. However, the mapper exists within the nosqlbench runtime as part of the component tree, so it is included for that reason alone. (You'll need it for super construtors in some cases)pop- TheParsedOpwhich is the parsed version of the user-provided op template. This contains all the fields provided by the user, as well as explicit knowledge of which ones are static and dynamic. It provides convenient lambda-construction methods to streamline the effort of creating the top-level op lambda.spaceF- This is the pre-baked lambda needed to access the specificOpMapperfor a given cycle, if or when it is needed. Not all op types need this, since they may have all the state needed fully captured within the native type. For those that do, ensure that you are accessing the value through this function lazily and only within the stack. Using this function to do anything but build more lambdas is probably a programming error.- Returns:
- An OpDispenser which can be used to synthesize directly executable operations.
- Type-mapping logic (determine which op type) is done in the main body of
-