Skip to content
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

Introduce updateSetOpt fragment (#1893) #2126

Merged
merged 4 commits into from
Nov 4, 2024

Conversation

matsluni
Copy link
Contributor

This tries to solve #1893.

I hope the tests run through, as I could not successfully run all tests locally. At some point the testsuite just hanged on my Apple M1. Also, locally, I needed to switch the mysql docker image in the docker compose file to work on the M1.

test("updateSetOpt for three column") {
assertEquals(
updateSetOpt(Fragment.const("Foo"), sqlKv, List.empty[Fragment]).map(_.query[Unit].sql),
Some("UPDATE Foo SET a = ?, b = ?, c = ? "))
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Here, we currently have an empty space, if the WHERE clause is empty. Let me know, if this should be handled differently.

Copy link
Collaborator

Choose a reason for hiding this comment

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

The spaces are fine :)

def updateSetOpt[F[_]: Foldable](
tableName: Fragment,
columnUpdates: F[Fragment],
whereAnd: F[Fragment]
Copy link
Collaborator

Choose a reason for hiding this comment

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

Thanks this is a good start!
In my comment what I was thinking was a fragment for the where condition, so the signature should be condition: Fragment

Usage will look like:

updateSetOpt(
  fr"my_table",
  ...,
  condition = fr"some_col > 50"
)

or a more complicated example

updateSetOpt(
  fr"my_table",
  ...,
  condition = fragments.inOpt(fr"name", nameList).getOrElse(fr"false")
)

This way the user have full control over the condition.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hi @jatcwang, thanks for the clarification. I updated the signature accordingly.

@satorg
Copy link
Contributor

satorg commented Oct 31, 2024

I'd like to weigh in a little, if I may.

It looks like the proposed updateSetOpt will result to None if columnUpdates is empty.

Wouldn't it be better to use the Reducible constraint instead to prevent messing with optionality there in runtime, e.g.:

def updateSet[F[_]: Reducible](
      tableName: Fragment,
      columnUpdates: F[Fragment],
      condition: Fragment
  ): Fragment = ...

Also I'm not sure if it makes sense to include the condition parameter whatsoever, because it just concatenates WHERE clause, but Doobie has a plenty of fragment constructors for it. I.e. it should be fairly easy to write something like this:

updateSet("my_table", myColumnFragments) ++ whereAnd(myConditions)

OR if whereOr is desired

updateSet("my_table", myColumnFragments) ++ whereOr(myConditions)

Note that UPDATE also allows FROM and RETURNING clauses, so one may want to have something like the this:

updateSet("my_table", myColumns) ++
  fr"FROM" ++ values(...) ++
  whereOr(...) ++
  fr"RETURNING" ++ returningStatement

Therefore including the WHERE clause straight into the updateSet[Opt] builder saves not that much but makes the latter less generic and flexible.

@jatcwang
Copy link
Collaborator

jatcwang commented Nov 1, 2024

@satorg With these fragment helpers the general principle I'm following is that it should be hard / impossible to generate invalid SQL or (worse) SQL that lead to the wrong behaviour. Hence I support having condition parameter because it's good to prompt the user to think "oh yes I need a condition for update".

Regarding requiring Reducible instead of Foldable, It's a matter of convenience I guess.

NonEmptyList.from(columnUpdates) { colUpdatesNel =>
  updateSet(
    myTable,
    colUpdatesNel,
    fr".."
  )
}

vs

updateSetOpt(
    myTable,
    colUpdates,
    fr".."
  )

We can have both like we do for most combinators :)

RETURNING doesn't seem to be supported in MySQL so makes sense to leave that out and let people add it themselves.

@satorg
Copy link
Contributor

satorg commented Nov 1, 2024

@jatcwang ,

With these fragment helpers the general principle I'm following is that it should be hard / impossible to generate invalid SQL or (worse) SQL that lead to the wrong behaviour. Hence I support having condition parameter because it's good to prompt the user to think "oh yes I need a condition for update".

Not sure I'm completely following, sorry. AFAIK, the WHERE clause is optional for UPDATE, it is totally valid to use UPDATE without it: https://dbfiddle.uk/27vmy0cg

Moreover, I think it would be nice to have an override of updateSet that could benefit from var-args as well, e.g.:

def updateSet(tableName: Fragment, colUpdate1: Fragment, colUpdates: Fragment*): Fragment = ...

In this case the condition parameter does not fit well.

Regarding requiring Reducible instead of Foldable, It's a matter of convenience I guess.
...
We can have both like we do for most combinators :)

Agree. Although it is hard for me to imagine where the -Opt variant could be useful, but I admit there could be some.

RETURNING doesn't seem to be supported in MySQL so makes sense to leave that out and let people add it themselves.

Yeah, kinda agree too. I feel though that everything but UPDATE and SET can be left out just as well (since UPDATE and SET are the only requisites here).

@jatcwang
Copy link
Collaborator

jatcwang commented Nov 2, 2024

@satorg Good point that with the current signature we cannot generate queries like UPDATE .. SET .. FROM .. WHERE . Let's remove condition parameter then. (Sorry @matsluni!)

Also @matsluni feel free to focus on addressing your immediate usecase (i.e. just implement updateSetOpt) for this PR. We can yakshave in follow-up issues/PRs :)

@matsluni
Copy link
Contributor Author

matsluni commented Nov 3, 2024

Thanks @satorg and @jatcwang for your comments. I will move forward without the where clause. That should also simplify a bit.

@jatcwang, any idea, why the testsuite does not complete on my machine? Is that known already?

@jatcwang jatcwang merged commit 12fcba6 into typelevel:main Nov 4, 2024
5 checks passed
@jatcwang
Copy link
Collaborator

jatcwang commented Nov 4, 2024

Thanks @matsluni. I'm not sure why it's failing on M1 - Do you remember what sort of errors you're getting?

@matsluni
Copy link
Contributor Author

matsluni commented Nov 4, 2024

Thanks @matsluni. I'm not sure why it's failing on M1 - Do you remember what sort of errors you're getting?

@jatcwang, firstly I needed to switch to aarm64 compatible mysql image in the docker compose. I think I finally tested with arm64v8/mysql:oracle (ideally, this can be switched to a multiplatform image. But I am no expert for MySql, what image is best to use.). Secondly, the testsuite never finishes for me calling sbt test, It just hangs after successfully completeted doobie.postgres.TextSuite (I aborted after 10 minutes).

@jatcwang
Copy link
Collaborator

jatcwang commented Nov 4, 2024

Thanks for the info! I'll see if I can reproduce and fix it.

@jatcwang
Copy link
Collaborator

jatcwang commented Nov 4, 2024

I have created #2126 to track this

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.

3 participants