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

DOPE-179 added on key for join clause, removed right join for lookup joins #29

Merged
merged 3 commits into from
Jun 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -67,15 +67,17 @@ interface ISelectFromClause : ISelectUseKeysClause {
interface ISelectJoinClause : ISelectFromClause {
fun join(bucket: Bucket, onCondition: TypeExpression<BooleanType>) = StandardJoinClause(bucket, onCondition, this)
fun join(bucket: Bucket, onKeys: Field<out ValidType>) = StandardJoinClause(bucket, onKeys, this)
fun join(bucket: Bucket, onKey: Field<out ValidType>, forBucket: Bucket) = StandardJoinClause(bucket, onKey, forBucket, this)

fun innerJoin(bucket: Bucket, onCondition: TypeExpression<BooleanType>) = InnerJoinClause(bucket, onCondition, this)
fun innerJoin(bucket: Bucket, onKeys: Field<out ValidType>) = InnerJoinClause(bucket, onKeys, this)
fun innerJoin(bucket: Bucket, onKey: Field<out ValidType>, forBucket: Bucket) = InnerJoinClause(bucket, onKey, forBucket, this)

fun leftJoin(bucket: Bucket, onCondition: TypeExpression<BooleanType>) = LeftJoinClause(bucket, onCondition, this)
fun leftJoin(bucket: Bucket, onKeys: Field<out ValidType>) = LeftJoinClause(bucket, onKeys, this)
fun leftJoin(bucket: Bucket, onKey: Field<out ValidType>, forBucket: Bucket) = LeftJoinClause(bucket, onKey, forBucket, this)

fun rightJoin(bucket: Bucket, onCondition: TypeExpression<BooleanType>) = RightJoinClause(bucket, onCondition, this)
fun rightJoin(bucket: Bucket, onKeys: Field<out ValidType>) = RightJoinClause(bucket, onKeys, this)
martinagallati-ergon marked this conversation as resolved.
Show resolved Hide resolved
}

interface ISelectUnnestClause : ISelectJoinClause {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,66 +3,95 @@ package ch.ergon.dope.resolvable.clause.model
import ch.ergon.dope.DopeQuery
import ch.ergon.dope.resolvable.clause.ISelectFromClause
import ch.ergon.dope.resolvable.clause.ISelectJoinClause
import ch.ergon.dope.resolvable.clause.model.JoinType.INNER_JOIN
import ch.ergon.dope.resolvable.clause.model.JoinType.JOIN
import ch.ergon.dope.resolvable.clause.model.JoinType.LEFT_JOIN
import ch.ergon.dope.resolvable.clause.model.JoinType.RIGHT_JOIN
import ch.ergon.dope.resolvable.expression.TypeExpression
import ch.ergon.dope.resolvable.expression.unaliased.type.Field
import ch.ergon.dope.resolvable.fromable.Bucket
import ch.ergon.dope.validtype.BooleanType
import ch.ergon.dope.validtype.ValidType

private enum class JoinType(val type: String) {
JOIN("JOIN"),
LEFT_JOIN("LEFT JOIN"),
INNER_JOIN("INNER JOIN"),
RIGHT_JOIN("RIGHT JOIN"),
}

sealed class SelectJoinClause : ISelectJoinClause {
private val dopeQuery: DopeQuery

constructor(joinType: String, bucket: Bucket, onCondition: TypeExpression<BooleanType>, parentClause: ISelectFromClause) {
constructor(joinType: JoinType, bucket: Bucket, onCondition: TypeExpression<BooleanType>, parentClause: ISelectFromClause) {
val parentDopeQuery = parentClause.toDopeQuery()
val bucketDopeQuery = bucket.toDopeQuery()
val onConditionDopeQuery = onCondition.toDopeQuery()
dopeQuery = DopeQuery(
queryString = "${parentDopeQuery.queryString} $joinType ${bucketDopeQuery.queryString} ON ${onConditionDopeQuery.queryString}",
queryString = "${parentDopeQuery.queryString} ${joinType.type} ${bucketDopeQuery.queryString} " +
"ON ${onConditionDopeQuery.queryString}",
parameters = parentDopeQuery.parameters + bucketDopeQuery.parameters + onConditionDopeQuery.parameters,
)
}

constructor(joinType: String, bucket: Bucket, key: Field<out ValidType>, parentClause: ISelectFromClause) {
constructor(joinType: JoinType, bucket: Bucket, onKeys: Field<out ValidType>, parentClause: ISelectFromClause) {
val parentDopeQuery = parentClause.toDopeQuery()
val bucketDopeQuery = bucket.toDopeQuery()
val keyDopeQuery = key.toDopeQuery()
val keyDopeQuery = onKeys.toDopeQuery()
dopeQuery = DopeQuery(
queryString = "${parentDopeQuery.queryString} $joinType ${bucketDopeQuery.queryString} ON KEYS ${keyDopeQuery.queryString}",
queryString = "${parentDopeQuery.queryString} ${joinType.type} ${bucketDopeQuery.queryString} " +
"ON KEYS ${keyDopeQuery.queryString}",
parameters = parentDopeQuery.parameters + bucketDopeQuery.parameters + keyDopeQuery.parameters,
)
}

constructor(joinType: JoinType, bucket: Bucket, onKey: Field<out ValidType>, forBucket: Bucket, parentClause: ISelectFromClause) {
val parentDopeQuery = parentClause.toDopeQuery()
val bucketDopeQuery = bucket.toDopeQuery()
val keyDopeQuery = onKey.toDopeQuery()
val forBucketDopeQuery = forBucket.toDopeQuery()
dopeQuery = DopeQuery(
queryString = "${parentDopeQuery.queryString} ${joinType.type} ${bucketDopeQuery.queryString} " +
"ON KEY ${keyDopeQuery.queryString} FOR ${forBucketDopeQuery.queryString}",
parameters = parentDopeQuery.parameters + bucketDopeQuery.parameters + keyDopeQuery.parameters + forBucketDopeQuery.parameters,
)
}

override fun toDopeQuery(): DopeQuery = dopeQuery
}

class StandardJoinClause : SelectJoinClause {
constructor(bucket: Bucket, onCondition: TypeExpression<BooleanType>, parentClause: ISelectFromClause) :
super("JOIN", bucket, onCondition, parentClause)
super(JOIN, bucket, onCondition, parentClause)

constructor(bucket: Bucket, onKeys: Field<out ValidType>, parentClause: ISelectFromClause) :
super("JOIN", bucket, onKeys, parentClause)
super(JOIN, bucket, onKeys, parentClause)

constructor(bucket: Bucket, onKey: Field<out ValidType>, forBucket: Bucket, parentClause: ISelectFromClause) :
super(JOIN, bucket, onKey, forBucket, parentClause)
}

class LeftJoinClause : SelectJoinClause {
constructor(bucket: Bucket, onCondition: TypeExpression<BooleanType>, parentClause: ISelectFromClause) :
super("LEFT JOIN", bucket, onCondition, parentClause)
super(LEFT_JOIN, bucket, onCondition, parentClause)

constructor(bucket: Bucket, onKeys: Field<out ValidType>, parentClause: ISelectFromClause) :
super("LEFT JOIN", bucket, onKeys, parentClause)
super(LEFT_JOIN, bucket, onKeys, parentClause)

constructor(bucket: Bucket, onKey: Field<out ValidType>, forBucket: Bucket, parentClause: ISelectFromClause) :
super(LEFT_JOIN, bucket, onKey, forBucket, parentClause)
}

class InnerJoinClause : SelectJoinClause {
constructor(bucket: Bucket, onCondition: TypeExpression<BooleanType>, parentClause: ISelectFromClause) :
super("INNER JOIN", bucket, onCondition, parentClause)
super(INNER_JOIN, bucket, onCondition, parentClause)

constructor(bucket: Bucket, onKeys: Field<out ValidType>, parentClause: ISelectFromClause) :
super("INNER JOIN", bucket, onKeys, parentClause)
}
super(INNER_JOIN, bucket, onKeys, parentClause)

class RightJoinClause : SelectJoinClause {
constructor(bucket: Bucket, onCondition: TypeExpression<BooleanType>, parentClause: ISelectFromClause) :
super("RIGHT JOIN", bucket, onCondition, parentClause)

constructor(bucket: Bucket, onKeys: Field<out ValidType>, parentClause: ISelectFromClause) :
super("RIGHT JOIN", bucket, onKeys, parentClause)
constructor(bucket: Bucket, onKey: Field<out ValidType>, forBucket: Bucket, parentClause: ISelectFromClause) :
super(INNER_JOIN, bucket, onKey, forBucket, parentClause)
}

class RightJoinClause(bucket: Bucket, onCondition: TypeExpression<BooleanType>, parentClause: ISelectFromClause) :
SelectJoinClause(RIGHT_JOIN, bucket, onCondition, parentClause)
68 changes: 46 additions & 22 deletions core/src/test/kotlin/ch/ergon/dope/JoinClauseTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -334,30 +334,54 @@ class JoinClauseTest {
}

@Test
fun `Use INDEX right join to flip the direction`() {
val expected = "SELECT DISTINCT `route`.`destinationairport`, " +
"`route`.`stops`, `route`.`airline`, `airline`.`name`, `airline`.`callsign` " +
"FROM `route` RIGHT JOIN `airline` ON KEYS `route`.`airlineid` " +
"WHERE `airline`.`icao` = \"SWA\" LIMIT 4"
fun `Use INDEX join to flip the direction with on key for`() {
val expected = "SELECT * FROM `airline` JOIN `route` ON KEY `route`.`airlineid` FOR `airline`"

val actual = create.selectDistinct(
someStringField("destinationairport", route),
someStringField("stops", route),
someStringField("airline", route),
someStringField("name", airline),
someStringField("callsign", airline),
).from(
route,
).rightJoin(
airline,
onKeys = someStringField("airlineid", route),
).where(
someStringField("icao", airline).isEqualTo("SWA".toStringType()),
).limit(
4,
).build().queryString
val actual: String = create
.selectAsterisk()
.from(
airline,
).join(
route,
onKey = someStringField("airlineid", route),
forBucket = airline,
).build().queryString

assertEquals(unifyString(expected), actual)
assertEquals(expected, actual)
}

@Test
fun `Use INDEX inner join to flip the direction with on key for`() {
val expected = "SELECT * FROM `airline` INNER JOIN `route` ON KEY `route`.`airlineid` FOR `airline`"

val actual: String = create
.selectAsterisk()
.from(
airline,
).innerJoin(
route,
onKey = someStringField("airlineid", route),
forBucket = airline,
).build().queryString

assertEquals(expected, actual)
}

@Test
fun `Use INDEX left join to flip the direction with on key for`() {
val expected = "SELECT * FROM `airline` LEFT JOIN `route` ON KEY `route`.`airlineid` FOR `airline`"

val actual: String = create
.selectAsterisk()
.from(
airline,
).leftJoin(
route,
onKey = someStringField("airlineid", route),
forBucket = airline,
).build().queryString

assertEquals(expected, actual)
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,13 @@ fun ISelectWhereClause.groupBy(field: CMType, vararg fields: CMType): GroupByCla
fun ISelectFromClause.where(whereExpression: CMField<Boolean>) = where(whereExpression.asField())

fun ISelectJoinClause.join(bucket: Bucket, onKeys: CMField<out Any>) = join(bucket, onKeys.asField())
fun ISelectJoinClause.join(bucket: Bucket, onKey: CMField<out Any>, forBucket: Bucket) = join(bucket, onKey.asField(), forBucket)

fun ISelectJoinClause.innerJoin(bucket: Bucket, onKeys: CMField<out Any>) = innerJoin(bucket, onKeys.asField())
fun ISelectJoinClause.innerJoin(bucket: Bucket, onKey: CMField<out Any>, forBucket: Bucket) = innerJoin(bucket, onKey.asField(), forBucket)

fun ISelectJoinClause.leftJoin(bucket: Bucket, onKeys: CMField<out Any>) = leftJoin(bucket, onKeys.asField())

fun ISelectJoinClause.rightJoin(bucket: Bucket, onKeys: CMField<out Any>) = rightJoin(bucket, onKeys.asField())
fun ISelectJoinClause.leftJoin(bucket: Bucket, onKey: CMField<out Any>, forBucket: Bucket) = leftJoin(bucket, onKey.asField(), forBucket)

@JvmName("unnestString")
fun ISelectUnnestClause.unnest(arrayField: CMList<String>) = unnest(arrayField.asArrayField())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import ch.ergon.dope.extension.clause.leftJoin
import ch.ergon.dope.extension.clause.limit
import ch.ergon.dope.extension.clause.offset
import ch.ergon.dope.extension.clause.orderBy
import ch.ergon.dope.extension.clause.rightJoin
import ch.ergon.dope.extension.clause.unnest
import ch.ergon.dope.extension.clause.where
import ch.ergon.dope.helper.someBucket
Expand Down Expand Up @@ -59,13 +58,27 @@ class SelectClauseTest {
assertEquals("SELECT * FROM `someBucket` JOIN `other` ON KEYS `someNumberField`", actual)
}

@Test
fun `should support select join on key for with CM`() {
val actual: String = someFrom().join(someBucket("other"), onKey = someCMNumberField(), someBucket()).toDopeQuery().queryString

assertEquals("SELECT * FROM `someBucket` JOIN `other` ON KEY `someNumberField` FOR `someBucket`", actual)
}

@Test
fun `should support select inner join with CM`() {
val actual: String = someFrom().innerJoin(someBucket("other"), onKeys = someCMNumberField()).toDopeQuery().queryString

assertEquals("SELECT * FROM `someBucket` INNER JOIN `other` ON KEYS `someNumberField`", actual)
}

@Test
fun `should support select inner join on key for with CM`() {
val actual: String = someFrom().innerJoin(someBucket("other"), onKey = someCMNumberField(), someBucket()).toDopeQuery().queryString

assertEquals("SELECT * FROM `someBucket` INNER JOIN `other` ON KEY `someNumberField` FOR `someBucket`", actual)
}

@Test
fun `should support select left join with CM`() {
val actual: String = someFrom().leftJoin(someBucket("other"), onKeys = someCMNumberField()).toDopeQuery().queryString
Expand All @@ -74,10 +87,10 @@ class SelectClauseTest {
}

@Test
fun `should support select right join with CM`() {
val actual: String = someFrom().rightJoin(someBucket("other"), onKeys = someCMNumberField()).toDopeQuery().queryString
fun `should support select left join on key for with CM`() {
val actual: String = someFrom().leftJoin(someBucket("other"), onKey = someCMNumberField(), someBucket()).toDopeQuery().queryString

assertEquals("SELECT * FROM `someBucket` RIGHT JOIN `other` ON KEYS `someNumberField`", actual)
assertEquals("SELECT * FROM `someBucket` LEFT JOIN `other` ON KEY `someNumberField` FOR `someBucket`", actual)
}

@Test
Expand Down
Loading