-
Notifications
You must be signed in to change notification settings - Fork 18
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: SQL Server / mssql dialect support #494
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
a few high level comments before getting into more details...
INSERT INTO $journalTable | ||
(slice, entity_type, persistence_id, seq_nr, writer, adapter_manifest, event_ser_id, event_ser_manifest, event_payload, tags, meta_ser_id, meta_ser_manifest, meta_payload, db_timestamp) | ||
OUTPUT inserted.db_timestamp | ||
VALUES (@slice, @entityType, @persistenceId, @seqNr, @writer, @adapterManifest, @eventSerId, @eventSerManifest, @eventPayload, @tags, @metaSerId, @metaSerManifest, @metaSerPayload, @dbTimestamp)""" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should use positional indexes for binding the values, same as we use for the other dialects. Slightly more efficient, and by using the same approach we can reuse much more code between the dialects.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Value binding in this specific driver does not allow positional index binding based on ?
s. The only alternative to what is currently used is positional binding zero-based - so numbers instead of ?
s.
Unless I misunderstand, this means we can not reuse code related to queries definition / binding.
If have no problem with moving to zero-based binding for this PR if you still suggest this is the preferred approach. I just think named binding is much more readable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For now I kept binding using named param as this seems to be recommended by the plugin: https://github.com/r2dbc/r2dbc-mssql/blob/main/src/main/java/io/r2dbc/mssql/MssqlStatement.java#L29
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alright, so you mean that the SqlServer itself uses named parameters in the actual requests sent to the server? That is different to Postgres, which uses positional parameters, and the translation from name to index happens in the client. Maybe the opposite translation from index to name isn't too bad for SqlServer and we could still use the same approach? The advantage of using the same approach would be that much more code would be the same (no need for special bind methods for all sql).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I agree. If the query is similar enough to re-use the binding based on positional index and then using some customization (e.g. for timestamps or tags) that would work out nice.
I will keep this in mind for later, once we have a direction regarding the other discussion below.
private[r2dbc] class SqlServerJournalDao(settings: R2dbcSettings, connectionFactory: ConnectionFactory)(implicit | ||
ec: ExecutionContext, | ||
system: ActorSystem[_]) | ||
extends JournalDao { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can't we extend the Postgres Dao and only override the bare minimum, in the same way we do for h2 and yugabyte? If there are some things that are different and need more specific override we can add such methods to be overridden in the Postgres Dao. It's internal api so we are free to refactor as needed when adding new dialect.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I can look into that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can't we extend the Postgres Dao and only override the bare minimum
@patriknw I took your comment serious and added a big refactoring to demonstrate how we could separate the actual sql query definition+binding (which is dialect specific) from the logic+error handling (which is not dialect specific and which should be reused as proposed). This is currently only done for SqlServerDurableStateDao
and SqlServerJournalDao
which I recommend to look at first as they show the real benefit of this change.
My last commit to this pr is rather big and is only a proposal at this point, but it shows how this could look like. If you agree with this direction, I would probably take a step back and create a separate pr from the current main
and add this kind of decoupling for postgres and then rebase this pr on that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see how that looks more structured by having clear definitions of all sql and bind methods together. On the other hand I'm worried that this becomes over-designed and just adds more ceremony and makes it more difficult to understand via the additional indirection. It's like we have two DAO layers?
What is the benefit compared to placing the sql and bind methods in the Dao itself? I can understand that it might feel wrong to use PostgresDao as the base, so we could extract that (all common parts) to a BaseDao.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Extracting common parts to a BaseDao would make sense to me if we agree that the overhead (of separating the sql and bind methods from the actual logic and error handling) is worth it.
Maybe adding a new dialect is not worth the complexity / overhead it seems to introduce? I definitely do not want to open up a can of worms without getting enough value back here, so please share you thoughts.
What is the benefit compared to placing the sql and bind methods in the Dao itself?
I actually also prefer the current approach, where the dao contains the sql and binding. Hover, if we keep that approach, it would mean I would have to duplicate lots of logic. Example is your recent contribution. Without the refactoring I would have to basically copy what you have added / changed in that pr for postgres and re-implement it in the sqlserver dialect.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After looking and thinking about it for a bit I lean towards preferring (reasonable) duplication with extending the PostgresDAO (and possibly making more things there possible to override) over the extra clever abstraction for now. Once/if we add two-three more dialects we can reconsider.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
and possibly making more things there possible to override
This is key to the pragmatic approach that I'm advocating. We evolve it and extract pieces and methods to override as we need, but not before we need it.
The way I would like to see it is that we extend PostgresDao and override only the things that are different for SqlServer. Still with sql in the Dao, and bindings in the Dao, where the bindings could be separate methods to be overridden when it's needed. That approach was quite successful when adding the H2 dialect, and I don't see that SqlServer would be so much different.
Then moving most of PostgresDao to BaseDao is pretty much just a naming cleanup that we can make separately afterwards.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good. I may start a new PR based on the learnings from this one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, don't hesitate to share a small early draft to make sure we are aligned.
val conf = ConfigFactory.parseString("""{ | ||
| tag-separator = "|" | ||
|} | ||
|""".stripMargin) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
such margin char isn't needed
@@ -80,7 +80,11 @@ class PersistTimestampSpec | |||
Row( | |||
pid = row.get("persistence_id", classOf[String]), | |||
seqNr = row.get[java.lang.Long]("seq_nr", classOf[java.lang.Long]), | |||
dbTimestamp = row.get("db_timestamp", classOf[Instant]), | |||
dbTimestamp = if (settings.dialectName == "sqlserver") { | |||
row.get("db_timestamp", classOf[LocalDateTime]).atZone(ZoneId.systemDefault()).toInstant |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I haven't read up on sqlserver timestamps, but the principle should be that we always use UTC
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes this is what I have done. I just missed migrating it in this test, good catch!
tags NVARCHAR(255), | ||
|
||
meta_ser_id INTEGER, | ||
meta_ser_manifest NVARCHAR(MAX), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
255 should be enough here
@@ -26,6 +26,10 @@ object Dependencies { | |||
val r2dbcPool = "io.r2dbc" % "r2dbc-pool" % "1.0.1.RELEASE" // ApacheV2 | |||
val r2dbcPostgres = "org.postgresql" % "r2dbc-postgresql" % "1.0.3.RELEASE" // ApacheV2 | |||
|
|||
// we have to stick to this version for now: https://github.com/r2dbc/r2dbc-mssql/issues/276 | |||
// bumping to 1.0.1.RELEASE or later currently requires pool config initial-size=1 and max-size=1 | |||
val r2dbcSqlServer = "io.r2dbc" % "r2dbc-mssql" % "1.0.0.RELEASE" // ApacheV2 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should be % Provided
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where does it come from if we mark it as Provided
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be defined in the build of the user. We don't want to include dependencies to all drivers. At some point we might make the postgres driver provided as well.
require(tagSeparator.length == 1, s"Tag separator '$tagSeparator' must be a single character.") | ||
|
||
def tagsToDb(tags: Set[String]): String = { | ||
if (tags.exists(_.contains(tagSeparator))) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
slightly more efficient to use Char as tagSeparator, since we only allow single character
@patriknw I took a short spike in extending from |
Tests are failing for the changes in |
It's in the right direction, but I think you have to start from the other end. Start with an empty SqlServerJournalDao that extends PostgresJournalDao. Then add overrides only on the things that are different. Look at H2JournalDao. Also note that I made some dao changes in #485 so you will need to rebase to get the latest, for example |
15be8aa
to
7b15676
Compare
…dling to reuse the latter one while adding new dialects
Separate sql query definition+binding from the actual locic
Closing and re-opening to re-trigger CI. |
Started off the
postgres
dialect impl, so some things should be familiar / identical.What I am currently not sure about is the usage of timezones and timestamps.
Some perf test is failing sometimes locally..
Docs will be in a separate pr.
jdbc uses a different docker image which is a bit old. Not using that for now, unless we need the benefit is provides.