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

sqlite: LEFT JOIN COALESCE has unwanted nullability #300

Open
vanniktech opened this issue Nov 30, 2021 · 6 comments
Open

sqlite: LEFT JOIN COALESCE has unwanted nullability #300

vanniktech opened this issue Nov 30, 2021 · 6 comments
Labels

Comments

@vanniktech
Copy link
Contributor

Dialect

SQLite

Failing SQL

SELECT
  coach.id, coach.firstName, coach.lastName, coach.badge, coach.email,
  COALESCE(jamesTemplateDatabaseCoach.jamesTemplateDatabaseId, :jamesTemplateDatabaseId) AS jamesTemplateDatabaseId,
  jamesTemplateDatabaseCoach.state
  FROM coach
  LEFT JOIN jamesTemplateDatabaseCoach
    ON jamesTemplateDatabaseCoach.coachId = coach.id AND jamesTemplateDatabaseCoach.jamesTemplateDatabaseId = :jamesTemplateDatabaseId
  WHERE coach.id NOT IN :botIds AND (coach.firstName LIKE :searchTerm OR coach.lastName LIKE :searchTerm)
  GROUP BY 1
  ORDER BY state DESC, firstName COLLATE NOCASE ASC, lastName COLLATE NOCASE ASC
;

Description

Even though I'm using a LEFT JOIN I want the jamesTemplateDatabaseId property to be always present in the generated Kotlin Data class.

This is what I came up with (if there's another solution, please let me know!)

COALESCE(jamesTemplateDatabaseCoach.jamesTemplateDatabaseId, :jamesTemplateDatabaseId) AS jamesTemplateDatabaseId,

this also works fine and the generated code also looks good:

      return driver.executeQuery(null, """
      |SELECT
      |  coach.id, coach.firstName, coach.lastName, coach.badge, coach.email,
      |  COALESCE(jamesTemplateDatabaseCoach.jamesTemplateDatabaseId, ?) AS jamesTemplateDatabaseId,
      |  jamesTemplateDatabaseCoach.state
      |  FROM coach
      |  LEFT JOIN jamesTemplateDatabaseCoach
      |    ON jamesTemplateDatabaseCoach.coachId = coach.id AND jamesTemplateDatabaseCoach.jamesTemplateDatabaseId ${ if (jamesTemplateDatabaseId == null) "IS" else "=" } ?
      |  WHERE coach.id NOT IN $botIdsIndexes AND (coach.firstName LIKE ? OR coach.lastName LIKE ?)
      |  GROUP BY 1
      |  ORDER BY state DESC, firstName COLLATE NOCASE ASC, lastName COLLATE NOCASE ASC
      """.trimMargin(), 4 + botIds.size) {
        bindString(1, jamesTemplateDatabaseId)
        bindString(2, jamesTemplateDatabaseId)
        botIds.forEachIndexed { index, botIds_ ->
            bindString(index + 3, botIds_)
            }
        bindString(botIds.size + 3, searchTerm)
        bindString(botIds.size + 4, searchTerm)
      }

The only problem is the generated class where jamesTemplateDatabaseId is null. It should be able to infer that the value is always present:

public data class SearchJamesTemplateDatabaseCoach(
  public val id: String,
  public val firstName: String,
  public val lastName: String,
  public val badge: Int?,
  public val email: String?,
-  public val jamesTemplateDatabaseId: String?,
+  public val jamesTemplateDatabaseId: String,
  public val state: Int?
) {
  public override fun toString(): String = """
  |SearchJamesTemplateDatabaseCoach [
  |  id: $id
  |  firstName: $firstName
  |  lastName: $lastName
  |  badge: $badge
  |  email: $email
  |  jamesTemplateDatabaseId: $jamesTemplateDatabaseId
  |  state: $state
  |]
  """.trimMargin()
}
@vanniktech vanniktech added the bug label Nov 30, 2021
@AlecKazakova
Copy link
Collaborator

AlecKazakova commented Nov 30, 2021 via email

@vanniktech
Copy link
Contributor Author

Ah right the generated signature is:

  public fun searchJamesTemplateDatabaseCoach(
    jamesTemplateDatabaseId: String?,
    botIds: Collection<String>,
    searchTerm: String
  ): Query<SearchJamesTemplateDatabaseCoach>

How can I enforce jamesTemplateDatabaseId to be non null?

@AlecKazakova
Copy link
Collaborator

since we're just creating an API to mirror what is accepted in SQL, I don't think there's a way with sql functions since most accept null.

@vanniktech
Copy link
Contributor Author

Fair enough, then I guess I'll just do:

-COALESCE(jamesTemplateDatabaseCoach.jamesTemplateDatabaseId, :jamesTemplateDatabaseId AS jamesTemplateDatabaseId,
+COALESCE(jamesTemplateDatabaseCoach.jamesTemplateDatabaseId, :jamesTemplateDatabaseId, "") AS jamesTemplateDatabaseId,

@vanniktech
Copy link
Contributor Author

I thought about this again and it does seem wrong to me. This is my column definition (on which I'm joining):

CREATE TABLE jamesTemplateDatabaseCoach (
  jamesTemplateDatabaseId TEXT NOT NULL
)

It's non null. This is my join statement:

LEFT JOIN jamesTemplateDatabaseCoach
    ON jamesTemplateDatabaseCoach.coachId = coach.id AND jamesTemplateDatabaseCoach.jamesTemplateDatabaseId = :jamesTemplateDatabaseId

Why would I want to have a nullable jamesTemplateDatabaseId if in the table statement the column is non null? Probably the LEFT JOIN triggers the unwanted nullability. It should not though.

@AlecKazakova
Copy link
Collaborator

yes, that part does seem like a bug, during the join clause you still have rows present so the arg there should be non null

@AlecKazakova AlecKazakova reopened this Dec 1, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants