diff --git a/dsl/src/main/scala/com/crobox/clickhouse/dsl/column/EmptyFunctions.scala b/dsl/src/main/scala/com/crobox/clickhouse/dsl/column/EmptyFunctions.scala index 4f97ee09..bc4310ff 100644 --- a/dsl/src/main/scala/com/crobox/clickhouse/dsl/column/EmptyFunctions.scala +++ b/dsl/src/main/scala/com/crobox/clickhouse/dsl/column/EmptyFunctions.scala @@ -6,18 +6,45 @@ trait EmptyFunctions { self: Magnets => sealed abstract class EmptyFunction[+V](val innerCol: Column) extends ExpressionColumn[V](innerCol) - case class Empty(col: EmptyNonEmptyCol[_]) extends EmptyFunction[Boolean](col.column) - case class NotEmpty(col: EmptyNonEmptyCol[_]) extends EmptyFunction[Boolean](col.column) - case class IsNull(col: EmptyNonEmptyCol[_]) extends EmptyFunction[Boolean](col.column) + case class Empty(col: EmptyNonEmptyCol[_]) extends EmptyFunction[Boolean](col.column) + case class NotEmpty(col: EmptyNonEmptyCol[_]) extends EmptyFunction[Boolean](col.column) + case class IsNull(col: EmptyNonEmptyCol[_]) extends EmptyFunction[Boolean](col.column) + case class IsNullable(col: EmptyNonEmptyCol[_]) extends EmptyFunction[Boolean](col.column) + case class IsNotNull(col: EmptyNonEmptyCol[_]) extends EmptyFunction[Boolean](col.column) + case class IsNotDistinctFrom(col: EmptyNonEmptyCol[_], other: EmptyNonEmptyCol[_]) + extends EmptyFunction[Boolean](col.column) + case class IsZeroOrNull(col: EmptyNonEmptyCol[_]) extends EmptyFunction[Boolean](col.column) + case class IfNull(col: EmptyNonEmptyCol[_], alt: String) extends EmptyFunction[Boolean](col.column) + case class NullIf(col: EmptyNonEmptyCol[_], other: EmptyNonEmptyCol[_]) extends EmptyFunction[Boolean](col.column) + case class AssumeNotNull(col: EmptyNonEmptyCol[_]) extends EmptyFunction[Boolean](col.column) + case class ToNullable(col: EmptyNonEmptyCol[_]) extends EmptyFunction[Boolean](col.column) trait EmptyOps[C] { self: EmptyNonEmptyCol[_] => - def empty(): Empty = Empty(self) - def notEmpty(): NotEmpty = NotEmpty(self) - def isNull(): IsNull = IsNull(self) + def empty(): Empty = Empty(self) + def notEmpty(): NotEmpty = NotEmpty(self) + def isNull(): IsNull = IsNull(self) + def isNotNull(): IsNotNull = IsNotNull(self) + def isNullable(): IsNullable = IsNullable(self) + def isNotDistinctFrom(other: EmptyNonEmptyCol[_]): IsNotDistinctFrom = IsNotDistinctFrom(self, other) + def isZeroOrNull(): IsZeroOrNull = IsZeroOrNull(self) + def ifNull(alternative: String): IfNull = IfNull(self, alternative) + def nullIf(other: EmptyNonEmptyCol[_]): NullIf = NullIf(self, other) + def assumeNotNull(): AssumeNotNull = AssumeNotNull(self) + def toNullable(): ToNullable = ToNullable(self) } - def empty(col: EmptyNonEmptyCol[_]): Empty = Empty(col: EmptyNonEmptyCol[_]) - def notEmpty(col: EmptyNonEmptyCol[_]): NotEmpty = NotEmpty(col: EmptyNonEmptyCol[_]) - def isNull(col: EmptyNonEmptyCol[_]): IsNull = IsNull(col: EmptyNonEmptyCol[_]) + def empty(col: EmptyNonEmptyCol[_]): Empty = Empty(col: EmptyNonEmptyCol[_]) + def notEmpty(col: EmptyNonEmptyCol[_]): NotEmpty = NotEmpty(col: EmptyNonEmptyCol[_]) + def isNull(col: EmptyNonEmptyCol[_]): IsNull = IsNull(col: EmptyNonEmptyCol[_]) + def isNotNull(col: EmptyNonEmptyCol[_]): IsNotNull = IsNotNull(col: EmptyNonEmptyCol[_]) + def isNullable(col: EmptyNonEmptyCol[_]): IsNullable = IsNullable(col: EmptyNonEmptyCol[_]) + def isNotDistinctFrom(col: EmptyNonEmptyCol[_], other: EmptyNonEmptyCol[_]): IsNotDistinctFrom = + IsNotDistinctFrom(col: EmptyNonEmptyCol[_], other: EmptyNonEmptyCol[_]) + def isZeroOrNull(col: EmptyNonEmptyCol[_]): IsZeroOrNull = IsZeroOrNull(col: EmptyNonEmptyCol[_]) + def ifNull(col: EmptyNonEmptyCol[_], alternative: String): IfNull = IfNull(col: EmptyNonEmptyCol[_], alternative) + def nullIf(col: EmptyNonEmptyCol[_], other: EmptyNonEmptyCol[_]): NullIf = + NullIf(col: EmptyNonEmptyCol[_], other: EmptyNonEmptyCol[_]) + def assumeNotNull(col: EmptyNonEmptyCol[_]): AssumeNotNull = AssumeNotNull(col: EmptyNonEmptyCol[_]) + def toNullable(col: EmptyNonEmptyCol[_]): ToNullable = ToNullable(col: EmptyNonEmptyCol[_]) } diff --git a/dsl/src/main/scala/com/crobox/clickhouse/dsl/language/EmptyFunctionTokenizer.scala b/dsl/src/main/scala/com/crobox/clickhouse/dsl/language/EmptyFunctionTokenizer.scala index 365b5d37..b7c80d1f 100644 --- a/dsl/src/main/scala/com/crobox/clickhouse/dsl/language/EmptyFunctionTokenizer.scala +++ b/dsl/src/main/scala/com/crobox/clickhouse/dsl/language/EmptyFunctionTokenizer.scala @@ -26,5 +26,13 @@ trait EmptyFunctionTokenizer { s"${tokenizeColumn(c.column)} != 0" case _ => s"isNull(${tokenizeColumn(c.column)})" } + case IsNotNull(c) => s"isNotNull(${tokenizeColumn(c.column)})" + case IsNullable(c) => s"isNullable(${tokenizeColumn(c.column)})" + case IsNotDistinctFrom(c, o) => s"isNotDistinctFrom(${tokenizeColumn(c.column)}, ${tokenizeColumn(o.column)})" + case IsZeroOrNull(c) => s"isZeroOrNull(${tokenizeColumn(c.column)})" + case IfNull(c, alt) => s"ifNull(${tokenizeColumn(c.column)}, '$alt')" + case NullIf(c, o) => s"nullIf(${tokenizeColumn(c.column)}, ${tokenizeColumn(o.column)})" + case AssumeNotNull(c) => s"assumeNotNull(${tokenizeColumn(c.column)})" + case ToNullable(c) => s"toNullable(${tokenizeColumn(c.column)})" } } diff --git a/dsl/src/test/scala/com/crobox/clickhouse/dsl/language/EmptyFunctionTokenizerTest.scala b/dsl/src/test/scala/com/crobox/clickhouse/dsl/language/EmptyFunctionTokenizerTest.scala index ea1a5081..aaaf56cf 100644 --- a/dsl/src/test/scala/com/crobox/clickhouse/dsl/language/EmptyFunctionTokenizerTest.scala +++ b/dsl/src/test/scala/com/crobox/clickhouse/dsl/language/EmptyFunctionTokenizerTest.scala @@ -1,60 +1,145 @@ package com.crobox.clickhouse.dsl.language import com.crobox.clickhouse.dsl._ -import com.crobox.clickhouse.{dsl, DslTestSpec} +import com.crobox.clickhouse.{DslTestSpec, dsl} class EmptyFunctionTokenizerTest extends DslTestSpec { it should "UUID empty" in { + val result = toSQL(dsl.empty(nativeUUID)) if (serverVersion.minimalVersion(21, 8)) { - toSQL(dsl.empty(nativeUUID)) should matchSQL("empty(uuid)") + result should matchSQL("empty(uuid)") } else { - toSQL(dsl.empty(nativeUUID)) should matchSQL("uuid == 0") + result should matchSQL("uuid == 0") } } it should "UUID notEmpty" in { + val result = toSQL(dsl.notEmpty(nativeUUID)) if (serverVersion.minimalVersion(21, 8)) { - toSQL(dsl.notEmpty(nativeUUID)) should matchSQL("notEmpty(uuid)") + result should matchSQL("notEmpty(uuid)") } else { - toSQL(dsl.notEmpty(nativeUUID)) should matchSQL("uuid != 0") + result should matchSQL("uuid != 0") } } it should "rewrite empty to empty(0)" in { - val query = select(All()).from(TwoTestTable).where(nativeUUID.empty()) + val query = select(All()).from(TwoTestTable).where(nativeUUID.empty()) + val result = toSql(query.internalQuery, None) if (serverVersion.minimalVersion(21, 8)) { - toSql(query.internalQuery, None) should matchSQL( - s"SELECT * FROM $database.twoTestTable WHERE empty(uuid)" - ) + result should matchSQL(s"SELECT * FROM $database.twoTestTable WHERE empty(uuid)") } else { - toSql(query.internalQuery, None) should matchSQL( - s"SELECT * FROM $database.twoTestTable WHERE uuid == 0" - ) + result should matchSQL(s"SELECT * FROM $database.twoTestTable WHERE uuid == 0") } } it should "rewrite notEmpty to notEquals(0)" in { - var query = select(All()).from(TwoTestTable).where(nativeUUID.notEmpty()) + val query = select(All()).from(TwoTestTable).where(nativeUUID.notEmpty()) + val result = toSql(query.internalQuery, None) if (serverVersion.minimalVersion(21, 8)) { - toSql(query.internalQuery, None) should matchSQL( - s"SELECT * FROM $database.twoTestTable WHERE notEmpty(uuid)" - ) + result should matchSQL(s"SELECT * FROM $database.twoTestTable WHERE notEmpty(uuid)") } else { - toSql(query.internalQuery, None) should matchSQL( - s"SELECT * FROM $database.twoTestTable WHERE uuid != 0" - ) + result should matchSQL(s"SELECT * FROM $database.twoTestTable WHERE uuid != 0") } - query = select(All()).from(TwoTestTable).where(notEmpty(nativeUUID)) + val query2 = select(All()).from(TwoTestTable).where(notEmpty(nativeUUID)) + val result2 = toSql(query2.internalQuery, None) if (serverVersion.minimalVersion(21, 8)) { - toSql(query.internalQuery, None) should matchSQL( - s"SELECT * FROM $database.twoTestTable WHERE notEmpty(uuid)" - ) + result2 should matchSQL(s"SELECT * FROM $database.twoTestTable WHERE notEmpty(uuid)") } else { - toSql(query.internalQuery, None) should matchSQL( - s"SELECT * FROM $database.twoTestTable WHERE uuid != 0" - ) + result2 should matchSQL(s"SELECT * FROM $database.twoTestTable WHERE uuid != 0") } } + + it should "tokenize IsNull" in { + val expected = s"SELECT * FROM $database.twoTestTable WHERE isNull(uuid)" + + val query = select(All()).from(TwoTestTable).where(nativeUUID.isNull()) + toSql(query.internalQuery, None) should matchSQL(expected) + + val query2 = select(All()).from(TwoTestTable).where(isNull(nativeUUID)) + toSql(query2.internalQuery, None) should matchSQL(expected) + } + + it should "tokenize IsNotNull" in { + val expected = s"SELECT * FROM $database.twoTestTable WHERE isNotNull(uuid)" + + val query = select(All()).from(TwoTestTable).where(nativeUUID.isNotNull()) + toSql(query.internalQuery, None) should matchSQL(expected) + + val query2 = select(All()).from(TwoTestTable).where(isNotNull(nativeUUID)) + toSql(query2.internalQuery, None) should matchSQL(expected) + } + + it should "tokenize IsNullable" in { + val expected = s"SELECT isNullable(uuid) FROM $database.twoTestTable" + + val query = select(nativeUUID.isNullable()).from(TwoTestTable) + toSql(query.internalQuery, None) should matchSQL(expected) + + val query2 = select(isNullable(nativeUUID)).from(TwoTestTable) + toSql(query2.internalQuery, None) should matchSQL(expected) + } + + it should "tokenize IsNotDistinctFrom" in { + val expected = s"SELECT isNotDistinctFrom(uuid, uuid) FROM $database.twoTestTable" + + val query = select(nativeUUID.isNotDistinctFrom(nativeUUID)).from(TwoTestTable) + toSql(query.internalQuery, None) should matchSQL(expected) + + val query2 = select(isNotDistinctFrom(nativeUUID, nativeUUID)).from(TwoTestTable) + toSql(query2.internalQuery, None) should matchSQL(expected) + } + + it should "tokenize IsZeroOrNull" in { + val expected = s"SELECT isZeroOrNull(uuid) FROM $database.twoTestTable" + + val query = select(nativeUUID.isZeroOrNull()).from(TwoTestTable) + toSql(query.internalQuery, None) should matchSQL(expected) + + val query2 = select(isZeroOrNull(nativeUUID)).from(TwoTestTable) + toSql(query2.internalQuery, None) should matchSQL(expected) + } + + it should "tokenize IfNull" in { + val defaultValue = "alternative" + val expected = s"SELECT ifNull(uuid, '$defaultValue') FROM $database.twoTestTable" + + val query = select(nativeUUID.ifNull(defaultValue)).from(TwoTestTable) + toSql(query.internalQuery, None) should matchSQL(expected) + + val query2 = select(ifNull(nativeUUID, defaultValue)).from(TwoTestTable) + toSql(query2.internalQuery, None) should matchSQL(expected) + } + + it should "tokenize NullIf" in { + val expected = s"SELECT nullIf(uuid, uuid) FROM $database.twoTestTable" + + val query = select(nativeUUID.nullIf(nativeUUID)).from(TwoTestTable) + toSql(query.internalQuery, None) should matchSQL(expected) + + val query2 = select(nullIf(nativeUUID, nativeUUID)).from(TwoTestTable) + toSql(query2.internalQuery, None) should matchSQL(expected) + } + + it should "tokenize AssumeNotNull" in { + val expected = s"SELECT assumeNotNull(uuid) FROM $database.twoTestTable" + + val query = select(nativeUUID.assumeNotNull()).from(TwoTestTable) + toSql(query.internalQuery, None) should matchSQL(expected) + + val query2 = select(assumeNotNull(nativeUUID)).from(TwoTestTable) + toSql(query2.internalQuery, None) should matchSQL(expected) + } + + it should "tokenize ToNullable" in { + val expected = s"SELECT toNullable(uuid) FROM $database.twoTestTable" + + val query = select(nativeUUID.toNullable()).from(TwoTestTable) + toSql(query.internalQuery, None) should matchSQL(expected) + + val query2 = select(toNullable(nativeUUID)).from(TwoTestTable) + toSql(query2.internalQuery, None) should matchSQL(expected) + } + }