diff --git a/dsl/src/main/scala/com/crobox/clickhouse/dsl/TableColumn.scala b/dsl/src/main/scala/com/crobox/clickhouse/dsl/TableColumn.scala index db0af47a..5d3a4a11 100644 --- a/dsl/src/main/scala/com/crobox/clickhouse/dsl/TableColumn.scala +++ b/dsl/src/main/scala/com/crobox/clickhouse/dsl/TableColumn.scala @@ -3,8 +3,6 @@ package com.crobox.clickhouse.dsl import com.crobox.clickhouse.dsl.marshalling.QueryValue import com.crobox.clickhouse.dsl.schemabuilder.{ColumnType, DefaultValue, TTL} -case object EmptyColumn extends TableColumn("NULL") - trait Column { val name: String lazy val quoted: String = ClickhouseStatement.quoteIdentifier(name) @@ -21,6 +19,8 @@ abstract class TableColumn[+V](val name: String) extends Column { def as[C <: Column](alias: C): AliasedColumn[V] = AliasedColumn(this, alias.name) } +case object EmptyColumn extends TableColumn("NULL") + case class NativeColumn[V](override val name: String, clickhouseType: ColumnType = ColumnType.String, defaultValue: DefaultValue = DefaultValue.NoDefault, diff --git a/dsl/src/main/scala/com/crobox/clickhouse/dsl/column/ClickhouseColumnFunctions.scala b/dsl/src/main/scala/com/crobox/clickhouse/dsl/column/ClickhouseColumnFunctions.scala index 4291fbd0..b98db39b 100644 --- a/dsl/src/main/scala/com/crobox/clickhouse/dsl/column/ClickhouseColumnFunctions.scala +++ b/dsl/src/main/scala/com/crobox/clickhouse/dsl/column/ClickhouseColumnFunctions.scala @@ -24,6 +24,7 @@ trait ClickhouseColumnFunctions with LogicalFunctions with MathematicalFunctions with MiscellaneousFunctions + with NullableFunctions with RandomFunctions with RoundingFunctions with SplitMergeFunctions 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 bc4310ff..2e4b9e89 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,45 +6,17 @@ 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 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) + case class Empty(col: EmptyNonEmptyCol[_]) extends EmptyFunction[Boolean](col.column) + case class NotEmpty(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 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(): Empty = Empty(self) + def notEmpty(): NotEmpty = NotEmpty(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 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[_]) + def empty(col: EmptyNonEmptyCol[_]): Empty = Empty(col: EmptyNonEmptyCol[_]) + def notEmpty(col: EmptyNonEmptyCol[_]): NotEmpty = NotEmpty(col: EmptyNonEmptyCol[_]) + } diff --git a/dsl/src/main/scala/com/crobox/clickhouse/dsl/column/Magnets.scala b/dsl/src/main/scala/com/crobox/clickhouse/dsl/column/Magnets.scala index 5c0e4e54..67e68ccd 100644 --- a/dsl/src/main/scala/com/crobox/clickhouse/dsl/column/Magnets.scala +++ b/dsl/src/main/scala/com/crobox/clickhouse/dsl/column/Magnets.scala @@ -1,11 +1,12 @@ package com.crobox.clickhouse.dsl.column -import java.util.UUID -import com.crobox.clickhouse.dsl.marshalling.{QueryValue, QueryValueFormats} import com.crobox.clickhouse.dsl.marshalling.QueryValueFormats._ +import com.crobox.clickhouse.dsl.marshalling.{QueryValue, QueryValueFormats} import com.crobox.clickhouse.dsl.schemabuilder.ColumnType.SimpleColumnType import com.crobox.clickhouse.dsl.{Const, EmptyColumn, ExpressionColumn, OperationalQuery, Table, TableColumn} import org.joda.time.{DateTime, LocalDate} + +import java.util.UUID import scala.language.implicitConversions trait Magnets { @@ -15,6 +16,7 @@ trait Magnets { with TypeCastFunctions with StringFunctions with EmptyFunctions + with NullableFunctions with StringSearchFunctions with ScalaBooleanFunctions with ScalaStringFunctions @@ -30,7 +32,7 @@ trait Magnets { val column: TableColumn[C] } - // ComparabeWith trait and Cast case class were members of ComparisonFunctions and TypeCastFunctions trait + // ComparableWith trait and Cast case class were members of ComparisonFunctions and TypeCastFunctions trait // respectively. But placing them in the mixin traits causes Scala 3 compiler to crash. Hence, placing these // constructs here is a workaround allowing for the codebase to be compiled with Scala 3. trait ComparableWith[M <: Magnet[_]] { @@ -64,7 +66,7 @@ trait Magnets { * Any constant or column. * Sidenote: The current implementation doesn't represent collections. */ - trait ConstOrColMagnet[+C] extends Magnet[C] with ScalaBooleanFunctionOps with InOps + trait ConstOrColMagnet[+C] extends Magnet[C] with ScalaBooleanFunctionOps with InOps with NullableOps implicit def constOrColMagnetFromCol[C](s: TableColumn[C]): ConstOrColMagnet[C] = new ConstOrColMagnet[C] { diff --git a/dsl/src/main/scala/com/crobox/clickhouse/dsl/column/NullableFunctions.scala b/dsl/src/main/scala/com/crobox/clickhouse/dsl/column/NullableFunctions.scala new file mode 100644 index 00000000..38508c53 --- /dev/null +++ b/dsl/src/main/scala/com/crobox/clickhouse/dsl/column/NullableFunctions.scala @@ -0,0 +1,44 @@ +package com.crobox.clickhouse.dsl.column + +import com.crobox.clickhouse.dsl.{Column, ExpressionColumn} + +trait NullableFunctions { self: Magnets => + + sealed trait NullableFunction + + sealed abstract class AbsNullableFunction[+V](val innerCol: Column) + extends ExpressionColumn[V](innerCol) + with NullableFunction + + case class IsNull(col: ConstOrColMagnet[_]) extends AbsNullableFunction[Boolean](col.column) + case class IsNullable(col: ConstOrColMagnet[_]) extends AbsNullableFunction[Boolean](col.column) + case class IsNotNull(col: ConstOrColMagnet[_]) extends AbsNullableFunction[Boolean](col.column) + case class IsZeroOrNull(col: ConstOrColMagnet[_]) extends AbsNullableFunction[Boolean](col.column) + case class AssumeNotNull(col: ConstOrColMagnet[_]) extends AbsNullableFunction(col.column) + case class ToNullable(col: ConstOrColMagnet[_]) extends AbsNullableFunction(col.column) + case class IfNull(col: ConstOrColMagnet[_], alt: ConstOrColMagnet[_]) extends AbsNullableFunction(col.column) + case class NullIf(col: ConstOrColMagnet[_], other: ConstOrColMagnet[_]) extends AbsNullableFunction(col.column) + + trait NullableOps { + self: ConstOrColMagnet[_] => + + def isNull(): IsNull = IsNull(self) + def isNullable(): IsNullable = IsNullable(self) + def isNotNull(): IsNotNull = IsNotNull(self) + def isZeroOrNull(): IsZeroOrNull = IsZeroOrNull(self) + def ifNull(alternative: ConstOrColMagnet[_]): IfNull = IfNull(self, alternative) + def nullIf(other: ConstOrColMagnet[_]): NullIf = NullIf(self, other) + def assumeNotNull(): AssumeNotNull = AssumeNotNull(self) + def toNullable(): ToNullable = ToNullable(self) + + } + + def isNull(col: ConstOrColMagnet[_]): IsNull = IsNull(col) + def isNullable(col: ConstOrColMagnet[_]): IsNullable = IsNullable(col) + def isNotNull(col: ConstOrColMagnet[_]): IsNotNull = IsNotNull(col) + def isZeroOrNull(col: ConstOrColMagnet[_]): IsZeroOrNull = IsZeroOrNull(col) + def ifNull(col: ConstOrColMagnet[_], alternative: ConstOrColMagnet[_]): IfNull = IfNull(col, alternative) + def nullIf(col: ConstOrColMagnet[_], other: ConstOrColMagnet[_]): NullIf = NullIf(col, other) + def assumeNotNull(col: ConstOrColMagnet[_]): AssumeNotNull = AssumeNotNull(col) + def toNullable(col: ConstOrColMagnet[_]): ToNullable = ToNullable(col) +} diff --git a/dsl/src/main/scala/com/crobox/clickhouse/dsl/language/ClickhouseTokenizerModule.scala b/dsl/src/main/scala/com/crobox/clickhouse/dsl/language/ClickhouseTokenizerModule.scala index 00194fe6..f945dffe 100644 --- a/dsl/src/main/scala/com/crobox/clickhouse/dsl/language/ClickhouseTokenizerModule.scala +++ b/dsl/src/main/scala/com/crobox/clickhouse/dsl/language/ClickhouseTokenizerModule.scala @@ -62,6 +62,7 @@ trait ClickhouseTokenizerModule with LogicalFunctionTokenizer with MathematicalFunctionTokenizer with MiscellaneousFunctionTokenizer + with NullableFunctionTokenizer with RandomFunctionTokenizer with RoundingFunctionTokenizer with SplitMergeFunctionTokenizer @@ -177,6 +178,7 @@ trait ClickhouseTokenizerModule case col: LogicalFunction => tokenizeLogicalFunction(col) case col: MathFuncColumn => tokenizeMathematicalFunction(col) case col: MiscellaneousFunction => tokenizeMiscellaneousFunction(col) + case col: NullableFunction => tokenizeNullableFunction(col) case col: RandomFunction => tokenizeRandomFunction(col) case col: RoundingFunction => tokenizeRoundingFunction(col) case col: SplitMergeFunction[_] => tokenizeSplitMergeFunction(col) 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 b7c80d1f..38ca3416 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 @@ -20,19 +20,5 @@ trait EmptyFunctionTokenizer { s"${tokenizeColumn(c.column)} != 0" case _ => s"notEmpty(${tokenizeColumn(c.column)})" } - case IsNull(c) => - c.column match { - case NativeColumn(_, ColumnType.UUID, _, None) if !ctx.version.minimalVersion(21, 8) => - 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/main/scala/com/crobox/clickhouse/dsl/language/NullableFunctionTokenizer.scala b/dsl/src/main/scala/com/crobox/clickhouse/dsl/language/NullableFunctionTokenizer.scala new file mode 100644 index 00000000..3416a860 --- /dev/null +++ b/dsl/src/main/scala/com/crobox/clickhouse/dsl/language/NullableFunctionTokenizer.scala @@ -0,0 +1,19 @@ +package com.crobox.clickhouse.dsl.language + +import com.crobox.clickhouse.dsl._ + +trait NullableFunctionTokenizer { + self: ClickhouseTokenizerModule => + + protected def tokenizeNullableFunction(col: NullableFunction)(implicit ctx: TokenizeContext): String = + col match { + case IsNull(c) => s"isNull(${tokenizeColumn(c.column)})" + case IsNullable(c) => s"isNullable(${tokenizeColumn(c.column)})" + case IsNotNull(c) => s"isNotNull(${tokenizeColumn(c.column)})" + case IsZeroOrNull(c) => s"isZeroOrNull(${tokenizeColumn(c.column)})" + case AssumeNotNull(c) => s"assumeNotNull(${tokenizeColumn(c.column)})" + case ToNullable(c) => s"toNullable(${tokenizeColumn(c.column)})" + case IfNull(c, alt) => s"ifNull(${tokenizeColumn(c.column)}, ${tokenizeColumn(alt.column)})" + case NullIf(c, o) => s"nullIf(${tokenizeColumn(c.column)}, ${tokenizeColumn(o.column)})" + } +} diff --git a/dsl/src/test/scala/com/crobox/clickhouse/DslTestSpec.scala b/dsl/src/test/scala/com/crobox/clickhouse/DslTestSpec.scala index f4880d32..435e2108 100644 --- a/dsl/src/test/scala/com/crobox/clickhouse/DslTestSpec.scala +++ b/dsl/src/test/scala/com/crobox/clickhouse/DslTestSpec.scala @@ -4,7 +4,7 @@ import com.crobox.clickhouse.dsl.language.{ClickhouseTokenizerModule, TokenizeCo import com.crobox.clickhouse.dsl.{InternalQuery, OperationalQuery, TableColumn} import com.crobox.clickhouse.testkit.ClickhouseMatchers import com.typesafe.config.{Config, ConfigFactory} -import org.scalatest.BeforeAndAfterAll +import org.scalatest.{Assertion, BeforeAndAfterAll} import org.scalatest.concurrent.ScalaFutures import org.scalatest.flatspec.AnyFlatSpec import org.scalatest.matchers.should.Matchers @@ -42,4 +42,8 @@ trait DslTestSpec } } else sql.substring(0, sql.indexOf(" FORMAT")).trim } + + def shouldMatch(query: OperationalQuery, expected: String): Assertion = { + toSql(query.internalQuery, None) should matchSQL(expected) + } } 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 aaaf56cf..80536c19 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 @@ -50,96 +50,4 @@ class EmptyFunctionTokenizerTest extends DslTestSpec { 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) - } - } diff --git a/dsl/src/test/scala/com/crobox/clickhouse/dsl/language/NullableFunctionTokenizerTest.scala b/dsl/src/test/scala/com/crobox/clickhouse/dsl/language/NullableFunctionTokenizerTest.scala new file mode 100644 index 00000000..7f4acbde --- /dev/null +++ b/dsl/src/test/scala/com/crobox/clickhouse/dsl/language/NullableFunctionTokenizerTest.scala @@ -0,0 +1,100 @@ +package com.crobox.clickhouse.dsl.language + +import com.crobox.clickhouse.DslTestSpec +import com.crobox.clickhouse.dsl._ + +class NullableFunctionTokenizerTest extends DslTestSpec { + + it should "tokenize IsNull" in { + val expected = s"SELECT * FROM $database.twoTestTable WHERE isNull(uuid)" + + val q1 = select(All()).from(TwoTestTable).where(nativeUUID.isNull()) + shouldMatch(q1, expected) + + val q2 = select(All()).from(TwoTestTable).where(isNull(nativeUUID)) + shouldMatch(q2, expected) + } + + it should "tokenize IsNull for Constants" in { + val expected = s"SELECT * FROM $database.twoTestTable WHERE isNull(1)" + + val q1 = select(All()).from(TwoTestTable).where(const(1).isNull()) + val q2 = select(All()).from(TwoTestTable).where(1.isNull()) + val q3 = select(All()).from(TwoTestTable).where(isNull(1)) + val q4 = select(All()).from(TwoTestTable).where(isNull(const(1))) + + Seq(q1, q2, q3, q4).foreach(q => shouldMatch(q, 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()) + shouldMatch(query, expected) + + val query2 = select(All()).from(TwoTestTable).where(isNotNull(nativeUUID)) + shouldMatch(query2, expected) + } + + it should "tokenize IsNullable" in { + val expected = s"SELECT isNullable(uuid) FROM $database.twoTestTable" + + val query = select(nativeUUID.isNullable()).from(TwoTestTable) + shouldMatch(query, expected) + + val query2 = select(isNullable(nativeUUID)).from(TwoTestTable) + shouldMatch(query2, expected) + } + + it should "tokenize IsZeroOrNull" in { + val expected = s"SELECT isZeroOrNull(uuid) FROM $database.twoTestTable" + + val query = select(nativeUUID.isZeroOrNull()).from(TwoTestTable) + shouldMatch(query, expected) + + val query2 = select(isZeroOrNull(nativeUUID)).from(TwoTestTable) + shouldMatch(query2, 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) + shouldMatch(query, expected) + + val query2 = select(ifNull(nativeUUID, defaultValue)).from(TwoTestTable) + shouldMatch(query2, expected) + } + + it should "tokenize NullIf" in { + val expected = s"SELECT nullIf(uuid, uuid) FROM $database.twoTestTable" + + val query = select(nativeUUID.nullIf(nativeUUID)).from(TwoTestTable) + shouldMatch(query, expected) + + val query2 = select(nullIf(nativeUUID, nativeUUID)).from(TwoTestTable) + shouldMatch(query2, expected) + } + + it should "tokenize AssumeNotNull" in { + val expected = s"SELECT assumeNotNull(uuid) FROM $database.twoTestTable" + + val query = select(nativeUUID.assumeNotNull()).from(TwoTestTable) + shouldMatch(query, expected) + + val query2 = select(assumeNotNull(nativeUUID)).from(TwoTestTable) + shouldMatch(query2, expected) + } + + it should "tokenize ToNullable" in { + val expected = s"SELECT toNullable(uuid) FROM $database.twoTestTable" + + val query = select(nativeUUID.toNullable()).from(TwoTestTable) + shouldMatch(query, expected) + + val query2 = select(toNullable(nativeUUID)).from(TwoTestTable) + shouldMatch(query2, expected) + } + +}