Skip to content

Commit

Permalink
Merge pull request #29 from ergon/feature/dope-179-join-with-on-key-for
Browse files Browse the repository at this point in the history
DOPE-179 added on key for join clause, removed right join for lookup joins
  • Loading branch information
martinagallati-ergon committed Jun 24, 2024
2 parents 76aeb7e + e8878ae commit ed399e6
Show file tree
Hide file tree
Showing 5 changed files with 116 additions and 47 deletions.
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)
}

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

0 comments on commit ed399e6

Please sign in to comment.