Andrew O'Hara
11/03/2022, 7:58 PMResultRow.hasValue
for the joined columns always returns true
, which makes it difficult for me to tell which optional columns are actually present.
Below is a contrived example, followed by a passing and a failing test. It should illustrate my problem better than my explanation. Am I doing this completely wrong?
object BaseUser: IntIdTable("base_users")
object SuperUser: IntIdTable("super_users") {
val baseId = reference("base_id", BaseUser)
val name = varchar("name", 128)
}
object NormalUser: IntIdTable("normal_users") {
val baseId = reference("base_id", BaseUser)
val name = varchar("name", 128)
}
fun Transaction.getUserName(baseId: EntityID<Int>): String? {
val row = BaseUser
.leftJoin(SuperUser, {SuperUser.baseId}, {BaseUser.id})
.leftJoin(NormalUser, {NormalUser.baseId}, {BaseUser.id})
.slice(NormalUser.name, SuperUser.name)
.select { BaseUser.id eq baseId }
.firstOrNull()
return when {
row == null -> null
row.hasValue(NormalUser.name) -> row[NormalUser.name] // Still enters here even for SuperUser
row.hasValue(SuperUser.name) -> row[SuperUser.name]
else -> null
}
}
@Test
fun `navigate subtype relationship`() {
transaction {
SchemaUtils.create(BaseUser, SuperUser, NormalUser)
val baseUserId1 = BaseUser.insertAndGetId {}
val baseUserId2 = BaseUser.insertAndGetId {}
NormalUser.insert {
it[baseId] = baseUserId1
it[name] = "normaldude"
}
SuperUser.insert {
it[baseId] = baseUserId2
it[name] = "superdude"
}
getUserName(baseUserId1) shouldBe "normaldude" // PASS
getUserName(baseUserId2) shouldBe "superdude" // FAIL; was actually null
}
}
Endre Deak
11/03/2022, 11:05 PMname
column? Also, if you could turn on logging to see the actual SQL statement would be helpful.Andrew O'Hara
11/04/2022, 1:19 PMname
columns to something unique per table, and it didn't change the outcome as expected. I've enabled logging and got this output; looks fine to me.
[Test worker] DEBUG Exposed - SELECT SETTING_VALUE FROM INFORMATION_SCHEMA.SETTINGS WHERE SETTING_NAME = 'MODE'
[Test worker] DEBUG Exposed - CREATE TABLE IF NOT EXISTS BASE_USERS (ID INT AUTO_INCREMENT PRIMARY KEY)
[Test worker] DEBUG Exposed - CREATE TABLE IF NOT EXISTS SUPER_USERS (ID INT AUTO_INCREMENT PRIMARY KEY, BASE_ID INT NOT NULL, SUPER_NAME VARCHAR(128) NOT NULL, CONSTRAINT FK_SUPER_USERS_BASE_ID__ID FOREIGN KEY (BASE_ID) REFERENCES BASE_USERS(ID) ON DELETE RESTRICT ON UPDATE RESTRICT)
[Test worker] DEBUG Exposed - CREATE TABLE IF NOT EXISTS NORMAL_USERS (ID INT AUTO_INCREMENT PRIMARY KEY, BASE_ID INT NOT NULL, NORMAL_NAME VARCHAR(128) NOT NULL, CONSTRAINT FK_NORMAL_USERS_BASE_ID__ID FOREIGN KEY (BASE_ID) REFERENCES BASE_USERS(ID) ON DELETE RESTRICT ON UPDATE RESTRICT)
[Test worker] DEBUG Exposed - INSERT INTO BASE_USERS DEFAULT VALUES
[Test worker] DEBUG Exposed - INSERT INTO BASE_USERS DEFAULT VALUES
[Test worker] DEBUG Exposed - INSERT INTO NORMAL_USERS (BASE_ID, NORMAL_NAME) VALUES (1, 'normaldude')
[Test worker] DEBUG Exposed - INSERT INTO SUPER_USERS (BASE_ID, SUPER_NAME) VALUES (2, 'superdude')
[Test worker] DEBUG Exposed - SELECT NORMAL_USERS.NORMAL_NAME, SUPER_USERS.SUPER_NAME FROM BASE_USERS LEFT JOIN SUPER_USERS ON SUPER_USERS.BASE_ID = BASE_USERS.ID LEFT JOIN NORMAL_USERS ON NORMAL_USERS.BASE_ID = BASE_USERS.ID WHERE BASE_USERS.ID = 1
[Test worker] DEBUG Exposed - SELECT NORMAL_USERS.NORMAL_NAME, SUPER_USERS.SUPER_NAME FROM BASE_USERS LEFT JOIN SUPER_USERS ON SUPER_USERS.BASE_ID = BASE_USERS.ID LEFT JOIN NORMAL_USERS ON NORMAL_USERS.BASE_ID = BASE_USERS.ID WHERE BASE_USERS.ID = 2
Endre Deak
11/04/2022, 2:32 PMEndre Deak
11/04/2022, 2:34 PMAndrew O'Hara
11/04/2022, 2:35 PMEndre Deak
11/04/2022, 2:37 PMEndre Deak
11/04/2022, 2:38 PMAndrew O'Hara
11/04/2022, 2:39 PMbaseUserId1
will fail and getting the name of baseUserId2
will pass; the opposite result given in my exampleAndrew O'Hara
11/04/2022, 2:40 PMwhen
expression will always choose the first case with hasValue
Endre Deak
11/04/2022, 2:41 PMrow
when code enters to the when
clause?Andrew O'Hara
11/04/2022, 3:27 PMhasValue
is returning true
because data[0]
is null
rather than NotInitializedValue
. I guess the question then is whether this is a bug I can submit a patch for, or if there's a more correct way of going about this.Andrew O'Hara
11/04/2022, 3:35 PMdata[it]
is null
would be incorrect, because null
might be the correct value. We would have to tell whether data
has an element for that index, and I'm not sure if that's possible for an array
.Endre Deak
11/04/2022, 4:02 PMNORMAL_USERS.NORMAL_NAME | SUPER_USERS.SUPER_NAME
-------------------------|-----------------------
normaldude | null
null | superdude
and in that case technically row.hasValue()
is true, so I think you might have to adjust your return statement like this:
return row?.let { r ->
r[NormalUser.name] ?: r[SuperUser.name]
}
so if row is null, it will return null, if normalusername is not null then returns with that else it tries to return with superusername which also can be null.Andrew O'Hara
11/04/2022, 4:30 PMr[NormalUser.name]
is supposed to be non-null, so the editor thinks the ?:
is unreachable.
If I then go and define and use a nullable alias, then it works too.
val nullableNormalName = NormalUser.normalName.nullable().alias("nullableNormalName")
But defining that alias seems to have a weird effects on the rest of my tables, that I can't quite characterize yet.Endre Deak
11/04/2022, 5:12 PMreturn row?.let { r ->
val normalUserName: String? = r[NormalUser.name]
val superUserName: String? = r[SuperUser.name]
if (normalUserName != null) {
normalUserName
} else {
superUserName
}
}
would this make your editor complaining as well?Andrew O'Hara
11/04/2022, 5:25 PMval normalUsername
nullable, since it knows row[NormalTable.normalName]
isn't supposed to be nullable, it still complains about the if-condition.Endre Deak
11/04/2022, 5:32 PM?
, you'd get a NPEAndrew O'Hara
11/04/2022, 5:36 PMr[NormalUser.normalName]
is non-null, it actually ends up being a null
. So the editor complains, because it thinks it's impossible, but it's wrong. Kind of like how a json mapper that doesn't support Kotlin (i.e. Gson) will happily ignore null-safety when deserializing.Endre Deak
11/04/2022, 6:56 PMRon S
11/06/2022, 9:15 PM