Skip to content

feat: R2DBC DAO base structure#2751

Open
obabichevjb wants to merge 3 commits into
mainfrom
obabichev/r2dbc-dao-2
Open

feat: R2DBC DAO base structure#2751
obabichevjb wants to merge 3 commits into
mainfrom
obabichev/r2dbc-dao-2

Conversation

@obabichevjb
Copy link
Copy Markdown
Collaborator

Description

Summary of the change: Implements R2DBC DAO layer - a functional, suspending equivalent of Exposed's JDBC DAO API, enabling entity-based reactive database access with change tracking and relationships.

  • What:

    • Core Entity Classes: R2dbcEntity, R2dbcEntityClass
    • Automatic INSERT/UPDATE execution with dependency-aware flushing via R2dbcEntityCache and R2dbcEntityLifecycleInterceptor
    • Relationship Support: Relationship types implemented with suspending access but not completed and not well tested: referencedOnSuspend / optionalReferencedOnSuspend, referrersOnSuspend / optionalReferrersOnSuspend, backReferencedOnSuspend / optionalBackReferencedOnSuspend
    • Entities are automatically flushed before SELECT queries to ensure data consistency
  • How:

    • Architecture mirrors JDBC DAO but adapted for suspending operations:

      • JDBC: val city = person.city (blocking property access)
      • R2DBC: val city = person.city() (suspend function call via invoke operator)
      • Relationship setters use infix operators: person.city set newCity (non suspend)
    • Lifecycle interceptor (GlobalSuspendStatementInterceptor) automatically flushes pending entity changes before queries, sorted by foreign key dependencies using SchemaUtils.sortTablesByReferences()

Key Differences from JDBC DAO:

  • Many entity operations are suspend functions
  • Relationships return suspend lambdas accessed via invoke(): entity.relationship()
  • No batch update support yet (updates execute individually)
  • No subscription/entity change tracking yet
  • Uses R2DBC-specific transaction management

Similarities with JDBC DAO:

  • Similar entity definition syntax
  • Same relationship DSL (by EntityClass referencedOnSuspend Column)

Type of Change

  • New feature

Affected databases:

  • All

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Within this PR the main approach was in taking tests from jdbc's EntityTests and adopting them to r2dbc with implementing minimal functionality to cover these tests. Not so many tests are moved, but they cover many key features like relationships, entity cache, lifecycle interceptor and so on

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's the main difference in relationships comparing to jdbc. It allows to make non-suspend set(), and suspend get() (via invoke())

SuspendAccessor - is in the code, but actually not used, but OptionalSuspendAccessor is present in the tests

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was copied from jdbc dao for now

@obabichevjb obabichevjb force-pushed the obabichev/r2dbc-dao-2 branch from c6f3b4d to da5a2fe Compare March 4, 2026 20:07
@obabichevjb obabichevjb requested review from bog-walk and e5l March 6, 2026 10:02
Copy link
Copy Markdown
Member

@bog-walk bog-walk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's great how much can be successfully accomplished & its a promising step towards providing DAO + R2DBC support.

My main thought overall is the huge amount of duplication that enters into the public API. With DSL there was of course a lot duplicated, but a huge amount was also kept in the core, so the end API for new R2DBC users remained almost the same unless you were customizing classes. For Entity versus R2dbcEntity for example, it seems to so far only be refresh(), flush(), and lookup() that differs.

I thought that 1 of the possible goals of R2DBC + DAO was to redesign the jdbc-dao so that it could be used common to both approaches. In the same way that a Table and most DSL non-executables can be shared by both, I hoped it might be possible to use the same Entity across both. With the executable methods (like save, refresh, update etc) extracted to interface implementations. So that a project could use both drivers together, even temporarily as they migrate or test r2dbc in parts of code.
With this approach, they'd instead have to either change their existing entities (just 1 change is nice though) or copy-paste new entities that implement R2dbcEntity. In the end, it's not really a problem, I'm just surprised that it's not possible to share code given how much similarity this semi-final product has.

Would it not at all be possible to have an exposed-dao-core module that is depended on by both? I suppose it would technically be a breaking change even if change wasn't felt in final API, but I don't think that should be a deterrent from trying a POC like that.

Comment thread build.gradle.kts Outdated
Comment thread exposed-dao-r2dbc/src/main/kotlin/org/jetbrains/exposed/r2dbc/dao/R2dbcEntity.kt Outdated
Comment thread exposed-dao-r2dbc/src/main/kotlin/org/jetbrains/exposed/r2dbc/dao/R2dbcEntity.kt Outdated
Comment on lines 132 to 133
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another question: What is wrong/incompatible with the existing EntityTypeChange implementation that jdbc-dao uses?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I hope to bring it in the next PR with subscribtions if it's ok, this PR was getting bigger and bigger already

Comment thread exposed-dao-r2dbc/src/main/kotlin/org/jetbrains/exposed/r2dbc/dao/LongEntity.kt Outdated
Copy link
Copy Markdown
Member

@e5l e5l left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the new api looks nice for the further implementation. Please check the package name

@obabichevjb obabichevjb force-pushed the obabichev/r2dbc-dao-2 branch 2 times, most recently from 1e4ab80 to 55e0a07 Compare April 13, 2026 12:16
@obabichevjb obabichevjb requested a review from bog-walk April 13, 2026 12:21
@obabichevjb obabichevjb force-pushed the obabichev/r2dbc-dao-2 branch from 55e0a07 to f0e3044 Compare April 17, 2026 06:16
@HacktheTime
Copy link
Copy Markdown

I have had troubles with the jdbc version due to to my project being multi threaded. Worked for a long time but has become problematic recently. Due to this I would be interested in a rough ETA on how long its expected to take to be useable by me. IDM If I have to spent a bit more time if the Stuff is experimental and is still being changed.

I am more than aware that these ETAs can change but the current expectation would be really nice to know to order in what my next best step is.

@HacktheTime
Copy link
Copy Markdown

@obabichevjb above question

@obabichevjb
Copy link
Copy Markdown
Collaborator Author

@HacktheTime The current PR doesn't affect existing jdbc dao, we're going to avoid breaking chnages within 1.x versions. So if you have particular issue (ideally if it's reproducable), I could look at that.

r2dbc dao has too many open questions now, and first of all in terms of public api, so it's not easy to say particular dates. The main questions - where should be suspending points in that dao, because jdbc dao could make db requests in almost any time (like reading from or wrinting in entity fields), what could not be reflected on r2dbc as it is.

@HacktheTime
Copy link
Copy Markdown

@obabichevjb

Idk if the Issue is fixable at all due to me using multi threading but sure.

I think a code with me session would make the most sense so you can look at the code + talking for explenation. I dont have minimal reproduction code and I dont know how I could make some either.

But it might be related to this issue: #2411

Also I saw that a suspend cancel caused the connection not to get cleaned in the commits but I dont think there is a new publish with that fix yet so that thing could potentially help as well.

@HacktheTime
Copy link
Copy Markdown

HacktheTime commented May 2, 2026

@obabichevjb

I had a look at the processlist and I noticed the following. It got stuck on an update statement.

Edit: also got one on insert but state says updating:
| Id | User | Host | db | Command | Time | State | Info | Progress |
| 1420 | user | localhost:port | bingonet | Query | 18 | Update | INSERT INTO .... | 0.000 |

mariadb said that there were no other processes with a statement. they were sleepy but for that 1.

noteworthy is that i also had a deadlock even trying to delete it to avoid updating and instead getting a creation. even when it deadlock errored and thereby was free again. so i think the issue i linked might be very related to the issue.

obabichevjb and others added 3 commits May 19, 2026 11:32
* feat: eager loading, many-to-many

* feat: Complete migration of EntityTests

* feat: Extract trimToFirst
@obabichevjb obabichevjb force-pushed the obabichev/r2dbc-dao-2 branch from ef9194b to 0f18f7a Compare May 19, 2026 09:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants