r2dbc / r2dbc-proxy

R2DBC Proxying Framework
https://r2dbc.io
Apache License 2.0
145 stars 21 forks source link

Propagate Contextview to ConnectionInfo and StatementInfo #127

Closed gkatzioura closed 1 year ago

gkatzioura commented 1 year ago

Feature Request

Propagate ContextView to ConnectionInfo and StatementInfo

Is your feature request related to a problem? Please describe

There cases where the logic on a BindParameterConverter might require the information Stored in a ContextView.

Describe the solution you'd like

Propagating the ContextView from the MethodExecutionInfo to the ConnectionInfo and the StatementInfo will make it possible.

Describe alternatives you've considered

Checked on the Listener based methods which do provide access to the ContextView, however in order to modify the query it was possible only through a BindParameterConverter.

Teachability, Documentation, Adoption, Migration Strategy

Create a custom BindParameterConverter

public class CBindParameterConverter implements BindParameterConverter {
    @Override
    public String onCreateStatement(String query, StatementInfo info) {
        ContextView contextView = info.getValueStore().get(ContextView.class, ContextView.class);
        if(contextView!=null) {
            UUID key = contextView.get("val");
            return query+"/*a comment for "+key+"*/";
        }

        return query;
    }

}

Add the BindParameterConverter through the Factory

            ProxyConfig proxyConfig = ProxyConfig.builder()
                    .bindParameterConverter(new CBindParameterConverter())
                                                 .build();
            return ProxyConnectionFactory.builder(ConnectionFactories.get(options),proxyConfig)
                                         .listener(new R2DBCCommenterListener())
                                         .build();
ttddyy commented 1 year ago

@gkatzioura Thanks for the request and PR. I need to think through it.

The difficulty is a scoping and difference between methods that return a publisher and normal return types.

For example, the order of executing a query is:

  1. ConnectionFactory#create
  2. Connection#createStatement
  3. Statement#bind
  4. Statement#execute

In this case, 1 and 4 return a publisher, and 2 and 3 return a non-publisher. If this is grouped by composing publishers, the first group is 1, and the second group is 2,3,4. Then, in the implementation of the PR(#128), it grabs the ContextView from 1 and passes it to 2, which is in a different publisher group.

So, if I do:

Mono.from(proxyConnectionFactory.create()).contextWrite(ctx -> ctx.put("foo", "FOO"))    // 1
  .flatMapMany(connection -> 
    Mono.from(connection
                .createStatement("SELECT id, name FROM emp WHERE id = ?")      // 2
                .bind(0, 20)                                                   // 3
                .execute())                                                    // 4
                .contextWrite(ctx -> ctx.put("bar", "BAR"))
    ...
  .contextWrite(ctx -> ctx.put("baz", "BAZ"))
  ...

The createStatement method is called in order to compose the publisher from the execute method. So, the method callback of createStatement receives a context that contains foo=FOO seems strange because they are in different publisher groups. Though, another difficulty is that createStatement cannot receive a context "bar=BAR" because the context is only available after createStatement is invoked.

ttddyy commented 1 year ago

@gkatzioura I just committed a change to populate reactor context in various ValueStore.

This documentation illustrates what values are available in where. For your case, the ValueStore in ConnectionInfo will have access to the reactor Context which wrote values to the entire workflow.