Dear R2DBC Community,

We released the first milestone of the R2DBC 0.9 specification (0.9.0.M1). This milestone marks a leap forward in the evolution of R2DBC towards the future goal of the 1.0 release. This milestone contains numerous changes, of which the two most significant are:

  • Extensible Transaction Definitions
  • Improved Bind-Parameter Declaration

With the feedback that we’ve collected from the driver community and users, these are the major topics driving the 0.9 specification line development. R2DBC 0.9 aims for additive SPI changes so that only R2DBC implementations require updates to their libraries while consumers of R2DBC 0.9 can remain unchanged.

Extensible Transaction Definitions

Extensible transaction definitions provide a standardized API to improve the definition of transactions and to make use of vendor-specific transaction attributes. Transactions SQL databases are characterized by isolation levels, transaction mutability, and often more attributes. The typical flow of a transaction requires:

  1. Disabling auto-commit
  2. Setting the isolation level
  3. Starting the actual transaction
  4. (Optionally) Setting vendor-specific transaction attributes

The typical flow is comprised of two to many database interactions. With extensible transaction definitions, callers can provide a transaction definition to an R2DBC implementation. The implementation can initiate the transaction by considering all defined attributes, which typically results in fewer roundtrips that primarily affect performance in networked arrangements.

At its core, extensible transaction definitions accept an object that implements TransactionDefinition. These objects can be provided by an R2DBC implementation, a framework, or application code:

interface TransactionDefinition {

    @Nullable
    <T> T getAttribute(Option<T> option);
}

Implementations query TransactionDefinition for known options. R2DBC SPI ships a few pre-defined options (ISOLATION_LEVEL, READ_ONLY, NAME, and LOCK_WAIT_TIMEOUT). Additionally, implementations may define their own options to which to react, such as consistency levels, snapshot references, rollback segments, etc.

Besides potential performance improvements and atomic transaction definition blocks, extensible transaction definitions let drivers route transactions to particular nodes if a database cluster is built from read-write and read-only nodes.

Improved Bind-Parameter Declaration

With the first release of R2DBC 0.8, the specification declared an API to bind parameters when using parameterized SQL statements. A caller can bind either a non NULL value (given the parameter bind marker and its parameter value) or bind a NULL value (given the parameter bind marker and its parameter type). This arrangement works for the majority of cases. We’ve learned from the community that there is a need to improve on that API. There are a couple of assumptions that do not render true for many cases:

  • The actual database type is derived from a value or class type
  • Calling the API requires upfront knowledge of whether the parameter is null

Type Derivation

Database types can be tricky, as a database type may map to more than a single Java type. A typical examples is String, which can be represented as CHAR, VARCHAR, NVARCHAR, or even JSON. To work around this aspect, implementations need to make a decision that requires either configuration or additional vendor-specific types to express which type to use. While the representation of the value in a database protocol can be the same between the types above, the type identifier tells the database which data type it should expect, and in some cases, JSON and VARCHAR are not compatible even if the String contains a valid JSON string.

R2DBC 0.9 specifies a Parameter abstraction to encapsulate the value and the type of a bind parameter. Bind parameters can be wrapped with a Parameter and bound to a parameter bind marker:

interface Parameter {

    Type getType();

    @Nullable
    Object getValue();

}

Implementations can evaluate the Type to determine the appropriate value type. Type can be any one of the 25 pre-defined R2DBC types or a vendor-specific implementation pointing to a vendor-specific or user-defined type.

This revision introduces a utility class (Parameters) to define an entry point that lets callers of the R2DBC SPI create a wrapped Parameter. Parameter instances can still use type derivation or accept a Type argument to specify the database type.

Parameter Nullability

A characteristic of the R2DBC 0.8 API was that Statement.bind(…) accepts only non-null parameters, while NULL parameters must be bound through the Statement.bindNull(…) method. This arrangement requires the calling code to use conditional code for calling the appropriate method resulting in hard-to-read code.

Introducing the Parameter abstraction and Parameters utility methods, the calling code can now specify nullable parameter values if the database type is selected:

String value = … ;// can be null
statement.bind(0, Parameters.in(R2dbcType.VARCHAR, value));

R2DBC drivers have all information to either bind the value to the statement or bind a typed NULL value, while the calling code is no longer required to differentiate between bind vs. bindNull methods. Instead, nullability is encapsulated as part of the Parameter value.

The Parameter abstraction is mainly driven by marker interfaces to indicate to a driver whether a parameter type should be inferred or whether the parameter type is an in, out, or in-out parameter. Distinguishing between in and out parameters is required for certain stored procedure invocations and out parameters are subject to type information.

Drivers

Now that the specification’s first milestone is released, we ask driver and library authors to pick up that milestone and provide implementations for the specification enhancements. We also ask for feedback, ideally through either the mailing list or as GitHub issues in the specification repository.

As of now, the following components have started adopting the changes:

  • R2DBC Proxy 0.9.0.M1
  • R2DBC Pool 0.9.0.M1
  • Postgres driver 0.9.0.M1 (note that the groupId has changed from io.r2dbc to org.postgresql)
  • SQL Server driver 0.9.0.M1

Once most drivers have upgraded to R2DBC SPI 0.9, we can ship a BOM with one of the future milestones for easier consumption.

Roadmap

The spec team aims for at least one more milestone (M2) before entering the RC phase. If necessary, we will schedule additional milestones depending on the feedback we’re looking for from R2DBC driver authors, integrators, and direct R2DBC SPI users.

As per the proposed schedule (https://groups.google.com/g/r2dbc/c/aN7j8RIvtBQ) we would like to relax the schedule to aim for a 0.9 release in September:

  • 0.9 M1: February 2021
  • 0.9 M2: May 2021
  • 0.9 RC1: August 2021
  • 0.9 GA: September 2021

Once 0.9 GA has proven stable, we envision a 1.0 GA release in early/mid-2022 with the same content as the 0.9 GA (or slight refinements). 1.0 gives drivers and implementations the opportunity to upgrade to the latest dependencies as a final step before shipping a major version increment. R2DBC 1.0 signals that the specification is feature-complete for the next few years (or until a strong need to include additional specification elements arises).