Fix repated typos in comments: more then -> more than, less then -> less than Signed-off-by: Yaniv Kaul <yaniv.kaul@scylladb.com> Closes scylladb/scylladb#17303
263 lines
11 KiB
Markdown
263 lines
11 KiB
Markdown
# IDL compiler
|
|
|
|
IDL compiler is a tool written in Python, that generates utility serialization/de-serialization code (specializations
|
|
for `ser::serializer<T>` class) for C++ classes and enums. It takes an IDL definition file as input and generates two
|
|
files from it: `<mod_name>.dist.hh`, which is declarations part, and `<mod_name>.dist.impl.hh`, which contains generated
|
|
code definitions (private part).
|
|
|
|
The syntax of IDL is similar to C++ to some degree (e.g. contains `struct/class` and `enum class` constructs), but is
|
|
also extended to support more complex things in the context of RPC serialization: RPC messages (also called
|
|
"RPC verbs").
|
|
|
|
By default, all generated code for Scylla is created under `ser` namespace.
|
|
|
|
## File syntax description
|
|
|
|
As noted above, the syntax of IDL definitions file resembles C++ by providing similar-looking code constructs for class
|
|
and enum serializers.
|
|
|
|
The IDL file contains a sequence of includes and entity declarations, most of which correspond directly to those from the actual
|
|
C++ code:
|
|
|
|
- Includes (`include`)
|
|
- Namespaces (`namespace`)
|
|
- Classes / structures (`class` / `struct`)
|
|
- Enums (`enum class`)
|
|
- RPC verbs (`verb`)
|
|
|
|
Classes can be templated, supporting the ordinary `template <typename T>`-style notation at the beginning of the class
|
|
declaration, similar to C++.
|
|
|
|
Some places (e.g. classes, class members and RPC verbs) also support modifying generation behavior by providing
|
|
attribute sequences (via C++ syntax of `[[attr]]` form).
|
|
|
|
### Including files nad modules
|
|
|
|
Data types that are defined elsewhere can be used in IDL definition files.
|
|
|
|
To include definitions from C++ headers, use the `#include "filename.hh"` statement.
|
|
The idl compiler generates a respective `#include "filename.hh"` directive in the generated `<mod_name>.dist.hh` file.
|
|
|
|
To import definitions from other IDL definition files, use the `#include "idl/<other_mod_name>.idl.hh"` statement.
|
|
The idl compiler generates a respective `#include "idl/<other_mod_name>.dist.hh"` directive in the generated `<mod_name>.dist.hh` file
|
|
and a `#include "idl/<other_mod_name>.dist.impl.hh"` directive in the generated `<mod_name>.dist.impl.hh` file
|
|
|
|
### Namespaces
|
|
|
|
Namespaces in IDL act the same way as in C++ or any other programming language, and can have arbitrary nesting depth.
|
|
Syntax:
|
|
|
|
```
|
|
namespace <ns-name> { <namespace-body> }
|
|
```
|
|
|
|
Where:
|
|
|
|
- `ns-name` — namespace identifier. Directly corresponds to the namespace from C++, defining a new namespace or
|
|
extending an existing one.
|
|
- `namespace-body` — a sequence of 0+ nested entities of any kind: classes, enums, RPC verbs or other nested
|
|
namespaces.
|
|
|
|
Example:
|
|
|
|
```
|
|
namespace ns {
|
|
|
|
class my_class {
|
|
int a;
|
|
bool b;
|
|
std::vector<int> c;
|
|
};
|
|
|
|
enum class e {
|
|
E1,
|
|
E2
|
|
};
|
|
|
|
// ...
|
|
|
|
} // namespace ns
|
|
```
|
|
|
|
### Classes
|
|
|
|
Class declaration creates a `ser::serializer<T>` specialization для for a class with a given name, along with an
|
|
implementation of `write`, `read` and `skip` methods. Syntax:
|
|
|
|
```
|
|
template <template-parameter-list> (optional)
|
|
class-key <class-name> final(optional) attributes_seq(optional) stub(optional) { <nested-class> | <member-specification> } ;(optional)
|
|
```
|
|
|
|
Where:
|
|
|
|
- `template-parameter-list` - the list of template arguments in case a class is a template.
|
|
- `class-key` - either `class`, or `struct`.
|
|
- `class-name` - class identifier, for which `ser::serializer<T>` specialization is to be created.
|
|
- `final` specifier — an optimization option, which denotes that a class is final, i.e. cannot be extended in the
|
|
future, in which case the size is not serialized. Should be used with care.
|
|
- `stub` specifier — skip generating serialization code for a class. Initially designed for documentation within IDL
|
|
(there is one exception to that with `[[writable]]` attribute, though. More on that below).
|
|
- `attributes_seq` - optional sequence of C++-style attributes. Only `[[writable]]` attribute is supported at the
|
|
moment, which means that: if specified, the writers and serialization views will also be generated for a class
|
|
(inside the private `<mod_name>.dist.impl.hh` part). Other attributes are ignored.
|
|
- `nested-class` - nested class definition following the same syntax.
|
|
- `member-specification` — data members and getter functions declarations (more details on the syntax below). Getter
|
|
functions should be used in cases where access to private class fields is needed. Both kinds of field accessors can
|
|
also be marked with `[[version id]]` version attribute, which denotes, that a field is accessible starting from
|
|
version `id`.
|
|
|
|
A class declaration can optionally include a semicolon at the end to more closely resemble C++ syntax.
|
|
|
|
If a class contains both `stub` specifier and `[[writable]]` attribute at the same time, the `ser::serializer` code is
|
|
not generated but the serialization views (classes with `_view` suffix in the name, that support reading and writing
|
|
data in the stream according to some fixed data layout) are created, nonetheless.
|
|
|
|
Class members are declared the following way:
|
|
|
|
```
|
|
<type> name <getter-marker>(optional) [[version version-id]](optional) <default-value>(optional);
|
|
```
|
|
|
|
Where:
|
|
|
|
- `type` - any valid C++ type, following the regular C++ syntax. Naturally, a serializer specialization for this
|
|
type should exist in order to serialize/deserialize it.
|
|
- `name` - accessor name. For ordinary data fields it is just a C++ name of a class field. In case it's a getter
|
|
function, it also should contain a "getter marker", which is denoted as empty `()` braces sequence right after the
|
|
name. As noted above, getter functions should be used if a field is not accessible (i.e. is private), otherwise a
|
|
regular data field can be used. Note, that getter functions can (and probably should) be const methods.
|
|
- version attribute — specify that a field is accessible starting from version `version-id` and above.
|
|
- `default-value` — an optional clause to specify default value for a field accessor. The syntax is: `= <value>`.
|
|
|
|
### Enums
|
|
|
|
Analogous to classes, `ser::serializer<T>` specializations can also be generated for enums. Declaration syntax:
|
|
|
|
```
|
|
enum class identifier enum-base { enumerator-list } ;(optional)
|
|
```
|
|
|
|
Where:
|
|
|
|
- `identifier` - the name of C++ enum class.
|
|
- `enum-base` - mandatory specification of C++ underlying type for the enum, following the regular C++ syntax: `: integer-type`
|
|
- `enumerator-list` - a list of enum cases or initializers of the following form: `name = integer-value`, where
|
|
`integer-value` is a plain integer literal value.
|
|
|
|
Note that although C++ allows `constexpr` as an initializer value, it makes the documentation less readable, hence is
|
|
not permitted.
|
|
|
|
### RPC Verbs
|
|
|
|
IDL can also contain declarations of RPC messages with a given signature (also called: "RPC verbs"). It allows to
|
|
automatically generate boilerplate code for message handlers registration and message passing code via an instance of
|
|
`netw::messaging_service` class. Declaration syntax:
|
|
|
|
```
|
|
verb id [[attributes...]](optional) (parameters...) (-> return-type...)(optional) ;(optional)
|
|
```
|
|
|
|
There should be a corresponding upper-cased enumerator `ID` inside the `netw::messaging_verb` enum for a verb with name
|
|
`id`. For example, for a `my_verb` declaration there should be a corresponding `netw::messaging_verb::MY_VERB` constant
|
|
to specify an id for the RPC client.
|
|
|
|
The parameters of the verb declarations will also act as parameters for the handler functions and corresponding `send`
|
|
functions. In case `[[with_timeout]]` attribute is set, the argument list is extended with a `time_point` argument at the
|
|
beginning of the parameter list to specify an RPC timeout when sending or handling the message.
|
|
|
|
The return value type is calculated as `future<return_type>` if only one type is present and
|
|
`future<rpc::tuple<type1, type2, ...>>` if there are more than one. It is used as return type for message handler and `send`
|
|
function. If the `-> return type` clause is omitted, the return type is assumed to be `future<>`. If `[[one_way]]`
|
|
attribute is specified, handler function return type is fixed to `future<rpc::no_wait_type>`, and the return type
|
|
clause should not be used, otherwise an exception will be thrown during IDL generation process.
|
|
|
|
If `[[with_client_info]]` attribute is used then the handler will contain a const reference to an `rpc::client_info` as
|
|
its first argument.
|
|
|
|
`[[with_client_info]]`, `[[with_timeout]]` and `[[one_way]]` attributes can be combined.
|
|
|
|
For an RPC verb with the definition of `verb x (arg1_t, arg2_t) -> ret_t;` , which is defined in some `my_mod.idl.hh`
|
|
module, the following `my_mod_rpc_verbs` class will be generated (approximately):
|
|
|
|
```cpp
|
|
// my_mod.dist.hh
|
|
|
|
namespace ser {
|
|
|
|
struct my_mod_rpc_verbs {
|
|
static void register_x(netw::messaging_service* ms, std::function<future<ret_t> (const rpc::client_info&, arg1_t, arg2_t>&&);
|
|
static future<> unregister_x(netw::messaging_service* ms);
|
|
static future<ret_t> send_x(netw::messaging_service* ms, netw::msg_addr id, arg1_t, arg2_t);
|
|
|
|
// calls unregister_x, and the same for other verbs, if there are any,
|
|
// and waits for all of them to resolve.
|
|
static future<> unregister(netw::messaging_service* ms);
|
|
};
|
|
|
|
} // namespace ser
|
|
```
|
|
|
|
Each parameter can optionally have a name, otherwise a placeholder name of form `_N` will be used, where `N` is the
|
|
index within the RPC verb parameters list. Also, each argument and return type can be annotated with `[[version id]]`
|
|
attribute, which will cause it to be accepted as a `rpc::optional<>` in the handler function signature or returned by send()
|
|
function respectively,
|
|
|
|
If a parameter is annotated with [[ref]] attribute send function will receive it as a const reference, but a handler
|
|
will still receive it by value.
|
|
|
|
Return type value can be also annotated with `[[unique_ptr]]` or `[[lw_shared_ptr]]` attributes. If the attribute is present
|
|
handler function's return value will have the type as `foreign_ptr<unique_ptr<>>` and `foreign_ptr<lw_shared_ptr<>>` respectively.
|
|
|
|
## IDL example
|
|
Forward slashes comments are ignored until the end of the line.
|
|
```
|
|
namespace utils {
|
|
// An example of a stub class
|
|
class UUID stub {
|
|
int64_t most_sig_bits;
|
|
int64_t least_sig_bits;
|
|
}
|
|
}
|
|
|
|
namespace gms {
|
|
//an enum example
|
|
enum class application_state:int {STATUS = 0,
|
|
LOAD,
|
|
SCHEMA,
|
|
DC};
|
|
|
|
// example of final class
|
|
class versioned_value final {
|
|
// getter and setter as public member
|
|
int version;
|
|
sstring value;
|
|
}
|
|
|
|
class heart_beat_state {
|
|
//getter as function
|
|
int32_t get_generation();
|
|
//default value example
|
|
int32_t get_heart_beat_version() = 1;
|
|
}
|
|
|
|
class endpoint_state {
|
|
heart_beat_state get_heart_beat_state();
|
|
std::map<application_state, versioned_value> get_application_state_map();
|
|
}
|
|
|
|
class gossip_digest {
|
|
inet_address get_endpoint();
|
|
int32_t get_generation();
|
|
//mark that a field was added on a specific version
|
|
int32_t get_max_version() [ [version 0.14.2] ];
|
|
}
|
|
|
|
class gossip_digest_ack {
|
|
std::vector<gossip_digest> digests();
|
|
std::map<inet_address, gms::endpoint_state> get_endpoint_state_map();
|
|
}
|
|
}
|
|
```
|
|
|