Skip to content

Functionally merging multiple (partial) queries

Roy Brondgeest edited this page Apr 23, 2018 · 1 revision

When you are working in a functional codebase, you ofcourse want to be able to keep your code to be composable and abstract.

The query DSL allows the use of query combinator operators.

Combinators can be used endlessly, together and in any order. E.g.:

select(a) :+> where(b) <+: from(c) + limit(Limit(size(10)))

Conflict resolving

Conflict resolving happens per part and thus not per column whatsoever. By part we refer to "SELECT, FROM, FINAL, WHERE, GROUPBY etc. etc."

So 2 queries with a select block set will be in conflict, and depending on the operator you choose it will try to resolve the conflict by overwriting one over the other.

Resolving joins

A join is in itself regarded as a part and is not resolved by its parts recursively. This means that this has to be done separately, which is by design, and should still allow for 100% composability.

Merge query with "Left hand wins" conflict resolving

The :+> operator takes two Query objects and returns a merged version of this, choosing the part of the left query when this part is defined on both queries.

  it should "merge with left preference" in {
    val query = select(shieldId) from OneTestTable
    val query2 = select(itemId) from OneTestTable where col2 >= 2
    val composed = query :+> query2

    clickhouseTokenizer.toSql(composed.internalQuery) should be (
      s"SELECT shield_id FROM test.OneTestTable WHERE column_2 >= 2 FORMAT JSON"
    )
  }

Merge query with "Right hand wins" conflict resolving

The <+: operator takes two Query objects and returns a merged version of this, choosing the part of the right query when this part is defined on both queries.

  it should "merge with right preference" in {
    val query = select(shieldId) from OneTestTable
    val query2 = select(itemId) from OneTestTable where col2 >= 2
    val composed = query <+: query2
    clickhouseTokenizer.toSql(composed.internalQuery) should be (
      s"SELECT item_id FROM test.OneTestTable WHERE column_2 >= 2 FORMAT JSON"
    )
  }

Merge query with a Try[Query] as result

The + operator takes two Query or Try[Query] objects and returns a Try which succeeds when theres no conflict, and fails otherwise.

  it should "fail on try merge of conflicting queries" in {
    val query = select(shieldId) from OneTestTable
    val query2 = select(itemId) from OneTestTable where col2 >= 2
    val composed = query + query2
    composed should matchPattern {
      case Failure(_:IllegalArgumentException) =>
    }
  }

  it should "succeed on safe override of non-conflicting multi part queries" in {
    val query = select(shieldId)
    val query2 = from(OneTestTable)
    val query3 = where(col2 >= 4)

    val composed = query + query2
    val composed2 = composed + query3

    composed should matchPattern {
      case t: Success[_] =>
    }

    clickhouseTokenizer.toSql(composed.get.internalQuery) should be (
      "SELECT shield_id FROM test.OneTestTable FORMAT JSON"
    )

    composed2 should matchPattern {
      case t: Success[_] =>
    }
    clickhouseTokenizer.toSql(composed2.get.internalQuery) should be (
      "SELECT shield_id FROM test.OneTestTable WHERE column_2 >= 4 FORMAT JSON"
    )
  }

Todo pages:

Clone this wiki locally