From 5fbf0147f4239d7f7801014389f70d0883160cdf Mon Sep 17 00:00:00 2001
From: qiaoyuang <qiaoyuang2012@gmail.com>
Date: Tue, 23 Apr 2024 14:39:47 +0800
Subject: [PATCH 1/5] Fix bugs about null values and String elements

---
 CHANGELOG.md                                  |  7 +++++
 .../com/ctrip/sqllin/driver/AndroidCursor.kt  | 31 ++++++++++++++++---
 .../com/ctrip/sqllin/driver/CommonCursor.kt   |  8 ++---
 .../com/ctrip/sqllin/driver/Extension.kt      |  2 +-
 .../com/ctrip/sqllin/driver/JdbcCursor.kt     | 12 ++++---
 .../sqllin/driver/ConcurrentStatement.kt      |  8 ++---
 .../com/ctrip/sqllin/driver/NativeCursor.kt   | 12 +++----
 .../ctrip/sqllin/driver/SQLiteStatement.kt    |  8 ++---
 .../sqllin/driver/cinterop/NativeStatement.kt | 29 +++++++++--------
 .../dsl/sql/compiler/AbstractValuesEncoder.kt | 18 +++++++++--
 .../sqllin/dsl/sql/compiler/QueryDecoder.kt   | 27 +++++++++-------
 .../ctrip/sqllin/dsl/sql/operation/Insert.kt  |  2 +-
 .../dsl/sql/statement/OtherStatement.kt       |  2 +-
 .../com/ctrip/sqllin/dsl/CommonBasicTest.kt   |  1 -
 14 files changed, 111 insertions(+), 56 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6bbf68f..0831543 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,13 @@
 
 - Date format: YYYY-MM-dd
 
+## v1.3.1 / 2024-04-xx
+
+### sqllin-dsl
+
+* Fix a crash when a data class doesn't contain any `String` element.
+* Fix the [issue#81](https://github.com/ctripcorp/SQLlin/issues/81) about insert and query null values
+
 ## v1.3.0 / 2024-04-21
 
 ### All
diff --git a/sqllin-driver/src/androidMain/kotlin/com/ctrip/sqllin/driver/AndroidCursor.kt b/sqllin-driver/src/androidMain/kotlin/com/ctrip/sqllin/driver/AndroidCursor.kt
index 0a2c2a5..e60af32 100644
--- a/sqllin-driver/src/androidMain/kotlin/com/ctrip/sqllin/driver/AndroidCursor.kt
+++ b/sqllin-driver/src/androidMain/kotlin/com/ctrip/sqllin/driver/AndroidCursor.kt
@@ -25,10 +25,33 @@ import android.database.Cursor
 
 internal class AndroidCursor(private val cursor: Cursor) : CommonCursor {
 
-    override fun getInt(columnIndex: Int): Int = cursor.getInt(columnIndex)
-    override fun getLong(columnIndex: Int): Long = cursor.getLong(columnIndex)
-    override fun getFloat(columnIndex: Int): Float = cursor.getFloat(columnIndex)
-    override fun getDouble(columnIndex: Int): Double = cursor.getDouble(columnIndex)
+    override fun getInt(columnIndex: Int): Int? = try {
+        cursor.getInt(columnIndex)
+    } catch (e: Exception) {
+        e.printStackTrace()
+        null
+    }
+
+    override fun getLong(columnIndex: Int): Long? = try {
+        cursor.getLong(columnIndex)
+    } catch (e: Exception) {
+        e.printStackTrace()
+        null
+    }
+
+    override fun getFloat(columnIndex: Int): Float? = try {
+        cursor.getFloat(columnIndex)
+    } catch (e: Exception) {
+        e.printStackTrace()
+        null
+    }
+
+    override fun getDouble(columnIndex: Int): Double? = try {
+        cursor.getDouble(columnIndex)
+    } catch (e: Exception) {
+        e.printStackTrace()
+        null
+    }
 
     override fun getString(columnIndex: Int): String? = try {
         cursor.getString(columnIndex)
diff --git a/sqllin-driver/src/commonMain/kotlin/com/ctrip/sqllin/driver/CommonCursor.kt b/sqllin-driver/src/commonMain/kotlin/com/ctrip/sqllin/driver/CommonCursor.kt
index d76e370..09ea108 100644
--- a/sqllin-driver/src/commonMain/kotlin/com/ctrip/sqllin/driver/CommonCursor.kt
+++ b/sqllin-driver/src/commonMain/kotlin/com/ctrip/sqllin/driver/CommonCursor.kt
@@ -24,10 +24,10 @@ package com.ctrip.sqllin.driver
 @OptIn(ExperimentalStdlibApi::class)
 public interface CommonCursor : AutoCloseable {
 
-    public fun getInt(columnIndex: Int): Int
-    public fun getLong(columnIndex: Int): Long
-    public fun getFloat(columnIndex: Int): Float
-    public fun getDouble(columnIndex: Int): Double
+    public fun getInt(columnIndex: Int): Int?
+    public fun getLong(columnIndex: Int): Long?
+    public fun getFloat(columnIndex: Int): Float?
+    public fun getDouble(columnIndex: Int): Double?
     public fun getString(columnIndex: Int): String?
     public fun getByteArray(columnIndex: Int): ByteArray?
 
diff --git a/sqllin-driver/src/commonMain/kotlin/com/ctrip/sqllin/driver/Extension.kt b/sqllin-driver/src/commonMain/kotlin/com/ctrip/sqllin/driver/Extension.kt
index 101aa28..e6856e1 100644
--- a/sqllin-driver/src/commonMain/kotlin/com/ctrip/sqllin/driver/Extension.kt
+++ b/sqllin-driver/src/commonMain/kotlin/com/ctrip/sqllin/driver/Extension.kt
@@ -93,7 +93,7 @@ internal fun DatabaseConnection.migrateIfNeeded(
 ) = withTransaction {
     val initialVersion = withQuery("PRAGMA user_version;") {
         it.next()
-        it.getInt(0)
+        it.getInt(0) ?: 0
     }
     if (initialVersion == 0) {
         create(this)
diff --git a/sqllin-driver/src/jvmMain/kotlin/com/ctrip/sqllin/driver/JdbcCursor.kt b/sqllin-driver/src/jvmMain/kotlin/com/ctrip/sqllin/driver/JdbcCursor.kt
index 7a66592..065aa57 100644
--- a/sqllin-driver/src/jvmMain/kotlin/com/ctrip/sqllin/driver/JdbcCursor.kt
+++ b/sqllin-driver/src/jvmMain/kotlin/com/ctrip/sqllin/driver/JdbcCursor.kt
@@ -24,13 +24,17 @@ import java.sql.ResultSet
  */
 internal class JdbcCursor(private val resultSet: ResultSet) : CommonCursor {
 
-    override fun getInt(columnIndex: Int): Int = resultSet.getInt(columnIndex + 1)
+    override fun getInt(columnIndex: Int): Int? =
+        resultSet.getInt(columnIndex + 1).takeUnless { resultSet.wasNull() }
 
-    override fun getLong(columnIndex: Int): Long = resultSet.getLong(columnIndex + 1)
+    override fun getLong(columnIndex: Int): Long? =
+        resultSet.getLong(columnIndex + 1).takeUnless { resultSet.wasNull() }
 
-    override fun getFloat(columnIndex: Int): Float = resultSet.getFloat(columnIndex + 1)
+    override fun getFloat(columnIndex: Int): Float? =
+        resultSet.getFloat(columnIndex + 1).takeUnless { resultSet.wasNull() }
 
-    override fun getDouble(columnIndex: Int): Double = resultSet.getDouble(columnIndex + 1)
+    override fun getDouble(columnIndex: Int): Double? =
+        resultSet.getDouble(columnIndex + 1).takeUnless { resultSet.wasNull() }
 
     override fun getString(columnIndex: Int): String? = resultSet.getString(columnIndex + 1)
 
diff --git a/sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/ConcurrentStatement.kt b/sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/ConcurrentStatement.kt
index 270c454..87fb885 100644
--- a/sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/ConcurrentStatement.kt
+++ b/sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/ConcurrentStatement.kt
@@ -30,19 +30,19 @@ internal class ConcurrentStatement(
     private val accessLock: Lock,
 ) : SQLiteStatement {
 
-    override fun columnGetLong(columnIndex: Int): Long = accessLock.withLock {
+    override fun columnGetLong(columnIndex: Int): Long? = accessLock.withLock {
         delegateStatement.columnGetLong(columnIndex)
     }
 
-    override fun columnGetDouble(columnIndex: Int): Double = accessLock.withLock {
+    override fun columnGetDouble(columnIndex: Int): Double? = accessLock.withLock {
         delegateStatement.columnGetDouble(columnIndex)
     }
 
-    override fun columnGetString(columnIndex: Int): String = accessLock.withLock {
+    override fun columnGetString(columnIndex: Int): String? = accessLock.withLock {
         delegateStatement.columnGetString(columnIndex)
     }
 
-    override fun columnGetBlob(columnIndex: Int): ByteArray = accessLock.withLock {
+    override fun columnGetBlob(columnIndex: Int): ByteArray? = accessLock.withLock {
         delegateStatement.columnGetBlob(columnIndex)
     }
 
diff --git a/sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/NativeCursor.kt b/sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/NativeCursor.kt
index fc7c469..f149f5e 100644
--- a/sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/NativeCursor.kt
+++ b/sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/NativeCursor.kt
@@ -25,17 +25,17 @@ internal class NativeCursor(
     private val statement: SQLiteStatement
 ) : CommonCursor {
 
-    override fun getInt(columnIndex: Int): Int = getLong(columnIndex).toInt()
+    override fun getInt(columnIndex: Int): Int? = getLong(columnIndex)?.toInt()
 
-    override fun getLong(columnIndex: Int): Long = statement.columnGetLong(columnIndex)
+    override fun getLong(columnIndex: Int): Long? = statement.columnGetLong(columnIndex)
 
-    override fun getFloat(columnIndex: Int): Float = getDouble(columnIndex).toFloat()
+    override fun getFloat(columnIndex: Int): Float? = getDouble(columnIndex)?.toFloat()
 
-    override fun getDouble(columnIndex: Int): Double = statement.columnGetDouble(columnIndex)
+    override fun getDouble(columnIndex: Int): Double? = statement.columnGetDouble(columnIndex)
 
-    override fun getString(columnIndex: Int): String = statement.columnGetString(columnIndex)
+    override fun getString(columnIndex: Int): String? = statement.columnGetString(columnIndex)
 
-    override fun getByteArray(columnIndex: Int): ByteArray = statement.columnGetBlob(columnIndex)
+    override fun getByteArray(columnIndex: Int): ByteArray? = statement.columnGetBlob(columnIndex)
 
     override fun getColumnIndex(columnName: String): Int = columnNames[columnName] ?: throw IllegalArgumentException("Col for $columnName not found")
 
diff --git a/sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/SQLiteStatement.kt b/sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/SQLiteStatement.kt
index 6d6c9ca..afb7e6c 100644
--- a/sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/SQLiteStatement.kt
+++ b/sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/SQLiteStatement.kt
@@ -23,13 +23,13 @@ package com.ctrip.sqllin.driver
 
 internal interface SQLiteStatement {
 
-    fun columnGetLong(columnIndex: Int): Long
+    fun columnGetLong(columnIndex: Int): Long?
 
-    fun columnGetDouble(columnIndex: Int): Double
+    fun columnGetDouble(columnIndex: Int): Double?
 
-    fun columnGetString(columnIndex: Int): String
+    fun columnGetString(columnIndex: Int): String?
 
-    fun columnGetBlob(columnIndex: Int): ByteArray
+    fun columnGetBlob(columnIndex: Int): ByteArray?
 
     fun columnCount(): Int
 
diff --git a/sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/cinterop/NativeStatement.kt b/sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/cinterop/NativeStatement.kt
index 0780dd7..f73307e 100644
--- a/sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/cinterop/NativeStatement.kt
+++ b/sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/cinterop/NativeStatement.kt
@@ -66,28 +66,31 @@ internal class NativeStatement(
 ) : SQLiteStatement {
 
     // Cursor methods
-    fun isNull(index: Int): Boolean =
+    private fun isNull(index: Int): Boolean =
         sqlite3_column_type(cStatementPointer, index) == SQLITE_NULL
 
-    override fun columnGetLong(columnIndex: Int): Long =
-        sqlite3_column_int64(cStatementPointer, columnIndex)
+    override fun columnGetLong(columnIndex: Int): Long? =
+        if (isNull(columnIndex)) null else sqlite3_column_int64(cStatementPointer, columnIndex)
 
-    override fun columnGetDouble(columnIndex: Int): Double =
-        sqlite3_column_double(cStatementPointer, columnIndex)
+    override fun columnGetDouble(columnIndex: Int): Double? =
+        if (isNull(columnIndex)) null else sqlite3_column_double(cStatementPointer, columnIndex)
 
-    override fun columnGetString(columnIndex: Int): String =
-        sqlite3_column_text(cStatementPointer, columnIndex)
-            ?.reinterpret<ByteVar>()
-            ?.let { bytesToString(it) }
-            ?: ""
+    override fun columnGetString(columnIndex: Int): String? =
+        if (isNull(columnIndex))
+            null
+        else
+            sqlite3_column_text(cStatementPointer, columnIndex)
+                ?.reinterpret<ByteVar>()
+                ?.let { bytesToString(it) }
 
-    override fun columnGetBlob(columnIndex: Int): ByteArray {
+    override fun columnGetBlob(columnIndex: Int): ByteArray? {
+        if (isNull(columnIndex))
+            return null
         val blobSize = sqlite3_column_bytes(cStatementPointer, columnIndex)
         return if (blobSize == 0)
-            byteArrayOf()
+            null
         else
             sqlite3_column_blob(cStatementPointer, columnIndex)?.readBytes(blobSize)
-                ?: throw sqliteException("Byte array size/type issue col $columnIndex")
     }
 
     override fun columnCount(): Int = sqlite3_column_count(cStatementPointer)
diff --git a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/compiler/AbstractValuesEncoder.kt b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/compiler/AbstractValuesEncoder.kt
index de1067d..58345fc 100644
--- a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/compiler/AbstractValuesEncoder.kt
+++ b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/compiler/AbstractValuesEncoder.kt
@@ -17,6 +17,7 @@
 package com.ctrip.sqllin.dsl.sql.compiler
 
 import kotlinx.serialization.ExperimentalSerializationApi
+import kotlinx.serialization.SerializationStrategy
 import kotlinx.serialization.descriptors.SerialDescriptor
 import kotlinx.serialization.encoding.AbstractEncoder
 import kotlinx.serialization.modules.EmptySerializersModule
@@ -44,8 +45,7 @@ internal abstract class AbstractValuesEncoder : AbstractEncoder() {
         get() = sqlStrBuilder.toString()
 
     override fun encodeElement(descriptor: SerialDescriptor, index: Int): Boolean {
-        if (index == 0)
-            elementsCount = descriptor.elementsCount
+        elementsCount = descriptor.elementsCount
         elementsIndex = index
         return true
     }
@@ -83,5 +83,19 @@ internal abstract class AbstractValuesEncoder : AbstractEncoder() {
         sqlStrBuilder.append(value).appendTail()
     }
 
+    override fun <T : Any> encodeNullableSerializableElement(
+        descriptor: SerialDescriptor,
+        index: Int,
+        serializer: SerializationStrategy<T>,
+        value: T?
+    ) {
+        if (value == null) {
+            elementsCount = descriptor.elementsCount
+            elementsIndex = index
+            sqlStrBuilder.append("NULL").appendTail()
+        } else
+            super.encodeNullableSerializableElement(descriptor, index, serializer, value)
+    }
+
     override fun encodeEnum(enumDescriptor: SerialDescriptor, index: Int) = encodeInt(index)
 }
\ No newline at end of file
diff --git a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/compiler/QueryDecoder.kt b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/compiler/QueryDecoder.kt
index d099141..15ff878 100644
--- a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/compiler/QueryDecoder.kt
+++ b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/compiler/QueryDecoder.kt
@@ -37,6 +37,7 @@ internal class QueryDecoder(
 
     private var elementIndex = 0
     private var elementName = ""
+    private var elementNotNullMark = true
 
     override val serializersModule: SerializersModule = EmptySerializersModule()
 
@@ -45,6 +46,7 @@ internal class QueryDecoder(
             CompositeDecoder.DECODE_DONE
         else {
             elementName = descriptor.getElementName(elementIndex)
+            elementNotNullMark = !descriptor.getElementDescriptor(elementIndex).isNullable
             val resultIndex = elementIndex++
             if (cursorColumnIndex >= 0)
                 resultIndex
@@ -57,22 +59,25 @@ internal class QueryDecoder(
     private inline val cursorColumnIndex
         get() = cursor.getColumnIndex(elementName)
 
+    private inline val sqlNullException
+        get() = IllegalStateException("The value in database is null, please declare your property be nullable")
+
     private inline fun <T> deserialize(block: (Int) -> T): T = cursorColumnIndex.let {
         if (it >= 0) block(it) else throw SerializationException("The Cursor doesn't have this column")
     }
 
-    override fun decodeBoolean(): Boolean = deserialize { cursor.getInt(it) > 0 }
-    override fun decodeByte(): Byte = deserialize { cursor.getInt(it).toByte() }
-    override fun decodeShort(): Short = deserialize { cursor.getInt(it).toShort() }
-    override fun decodeInt(): Int = deserialize { cursor.getInt(it) }
-    override fun decodeLong(): Long = deserialize { cursor.getLong(it) }
-    override fun decodeChar(): Char = deserialize { cursor.getString(it)?.first() ?: '\u0000' }
-    override fun decodeString(): String = deserialize { cursor.getString(it) ?: "" }
-    override fun decodeFloat(): Float = deserialize { cursor.getFloat(it) }
-    override fun decodeDouble(): Double = deserialize { cursor.getDouble(it) }
-    override fun decodeEnum(enumDescriptor: SerialDescriptor): Int = deserialize { cursor.getInt(it) }
+    override fun decodeBoolean(): Boolean = deserialize { (cursor.getInt(it) ?: throw sqlNullException) > 0 }
+    override fun decodeByte(): Byte = deserialize { cursor.getInt(it)?.toByte() ?: throw sqlNullException }
+    override fun decodeShort(): Short = deserialize { cursor.getInt(it)?.toShort() ?: throw sqlNullException }
+    override fun decodeInt(): Int = deserialize { cursor.getInt(it) ?: throw sqlNullException }
+    override fun decodeLong(): Long = deserialize { cursor.getLong(it) ?: throw sqlNullException }
+    override fun decodeChar(): Char = deserialize { cursor.getString(it)?.first() ?: throw sqlNullException }
+    override fun decodeString(): String = deserialize { cursor.getString(it) ?: throw sqlNullException }
+    override fun decodeFloat(): Float = deserialize { cursor.getFloat(it) ?: throw sqlNullException }
+    override fun decodeDouble(): Double = deserialize { cursor.getDouble(it) ?: throw sqlNullException }
+    override fun decodeEnum(enumDescriptor: SerialDescriptor): Int = deserialize { cursor.getInt(it) ?: throw sqlNullException }
 
-    override fun decodeNotNullMark(): Boolean = cursorColumnIndex >= 0
+    override fun decodeNotNullMark(): Boolean = elementNotNullMark
 
     // override fun decodeCollectionSize(descriptor: SerialDescriptor): Int {}
 }
\ No newline at end of file
diff --git a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/operation/Insert.kt b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/operation/Insert.kt
index 796f2e8..e2bc531 100644
--- a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/operation/Insert.kt
+++ b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/operation/Insert.kt
@@ -40,6 +40,6 @@ internal object Insert : Operation {
             append(' ')
             append(encodeEntities2InsertValues(table.kSerializer(), entities, parameters))
         }
-        return InsertStatement(sql, connection, parameters)
+        return InsertStatement(sql, connection, parameters.takeIf { it.isNotEmpty() })
     }
 }
\ No newline at end of file
diff --git a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/statement/OtherStatement.kt b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/statement/OtherStatement.kt
index 0a3cae2..6e94365 100644
--- a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/statement/OtherStatement.kt
+++ b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/statement/OtherStatement.kt
@@ -43,7 +43,7 @@ public class UpdateDeleteStatement internal constructor(
 public class InsertStatement internal constructor(
     sqlStr: String,
     private val connection: DatabaseConnection,
-    override val parameters: MutableList<String>,
+    override val parameters: MutableList<String>?,
 ) : SingleStatement(sqlStr) {
     public override fun execute(): Unit = connection.executeInsert(sqlStr, params)
 }
diff --git a/sqllin-dsl/src/commonTest/kotlin/com/ctrip/sqllin/dsl/CommonBasicTest.kt b/sqllin-dsl/src/commonTest/kotlin/com/ctrip/sqllin/dsl/CommonBasicTest.kt
index b2a63bf..3cf09eb 100644
--- a/sqllin-dsl/src/commonTest/kotlin/com/ctrip/sqllin/dsl/CommonBasicTest.kt
+++ b/sqllin-dsl/src/commonTest/kotlin/com/ctrip/sqllin/dsl/CommonBasicTest.kt
@@ -24,7 +24,6 @@ import com.ctrip.sqllin.dsl.sql.clause.OrderByWay.ASC
 import com.ctrip.sqllin.dsl.sql.clause.OrderByWay.DESC
 import com.ctrip.sqllin.dsl.sql.statement.SelectStatement
 import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.delay
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.runBlocking
 import kotlin.test.assertEquals

From 5f046417f1ab42d08483acfa830e630a9213113a Mon Sep 17 00:00:00 2001
From: qiaoyuang <qiaoyuang2012@gmail.com>
Date: Tue, 23 Apr 2024 22:12:33 +0800
Subject: [PATCH 2/5] Add the null-values unit tests

---
 .../com/ctrip/sqllin/dsl/AndroidTest.kt       |  3 +++
 .../dsl/sql/compiler/AbstractValuesEncoder.kt | 15 ++---------
 .../com/ctrip/sqllin/dsl/CommonBasicTest.kt   | 25 ++++++++++++++++++
 .../kotlin/com/ctrip/sqllin/dsl/Entities.kt   | 26 ++++++++++++-------
 .../kotlin/com/ctrip/sqllin/dsl/JvmTest.kt    |  3 +++
 .../kotlin/com/ctrip/sqllin/dsl/NativeTest.kt |  3 +++
 6 files changed, 53 insertions(+), 22 deletions(-)

diff --git a/sqllin-dsl/src/androidInstrumentedTest/kotlin/com/ctrip/sqllin/dsl/AndroidTest.kt b/sqllin-dsl/src/androidInstrumentedTest/kotlin/com/ctrip/sqllin/dsl/AndroidTest.kt
index a5ec336..3a75f15 100644
--- a/sqllin-dsl/src/androidInstrumentedTest/kotlin/com/ctrip/sqllin/dsl/AndroidTest.kt
+++ b/sqllin-dsl/src/androidInstrumentedTest/kotlin/com/ctrip/sqllin/dsl/AndroidTest.kt
@@ -74,6 +74,9 @@ class AndroidTest {
     @Test
     fun testPrimitiveTypeForKSP() = commonTest.testPrimitiveTypeForKSP()
 
+    @Test
+    fun testNullInsertAndSelect() = commonTest.testNullInsertAndSelect()
+
     @Before
     fun setUp() {
         val context = InstrumentationRegistry.getInstrumentation().targetContext
diff --git a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/compiler/AbstractValuesEncoder.kt b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/compiler/AbstractValuesEncoder.kt
index 58345fc..41c6b95 100644
--- a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/compiler/AbstractValuesEncoder.kt
+++ b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/compiler/AbstractValuesEncoder.kt
@@ -17,7 +17,6 @@
 package com.ctrip.sqllin.dsl.sql.compiler
 
 import kotlinx.serialization.ExperimentalSerializationApi
-import kotlinx.serialization.SerializationStrategy
 import kotlinx.serialization.descriptors.SerialDescriptor
 import kotlinx.serialization.encoding.AbstractEncoder
 import kotlinx.serialization.modules.EmptySerializersModule
@@ -83,18 +82,8 @@ internal abstract class AbstractValuesEncoder : AbstractEncoder() {
         sqlStrBuilder.append(value).appendTail()
     }
 
-    override fun <T : Any> encodeNullableSerializableElement(
-        descriptor: SerialDescriptor,
-        index: Int,
-        serializer: SerializationStrategy<T>,
-        value: T?
-    ) {
-        if (value == null) {
-            elementsCount = descriptor.elementsCount
-            elementsIndex = index
-            sqlStrBuilder.append("NULL").appendTail()
-        } else
-            super.encodeNullableSerializableElement(descriptor, index, serializer, value)
+    override fun encodeNull() {
+        sqlStrBuilder.append("NULL").appendTail()
     }
 
     override fun encodeEnum(enumDescriptor: SerialDescriptor, index: Int) = encodeInt(index)
diff --git a/sqllin-dsl/src/commonTest/kotlin/com/ctrip/sqllin/dsl/CommonBasicTest.kt b/sqllin-dsl/src/commonTest/kotlin/com/ctrip/sqllin/dsl/CommonBasicTest.kt
index 3cf09eb..a985f64 100644
--- a/sqllin-dsl/src/commonTest/kotlin/com/ctrip/sqllin/dsl/CommonBasicTest.kt
+++ b/sqllin-dsl/src/commonTest/kotlin/com/ctrip/sqllin/dsl/CommonBasicTest.kt
@@ -40,6 +40,7 @@ class CommonBasicTest(private val path: DatabasePath) {
         const val DATABASE_NAME = "BookStore.db"
         const val SQL_CREATE_BOOK = "create table book (id integer primary key autoincrement, name text, author text, pages integer, price real)"
         const val SQL_CREATE_CATEGORY = "create table category (id integer primary key autoincrement, name text, code integer)"
+        const val SQL_CREATE_NULL_TESTER = "create table NullTester (id integer primary key autoincrement, paramInt integer, paramString text, paramDouble real)"
     }
 
     private inline fun Database.databaseAutoClose(block: (Database) -> Unit) = try {
@@ -427,6 +428,30 @@ class CommonBasicTest(private val path: DatabasePath) {
         }
     }
 
+    fun testNullInsertAndSelect() {
+        val config = DatabaseConfiguration(
+            name = DATABASE_NAME,
+            path = path,
+            version = 1,
+            create = {
+                it.execSQL(SQL_CREATE_NULL_TESTER)
+            }
+        )
+        Database(config, true).databaseAutoClose { database ->
+            lateinit var selectStatement: SelectStatement<NullTester>
+            database {
+                NullTesterTable { table ->
+                    table INSERT listOf(
+                        NullTester(null, null, null),
+                        NullTester(8, "888", 8.8),
+                    )
+                    selectStatement = table SELECT X
+                }
+            }
+            assertEquals(2, selectStatement.getResults().size)
+        }
+    }
+
     private fun getDefaultDBConfig(): DatabaseConfiguration =
         DatabaseConfiguration(
             name = DATABASE_NAME,
diff --git a/sqllin-dsl/src/commonTest/kotlin/com/ctrip/sqllin/dsl/Entities.kt b/sqllin-dsl/src/commonTest/kotlin/com/ctrip/sqllin/dsl/Entities.kt
index 73451d1..445e731 100644
--- a/sqllin-dsl/src/commonTest/kotlin/com/ctrip/sqllin/dsl/Entities.kt
+++ b/sqllin-dsl/src/commonTest/kotlin/com/ctrip/sqllin/dsl/Entities.kt
@@ -42,17 +42,25 @@ data class Category(
 
 @Serializable
 data class Joiner(
-    val name: String,
-    val author: String,
-    val price: Double,
-    val pages: Int,
-    val code: Int,
+    val name: String?,
+    val author: String?,
+    val price: Double?,
+    val pages: Int?,
+    val code: Int?,
 )
 
 @Serializable
 data class CrossJoiner(
-    val author: String,
-    val price: Double,
-    val pages: Int,
-    val code: Int,
+    val author: String?,
+    val price: Double?,
+    val pages: Int?,
+    val code: Int?,
+)
+
+@DBRow("NullTester")
+@Serializable
+data class NullTester(
+    val paramInt: Int?,
+    val paramString: String?,
+    val paramDouble: Double?,
 )
\ No newline at end of file
diff --git a/sqllin-dsl/src/jvmTest/kotlin/com/ctrip/sqllin/dsl/JvmTest.kt b/sqllin-dsl/src/jvmTest/kotlin/com/ctrip/sqllin/dsl/JvmTest.kt
index 803bc0a..9f75932 100644
--- a/sqllin-dsl/src/jvmTest/kotlin/com/ctrip/sqllin/dsl/JvmTest.kt
+++ b/sqllin-dsl/src/jvmTest/kotlin/com/ctrip/sqllin/dsl/JvmTest.kt
@@ -65,6 +65,9 @@ class JvmTest {
     @Test
     fun testPrimitiveTypeForKSP() = commonTest.testPrimitiveTypeForKSP()
 
+    @Test
+    fun testNullInsertAndSelect() = commonTest.testNullInsertAndSelect()
+
     @BeforeTest
     fun setUp() {
         deleteDatabase(path, CommonBasicTest.DATABASE_NAME)
diff --git a/sqllin-dsl/src/nativeTest/kotlin/com/ctrip/sqllin/dsl/NativeTest.kt b/sqllin-dsl/src/nativeTest/kotlin/com/ctrip/sqllin/dsl/NativeTest.kt
index bf06a11..4b4ab0c 100644
--- a/sqllin-dsl/src/nativeTest/kotlin/com/ctrip/sqllin/dsl/NativeTest.kt
+++ b/sqllin-dsl/src/nativeTest/kotlin/com/ctrip/sqllin/dsl/NativeTest.kt
@@ -68,6 +68,9 @@ class NativeTest {
     @Test
     fun testPrimitiveTypeForKSP() = commonTest.testPrimitiveTypeForKSP()
 
+    @Test
+    fun testNullInsertAndSelect() = commonTest.testNullInsertAndSelect()
+
     @BeforeTest
     fun setUp() {
         deleteDatabase(path, CommonBasicTest.DATABASE_NAME)

From 8b286c5e3b0c30be62025732b40d8f9ff4a1d5d5 Mon Sep 17 00:00:00 2001
From: qiaoyuang <qiaoyuang2012@gmail.com>
Date: Wed, 24 Apr 2024 13:23:37 +0800
Subject: [PATCH 3/5] Fix some bugs

---
 CHANGELOG.md                                  |  9 +++-
 .../com/ctrip/sqllin/driver/AndroidCursor.kt  | 24 +++++------
 .../com/ctrip/sqllin/driver/CommonCursor.kt   | 15 +++----
 .../ctrip/sqllin/driver/SQLiteException.kt    | 24 +++++++++++
 .../ctrip/sqllin/driver/CommonBasicTest.kt    | 12 ++++--
 .../com/ctrip/sqllin/driver/JdbcCursor.kt     | 43 +++++++++++++------
 .../sqllin/driver/ConcurrentStatement.kt      |  8 +++-
 .../com/ctrip/sqllin/driver/NativeCursor.kt   | 24 ++++++-----
 ...SQLiteException.kt => SQLiteResultCode.kt} |  8 +---
 .../ctrip/sqllin/driver/SQLiteStatement.kt    |  6 ++-
 .../sqllin/driver/cinterop/NativeStatement.kt | 23 +++++-----
 .../sqllin/dsl/sql/clause/ClauseNumber.kt     |  4 +-
 .../sqllin/dsl/sql/clause/ClauseString.kt     | 17 ++++----
 .../ctrip/sqllin/dsl/sql/clause/SetClause.kt  |  4 +-
 .../sqllin/dsl/sql/compiler/QueryDecoder.kt   | 31 ++++++-------
 .../com/ctrip/sqllin/dsl/CommonBasicTest.kt   | 43 ++++++++++++++++++-
 16 files changed, 188 insertions(+), 107 deletions(-)
 create mode 100644 sqllin-driver/src/commonMain/kotlin/com/ctrip/sqllin/driver/SQLiteException.kt
 rename sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/{SQLiteException.kt => SQLiteResultCode.kt} (87%)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0831543..da4de98 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,12 +2,19 @@
 
 - Date format: YYYY-MM-dd
 
-## v1.3.1 / 2024-04-xx
+## v1.3.1 / 2024-04-24
 
 ### sqllin-dsl
 
 * Fix a crash when a data class doesn't contain any `String` element.
 * Fix the [issue#81](https://github.com/ctripcorp/SQLlin/issues/81) about insert and query null values
+* Fix some wrongs about generation of SQL syntax
+
+### sqllin-driver
+
+* **Breaking change**: Remove the deprecated API `CommonCursor#forEachRows`
+* **Breaking change**: the `getInt`, `getLong`, `getFloat` and `getDouble` will throw an exception when the value is NULl in SQLite
+* Add a new public API: `CommonCursor#isNull`, for check if the value is NULL in SQLite
 
 ## v1.3.0 / 2024-04-21
 
diff --git a/sqllin-driver/src/androidMain/kotlin/com/ctrip/sqllin/driver/AndroidCursor.kt b/sqllin-driver/src/androidMain/kotlin/com/ctrip/sqllin/driver/AndroidCursor.kt
index e60af32..72279e7 100644
--- a/sqllin-driver/src/androidMain/kotlin/com/ctrip/sqllin/driver/AndroidCursor.kt
+++ b/sqllin-driver/src/androidMain/kotlin/com/ctrip/sqllin/driver/AndroidCursor.kt
@@ -25,32 +25,32 @@ import android.database.Cursor
 
 internal class AndroidCursor(private val cursor: Cursor) : CommonCursor {
 
-    override fun getInt(columnIndex: Int): Int? = try {
+    override fun getInt(columnIndex: Int): Int = try {
         cursor.getInt(columnIndex)
     } catch (e: Exception) {
         e.printStackTrace()
-        null
+        throw SQLiteException("The value of column $columnIndex is NULL")
     }
 
-    override fun getLong(columnIndex: Int): Long? = try {
+    override fun getLong(columnIndex: Int): Long = try {
         cursor.getLong(columnIndex)
     } catch (e: Exception) {
         e.printStackTrace()
-        null
+        throw SQLiteException("The value of column $columnIndex is NULL")
     }
 
-    override fun getFloat(columnIndex: Int): Float? = try {
+    override fun getFloat(columnIndex: Int): Float = try {
         cursor.getFloat(columnIndex)
     } catch (e: Exception) {
         e.printStackTrace()
-        null
+        throw SQLiteException("The value of column $columnIndex is NULL")
     }
 
-    override fun getDouble(columnIndex: Int): Double? = try {
+    override fun getDouble(columnIndex: Int): Double = try {
         cursor.getDouble(columnIndex)
     } catch (e: Exception) {
         e.printStackTrace()
-        null
+        throw SQLiteException("The value of column $columnIndex is NULL")
     }
 
     override fun getString(columnIndex: Int): String? = try {
@@ -69,12 +69,6 @@ internal class AndroidCursor(private val cursor: Cursor) : CommonCursor {
 
     override fun getColumnIndex(columnName: String): Int = cursor.getColumnIndexOrThrow(columnName)
 
-    @Deprecated(
-        message = "Please use the new API: forEachRow",
-        replaceWith = ReplaceWith(expression = "forEachRow"),
-    )
-    override fun forEachRows(block: (Int) -> Unit) = forEachRow(block)
-
     override fun forEachRow(block: (Int) -> Unit) {
         if (!cursor.moveToFirst()) return
         var index = 0
@@ -84,5 +78,7 @@ internal class AndroidCursor(private val cursor: Cursor) : CommonCursor {
 
     override fun next(): Boolean = cursor.moveToNext()
 
+    override fun isNull(columnIndex: Int): Boolean = cursor.isNull(columnIndex)
+
     override fun close() = cursor.close()
 }
\ No newline at end of file
diff --git a/sqllin-driver/src/commonMain/kotlin/com/ctrip/sqllin/driver/CommonCursor.kt b/sqllin-driver/src/commonMain/kotlin/com/ctrip/sqllin/driver/CommonCursor.kt
index 09ea108..7e01a65 100644
--- a/sqllin-driver/src/commonMain/kotlin/com/ctrip/sqllin/driver/CommonCursor.kt
+++ b/sqllin-driver/src/commonMain/kotlin/com/ctrip/sqllin/driver/CommonCursor.kt
@@ -24,23 +24,20 @@ package com.ctrip.sqllin.driver
 @OptIn(ExperimentalStdlibApi::class)
 public interface CommonCursor : AutoCloseable {
 
-    public fun getInt(columnIndex: Int): Int?
-    public fun getLong(columnIndex: Int): Long?
-    public fun getFloat(columnIndex: Int): Float?
-    public fun getDouble(columnIndex: Int): Double?
+    public fun getInt(columnIndex: Int): Int
+    public fun getLong(columnIndex: Int): Long
+    public fun getFloat(columnIndex: Int): Float
+    public fun getDouble(columnIndex: Int): Double
     public fun getString(columnIndex: Int): String?
     public fun getByteArray(columnIndex: Int): ByteArray?
 
     public fun getColumnIndex(columnName: String): Int
 
-    @Deprecated(
-        message = "Please use the new API: forEachRow",
-        replaceWith = ReplaceWith(expression = "forEachRow"),
-    )
-    public fun forEachRows(block: (Int) -> Unit)
     public fun forEachRow(block: (Int) -> Unit)
 
     public fun next(): Boolean
 
+    public fun isNull(columnIndex: Int): Boolean
+
     public override fun close()
 }
\ No newline at end of file
diff --git a/sqllin-driver/src/commonMain/kotlin/com/ctrip/sqllin/driver/SQLiteException.kt b/sqllin-driver/src/commonMain/kotlin/com/ctrip/sqllin/driver/SQLiteException.kt
new file mode 100644
index 0000000..f3b49e5
--- /dev/null
+++ b/sqllin-driver/src/commonMain/kotlin/com/ctrip/sqllin/driver/SQLiteException.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2024 Trip.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.ctrip.sqllin.driver
+
+/**
+ * The exceptions about SQLite, they include the native SQLite result codes and error message
+ * @author Yuang Qiao
+ */
+
+public open class SQLiteException(message: String) : Exception(message)
\ No newline at end of file
diff --git a/sqllin-driver/src/commonTest/kotlin/com/ctrip/sqllin/driver/CommonBasicTest.kt b/sqllin-driver/src/commonTest/kotlin/com/ctrip/sqllin/driver/CommonBasicTest.kt
index 30787d8..6b44705 100644
--- a/sqllin-driver/src/commonTest/kotlin/com/ctrip/sqllin/driver/CommonBasicTest.kt
+++ b/sqllin-driver/src/commonTest/kotlin/com/ctrip/sqllin/driver/CommonBasicTest.kt
@@ -28,15 +28,17 @@ class CommonBasicTest(private val path: DatabasePath) {
 
     private class Book(
         val name: String,
-        val author: String,
+        val author: String?,
         val pages: Int,
         val price: Double,
-        val array: ByteArray,
+        val array: ByteArray?,
     )
 
     private val bookList = listOf(
         Book(name = "The Da Vinci Code", author = "Dan Brown", pages = 454, price = 16.96, byteArrayOf()),
         Book(name = "The Lost Symbol", author = "Dan Brown", pages = 510, price = 19.95, byteArrayOf(1, 2, 3)),
+        Book(name = "", author = "Dan Brown", pages = 454, price = 16.96, byteArrayOf()),
+        Book(name = "The Lost Symbol", author = null, pages = 510, price = 19.95, null),
     )
 
     fun testCreateAndUpgrade() {
@@ -87,6 +89,8 @@ class CommonBasicTest(private val path: DatabasePath) {
             it.withTransaction { connection ->
                 connection.executeInsert(SQL.INSERT_BOOK, arrayOf("The Da Vinci Code", "Dan Brown", 454, 16.96, byteArrayOf()))
                 connection.executeInsert(SQL.INSERT_BOOK, arrayOf("The Lost Symbol", "Dan Brown", 510, 19.95, byteArrayOf(1, 2, 3)))
+                connection.executeInsert(SQL.INSERT_BOOK, arrayOf("", "Dan Brown", 454, 16.96, byteArrayOf()))
+                connection.executeInsert(SQL.INSERT_BOOK, arrayOf("The Lost Symbol", null, 510, 19.95, null))
             }
         }
         val readOnlyConfig = getDefaultDBConfig(true)
@@ -99,7 +103,7 @@ class CommonBasicTest(private val path: DatabasePath) {
                     assertEquals(book.author, cursor.getString(++columnIndex))
                     assertEquals(book.pages, cursor.getInt(++columnIndex))
                     assertEquals(book.price, cursor.getDouble(++columnIndex))
-                    assertEquals(book.array.size, cursor.getByteArray(++columnIndex)?.size)
+                    assertEquals(book.array?.size, cursor.getByteArray(++columnIndex)?.size)
                 }
             }
         }
@@ -204,7 +208,7 @@ class CommonBasicTest(private val path: DatabasePath) {
                             assertEquals(book.author, cursor.getString(++columnIndex))
                             assertEquals(book.pages, cursor.getInt(++columnIndex))
                             assertEquals(book.price, cursor.getDouble(++columnIndex))
-                            assertEquals(book.array.size, cursor.getByteArray(++columnIndex)?.size)
+                            assertEquals(book.array?.size, cursor.getByteArray(++columnIndex)?.size)
                         }
                     }
                 }
diff --git a/sqllin-driver/src/jvmMain/kotlin/com/ctrip/sqllin/driver/JdbcCursor.kt b/sqllin-driver/src/jvmMain/kotlin/com/ctrip/sqllin/driver/JdbcCursor.kt
index 065aa57..9fdd4b2 100644
--- a/sqllin-driver/src/jvmMain/kotlin/com/ctrip/sqllin/driver/JdbcCursor.kt
+++ b/sqllin-driver/src/jvmMain/kotlin/com/ctrip/sqllin/driver/JdbcCursor.kt
@@ -24,17 +24,33 @@ import java.sql.ResultSet
  */
 internal class JdbcCursor(private val resultSet: ResultSet) : CommonCursor {
 
-    override fun getInt(columnIndex: Int): Int? =
-        resultSet.getInt(columnIndex + 1).takeUnless { resultSet.wasNull() }
+    override fun getInt(columnIndex: Int): Int {
+        val result = resultSet.getInt(columnIndex + 1)
+        if (resultSet.wasNull())
+            throw SQLiteException("The value of column $columnIndex is NULL")
+        return result
+    }
 
-    override fun getLong(columnIndex: Int): Long? =
-        resultSet.getLong(columnIndex + 1).takeUnless { resultSet.wasNull() }
+    override fun getLong(columnIndex: Int): Long {
+        val result = resultSet.getLong(columnIndex + 1)
+        if (resultSet.wasNull())
+            throw SQLiteException("The value of column $columnIndex is NULL")
+        return result
+    }
 
-    override fun getFloat(columnIndex: Int): Float? =
-        resultSet.getFloat(columnIndex + 1).takeUnless { resultSet.wasNull() }
+    override fun getFloat(columnIndex: Int): Float {
+        val result = resultSet.getFloat(columnIndex + 1)
+        if (resultSet.wasNull())
+            throw SQLiteException("The value of column $columnIndex is NULL")
+        return result
+    }
 
-    override fun getDouble(columnIndex: Int): Double? =
-        resultSet.getDouble(columnIndex + 1).takeUnless { resultSet.wasNull() }
+    override fun getDouble(columnIndex: Int): Double {
+        val result = resultSet.getDouble(columnIndex + 1)
+        if (resultSet.wasNull())
+            throw SQLiteException("The value of column $columnIndex is NULL")
+        return result
+    }
 
     override fun getString(columnIndex: Int): String? = resultSet.getString(columnIndex + 1)
 
@@ -42,12 +58,6 @@ internal class JdbcCursor(private val resultSet: ResultSet) : CommonCursor {
 
     override fun getColumnIndex(columnName: String): Int = resultSet.findColumn(columnName) - 1
 
-    @Deprecated(
-        message = "Please use the new API: forEachRow",
-        replaceWith = ReplaceWith(expression = "forEachRow"),
-    )
-    override fun forEachRows(block: (Int) -> Unit) = forEachRow(block)
-
     override fun forEachRow(block: (Int) -> Unit) {
         var index = 0
         while (next())
@@ -56,6 +66,11 @@ internal class JdbcCursor(private val resultSet: ResultSet) : CommonCursor {
 
     override fun next(): Boolean = resultSet.next()
 
+    override fun isNull(columnIndex: Int): Boolean {
+        resultSet.getObject(columnIndex + 1)
+        return resultSet.wasNull()
+    }
+
     override fun close() {
         resultSet.close()
         resultSet.statement.close()
diff --git a/sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/ConcurrentStatement.kt b/sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/ConcurrentStatement.kt
index 87fb885..6221fbe 100644
--- a/sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/ConcurrentStatement.kt
+++ b/sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/ConcurrentStatement.kt
@@ -30,11 +30,15 @@ internal class ConcurrentStatement(
     private val accessLock: Lock,
 ) : SQLiteStatement {
 
-    override fun columnGetLong(columnIndex: Int): Long? = accessLock.withLock {
+    override fun isNull(columnIndex: Int): Boolean = accessLock.withLock {
+        delegateStatement.isNull(columnIndex)
+    }
+
+    override fun columnGetLong(columnIndex: Int): Long = accessLock.withLock {
         delegateStatement.columnGetLong(columnIndex)
     }
 
-    override fun columnGetDouble(columnIndex: Int): Double? = accessLock.withLock {
+    override fun columnGetDouble(columnIndex: Int): Double = accessLock.withLock {
         delegateStatement.columnGetDouble(columnIndex)
     }
 
diff --git a/sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/NativeCursor.kt b/sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/NativeCursor.kt
index f149f5e..efa7ca6 100644
--- a/sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/NativeCursor.kt
+++ b/sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/NativeCursor.kt
@@ -25,13 +25,21 @@ internal class NativeCursor(
     private val statement: SQLiteStatement
 ) : CommonCursor {
 
-    override fun getInt(columnIndex: Int): Int? = getLong(columnIndex)?.toInt()
+    override fun getInt(columnIndex: Int): Int = getLong(columnIndex).toInt()
 
-    override fun getLong(columnIndex: Int): Long? = statement.columnGetLong(columnIndex)
+    override fun getLong(columnIndex: Int): Long {
+        if (isNull(columnIndex))
+            throw SQLiteException("The value of column $columnIndex is NULL")
+        return statement.columnGetLong(columnIndex)
+    }
 
-    override fun getFloat(columnIndex: Int): Float? = getDouble(columnIndex)?.toFloat()
+    override fun getFloat(columnIndex: Int): Float = getDouble(columnIndex).toFloat()
 
-    override fun getDouble(columnIndex: Int): Double? = statement.columnGetDouble(columnIndex)
+    override fun getDouble(columnIndex: Int): Double {
+        if (isNull(columnIndex))
+            throw SQLiteException("The value of column $columnIndex is NULL")
+        return statement.columnGetDouble(columnIndex)
+    }
 
     override fun getString(columnIndex: Int): String? = statement.columnGetString(columnIndex)
 
@@ -41,12 +49,6 @@ internal class NativeCursor(
 
     override fun next(): Boolean = statement.step()
 
-    @Deprecated(
-        message = "Please use the new API: forEachRow",
-        replaceWith = ReplaceWith(expression = "forEachRow"),
-    )
-    override fun forEachRows(block: (Int) -> Unit) = forEachRow(block)
-
     override fun forEachRow(block: (Int) -> Unit) {
         var index = 0
         while (next())
@@ -55,6 +57,8 @@ internal class NativeCursor(
 
     override fun close() = statement.finalizeStatement()
 
+    override fun isNull(columnIndex: Int): Boolean = statement.isNull(columnIndex)
+
     private val columnNames: Map<String, Int> by lazy {
         val count = statement.columnCount()
         val map = HashMap<String, Int>(count)
diff --git a/sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/SQLiteException.kt b/sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/SQLiteResultCode.kt
similarity index 87%
rename from sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/SQLiteException.kt
rename to sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/SQLiteResultCode.kt
index 3a4b6d3..4f8dbb7 100644
--- a/sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/SQLiteException.kt
+++ b/sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/SQLiteResultCode.kt
@@ -19,15 +19,9 @@ package com.ctrip.sqllin.driver
 import com.ctrip.sqllin.driver.SQLiteResultCode.Companion.INVALID_CODE
 import com.ctrip.sqllin.driver.cinterop.SQLiteErrorType
 
-/**
- * The exceptions about SQLite, they include the native SQLite result codes and error message
- * @author yaqiao
- */
-
-public open class SQLiteException(message: String) : Exception(message)
-
 /**
  * The result codes in SQLite
+ * @author Yuang Qiao
  */
 public class SQLiteResultCode(message: String, resultCode: Int) : SQLiteException(
     "$message | error code ${
diff --git a/sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/SQLiteStatement.kt b/sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/SQLiteStatement.kt
index afb7e6c..c968129 100644
--- a/sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/SQLiteStatement.kt
+++ b/sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/SQLiteStatement.kt
@@ -23,9 +23,11 @@ package com.ctrip.sqllin.driver
 
 internal interface SQLiteStatement {
 
-    fun columnGetLong(columnIndex: Int): Long?
+    fun isNull(columnIndex: Int): Boolean
 
-    fun columnGetDouble(columnIndex: Int): Double?
+    fun columnGetLong(columnIndex: Int): Long
+
+    fun columnGetDouble(columnIndex: Int): Double
 
     fun columnGetString(columnIndex: Int): String?
 
diff --git a/sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/cinterop/NativeStatement.kt b/sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/cinterop/NativeStatement.kt
index f73307e..d8090c8 100644
--- a/sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/cinterop/NativeStatement.kt
+++ b/sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/cinterop/NativeStatement.kt
@@ -66,29 +66,26 @@ internal class NativeStatement(
 ) : SQLiteStatement {
 
     // Cursor methods
-    private fun isNull(index: Int): Boolean =
-        sqlite3_column_type(cStatementPointer, index) == SQLITE_NULL
+    override fun isNull(columnIndex: Int): Boolean =
+        sqlite3_column_type(cStatementPointer, columnIndex) == SQLITE_NULL
 
-    override fun columnGetLong(columnIndex: Int): Long? =
-        if (isNull(columnIndex)) null else sqlite3_column_int64(cStatementPointer, columnIndex)
+    override fun columnGetLong(columnIndex: Int): Long =
+        sqlite3_column_int64(cStatementPointer, columnIndex)
 
-    override fun columnGetDouble(columnIndex: Int): Double? =
-        if (isNull(columnIndex)) null else sqlite3_column_double(cStatementPointer, columnIndex)
+    override fun columnGetDouble(columnIndex: Int): Double =
+        sqlite3_column_double(cStatementPointer, columnIndex)
 
     override fun columnGetString(columnIndex: Int): String? =
-        if (isNull(columnIndex))
-            null
-        else
-            sqlite3_column_text(cStatementPointer, columnIndex)
-                ?.reinterpret<ByteVar>()
-                ?.let { bytesToString(it) }
+        sqlite3_column_text(cStatementPointer, columnIndex)
+            ?.reinterpret<ByteVar>()
+            ?.let { bytesToString(it) }
 
     override fun columnGetBlob(columnIndex: Int): ByteArray? {
         if (isNull(columnIndex))
             return null
         val blobSize = sqlite3_column_bytes(cStatementPointer, columnIndex)
         return if (blobSize == 0)
-            null
+            byteArrayOf()
         else
             sqlite3_column_blob(cStatementPointer, columnIndex)?.readBytes(blobSize)
     }
diff --git a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/ClauseNumber.kt b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/ClauseNumber.kt
index e569583..03b8b31 100644
--- a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/ClauseNumber.kt
+++ b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/ClauseNumber.kt
@@ -42,13 +42,13 @@ public class ClauseNumber(
     internal infix fun lte(clauseNumber: ClauseNumber): SelectCondition = appendClauseNumber("<=", clauseNumber)
 
     // Equals, ==
-    internal infix fun eq(number: Number?): SelectCondition = appendNullableNumber("=", "IS", number)
+    internal infix fun eq(number: Number?): SelectCondition = appendNullableNumber("=", " IS", number)
 
     // Equals, append to ClauseNumber
     internal infix fun eq(clauseNumber: ClauseNumber): SelectCondition = appendClauseNumber("=", clauseNumber)
 
     // Not equals to, !=
-    internal infix fun neq(number: Number?): SelectCondition = appendNullableNumber("!=", "IS NOT", number)
+    internal infix fun neq(number: Number?): SelectCondition = appendNullableNumber("!=", " IS NOT", number)
 
     // Not equals to, append to ClauseNumber
     internal infix fun neq(clauseNumber: ClauseNumber): SelectCondition = appendClauseNumber("!=", clauseNumber)
diff --git a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/ClauseString.kt b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/ClauseString.kt
index b897a20..980ccfd 100644
--- a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/ClauseString.kt
+++ b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/ClauseString.kt
@@ -30,20 +30,20 @@ public class ClauseString(
 ) : ClauseElement(valueName, table, isFunction) {
 
     // Equals, ==
-    internal infix fun eq(str: String?): SelectCondition = appendString("=", "IS", str)
+    internal infix fun eq(str: String?): SelectCondition = appendString("=", " IS", str)
 
     // Equals, append another ClauseString
     internal infix fun eq(clauseString: ClauseString): SelectCondition = appendClauseString("=", clauseString)
 
     // Not equals to, !=
-    internal infix fun neq(str: String?): SelectCondition = appendString("!=", "IS NOT", str)
+    internal infix fun neq(str: String?): SelectCondition = appendString("!=", " IS NOT", str)
 
     // Not equal to, append another ClauseString
     internal infix fun neq(clauseString: ClauseString): SelectCondition = appendClauseString("!=", clauseString)
 
-    internal infix fun like(regex: String): SelectCondition = appendRegex("LIKE", regex)
+    internal infix fun like(regex: String): SelectCondition = appendRegex(" LIKE ", regex)
 
-    internal infix fun glob(regex: String): SelectCondition = appendRegex("GLOB", regex)
+    internal infix fun glob(regex: String): SelectCondition = appendRegex(" GLOB ", regex)
 
     private fun appendRegex(symbol: String, regex: String): SelectCondition {
         val sql = buildString {
@@ -66,12 +66,13 @@ public class ClauseString(
             }
             append(valueName)
             val isNull = str == null
-            val symbol = if (isNull) nullSymbol else notNullSymbol
-            append(symbol)
-            if (str == null)
+            if (isNull) {
+                append(nullSymbol)
                 append(" NULL")
-            else
+            } else {
+                append(notNullSymbol)
                 append('?')
+            }
         }
         return SelectCondition(sql, if (str == null) null else mutableListOf(str))
     }
diff --git a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/SetClause.kt b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/SetClause.kt
index c9508b5..b26e2da 100644
--- a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/SetClause.kt
+++ b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/SetClause.kt
@@ -31,9 +31,9 @@ public class SetClause<T> : Clause<T> {
     public fun appendString(propertyName: String, propertyValue: String?) {
         clauseBuilder.append(propertyName)
         if (propertyValue == null)
-            clauseBuilder.append("NULL,")
+            clauseBuilder.append("=NULL,")
         else {
-            clauseBuilder.append("?,")
+            clauseBuilder.append("=?,")
             val params = parameters ?: ArrayList<String>().also {
                 parameters = it
             }
diff --git a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/compiler/QueryDecoder.kt b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/compiler/QueryDecoder.kt
index 15ff878..0b8af6f 100644
--- a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/compiler/QueryDecoder.kt
+++ b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/compiler/QueryDecoder.kt
@@ -37,7 +37,7 @@ internal class QueryDecoder(
 
     private var elementIndex = 0
     private var elementName = ""
-    private var elementNotNullMark = true
+    private var elementNullable = false
 
     override val serializersModule: SerializersModule = EmptySerializersModule()
 
@@ -46,7 +46,7 @@ internal class QueryDecoder(
             CompositeDecoder.DECODE_DONE
         else {
             elementName = descriptor.getElementName(elementIndex)
-            elementNotNullMark = !descriptor.getElementDescriptor(elementIndex).isNullable
+            elementNullable = descriptor.getElementDescriptor(elementIndex).isNullable
             val resultIndex = elementIndex++
             if (cursorColumnIndex >= 0)
                 resultIndex
@@ -59,25 +59,20 @@ internal class QueryDecoder(
     private inline val cursorColumnIndex
         get() = cursor.getColumnIndex(elementName)
 
-    private inline val sqlNullException
-        get() = IllegalStateException("The value in database is null, please declare your property be nullable")
-
     private inline fun <T> deserialize(block: (Int) -> T): T = cursorColumnIndex.let {
         if (it >= 0) block(it) else throw SerializationException("The Cursor doesn't have this column")
     }
 
-    override fun decodeBoolean(): Boolean = deserialize { (cursor.getInt(it) ?: throw sqlNullException) > 0 }
-    override fun decodeByte(): Byte = deserialize { cursor.getInt(it)?.toByte() ?: throw sqlNullException }
-    override fun decodeShort(): Short = deserialize { cursor.getInt(it)?.toShort() ?: throw sqlNullException }
-    override fun decodeInt(): Int = deserialize { cursor.getInt(it) ?: throw sqlNullException }
-    override fun decodeLong(): Long = deserialize { cursor.getLong(it) ?: throw sqlNullException }
-    override fun decodeChar(): Char = deserialize { cursor.getString(it)?.first() ?: throw sqlNullException }
-    override fun decodeString(): String = deserialize { cursor.getString(it) ?: throw sqlNullException }
-    override fun decodeFloat(): Float = deserialize { cursor.getFloat(it) ?: throw sqlNullException }
-    override fun decodeDouble(): Double = deserialize { cursor.getDouble(it) ?: throw sqlNullException }
-    override fun decodeEnum(enumDescriptor: SerialDescriptor): Int = deserialize { cursor.getInt(it) ?: throw sqlNullException }
-
-    override fun decodeNotNullMark(): Boolean = elementNotNullMark
+    override fun decodeBoolean(): Boolean = deserialize { cursor.getInt(it) > 0 }
+    override fun decodeByte(): Byte = deserialize { cursor.getInt(it).toByte() }
+    override fun decodeShort(): Short = deserialize { cursor.getInt(it).toShort() }
+    override fun decodeInt(): Int = deserialize { cursor.getInt(it) }
+    override fun decodeLong(): Long = deserialize { cursor.getLong(it) }
+    override fun decodeChar(): Char = deserialize { cursor.getString(it)?.first() ?: '\u0000' }
+    override fun decodeString(): String = deserialize { cursor.getString(it) ?: "" }
+    override fun decodeFloat(): Float = deserialize { cursor.getFloat(it) }
+    override fun decodeDouble(): Double = deserialize { cursor.getDouble(it) }
+    override fun decodeEnum(enumDescriptor: SerialDescriptor): Int = deserialize { cursor.getInt(it) }
 
-    // override fun decodeCollectionSize(descriptor: SerialDescriptor): Int {}
+    override fun decodeNotNullMark(): Boolean = !cursor.isNull(cursorColumnIndex) || !elementNullable
 }
\ No newline at end of file
diff --git a/sqllin-dsl/src/commonTest/kotlin/com/ctrip/sqllin/dsl/CommonBasicTest.kt b/sqllin-dsl/src/commonTest/kotlin/com/ctrip/sqllin/dsl/CommonBasicTest.kt
index a985f64..936e3ea 100644
--- a/sqllin-dsl/src/commonTest/kotlin/com/ctrip/sqllin/dsl/CommonBasicTest.kt
+++ b/sqllin-dsl/src/commonTest/kotlin/com/ctrip/sqllin/dsl/CommonBasicTest.kt
@@ -439,6 +439,7 @@ class CommonBasicTest(private val path: DatabasePath) {
         )
         Database(config, true).databaseAutoClose { database ->
             lateinit var selectStatement: SelectStatement<NullTester>
+            // INSERT & SELECT
             database {
                 NullTesterTable { table ->
                     table INSERT listOf(
@@ -448,7 +449,47 @@ class CommonBasicTest(private val path: DatabasePath) {
                     selectStatement = table SELECT X
                 }
             }
-            assertEquals(2, selectStatement.getResults().size)
+
+            selectStatement.getResults().forEachIndexed { i, tester ->
+                when (i) {
+                    0 -> {
+                        assertEquals(null, tester.paramInt)
+                        assertEquals(null, tester.paramString)
+                        assertEquals(null, tester.paramDouble)
+                    }
+                    1 -> {
+                        assertEquals(8, tester.paramInt)
+                        assertEquals("888", tester.paramString)
+                        assertEquals(8.8, tester.paramDouble)
+                    }
+                }
+            }
+
+            // UPDATE & SELECT
+            database {
+                NullTesterTable { table ->
+                    table UPDATE SET { paramString = null } WHERE (paramDouble EQ 8.8)
+                    selectStatement = table SELECT WHERE (paramInt NEQ null)
+                }
+            }
+            val result1 = selectStatement.getResults().first()
+            assertEquals(1, selectStatement.getResults().size)
+            assertEquals(8, result1.paramInt)
+            assertEquals(null, result1.paramString)
+            assertEquals(8.8, result1.paramDouble)
+
+            // DELETE & SELECT
+            database {
+                NullTesterTable { table ->
+                    table DELETE WHERE (paramInt EQ null OR (paramDouble EQ null))
+                    selectStatement = table SELECT X
+                }
+            }
+            val result2 = selectStatement.getResults().first()
+            assertEquals(1, selectStatement.getResults().size)
+            assertEquals(8, result1.paramInt)
+            assertEquals(null, result1.paramString)
+            assertEquals(8.8, result1.paramDouble)
         }
     }
 

From 2b55a006e134a5095bc456140f882b394e5b080d Mon Sep 17 00:00:00 2001
From: qiaoyuang <qiaoyuang2012@gmail.com>
Date: Wed, 24 Apr 2024 13:36:56 +0800
Subject: [PATCH 4/5] Fix the unit tests of sqllin-dsl

---
 .../com/ctrip/sqllin/dsl/AndroidTest.kt       |  2 +-
 .../com/ctrip/sqllin/dsl/CommonBasicTest.kt   | 66 +++++++++----------
 .../kotlin/com/ctrip/sqllin/dsl/JvmTest.kt    |  2 +-
 .../kotlin/com/ctrip/sqllin/dsl/NativeTest.kt |  2 +-
 4 files changed, 36 insertions(+), 36 deletions(-)

diff --git a/sqllin-dsl/src/androidInstrumentedTest/kotlin/com/ctrip/sqllin/dsl/AndroidTest.kt b/sqllin-dsl/src/androidInstrumentedTest/kotlin/com/ctrip/sqllin/dsl/AndroidTest.kt
index 3a75f15..f1c99cf 100644
--- a/sqllin-dsl/src/androidInstrumentedTest/kotlin/com/ctrip/sqllin/dsl/AndroidTest.kt
+++ b/sqllin-dsl/src/androidInstrumentedTest/kotlin/com/ctrip/sqllin/dsl/AndroidTest.kt
@@ -75,7 +75,7 @@ class AndroidTest {
     fun testPrimitiveTypeForKSP() = commonTest.testPrimitiveTypeForKSP()
 
     @Test
-    fun testNullInsertAndSelect() = commonTest.testNullInsertAndSelect()
+    fun testNullValue() = commonTest.testNullValue()
 
     @Before
     fun setUp() {
diff --git a/sqllin-dsl/src/commonTest/kotlin/com/ctrip/sqllin/dsl/CommonBasicTest.kt b/sqllin-dsl/src/commonTest/kotlin/com/ctrip/sqllin/dsl/CommonBasicTest.kt
index 936e3ea..9b42576 100644
--- a/sqllin-dsl/src/commonTest/kotlin/com/ctrip/sqllin/dsl/CommonBasicTest.kt
+++ b/sqllin-dsl/src/commonTest/kotlin/com/ctrip/sqllin/dsl/CommonBasicTest.kt
@@ -369,43 +369,43 @@ class CommonBasicTest(private val path: DatabasePath) {
         assertEquals(outerJoinStatementWithOn?.getResults()?.size, books.size)
     }
 
-    fun testConcurrency() = runBlocking(Dispatchers.Default) {
-        val book1 = Book(name = "The Da Vinci Code", author = "Dan Brown", pages = 454, price = 16.96)
-        val book2 = Book(name = "The Lost Symbol", author = "Dan Brown", pages = 510, price = 19.95)
-        val database = Database(getDefaultDBConfig(), true)
-        launch {
-            var statement: SelectStatement<Book>? = null
-            database suspendedScope {
-                statement = BookTable { table ->
-                    table INSERT listOf(book1, book2)
-                    table SELECT X
-                }
-            }
-
+    fun testConcurrency() = Database(getDefaultDBConfig(), true).databaseAutoClose { database ->
+        runBlocking(Dispatchers.Default) {
+            val book1 = Book(name = "The Da Vinci Code", author = "Dan Brown", pages = 454, price = 16.96)
+            val book2 = Book(name = "The Lost Symbol", author = "Dan Brown", pages = 510, price = 19.95)
             launch {
-                val book1NewPrice = 18.96
-                val book2NewPrice = 21.95
-                val newBook1 = Book(name = "The Da Vinci Code", author = "Dan Brown", pages = 454, price = book1NewPrice)
-                val newBook2 = Book(name = "The Lost Symbol", author = "Dan Brown", pages = 510, price = book2NewPrice)
-                var newResult: SelectStatement<Book>? = null
+                var statement: SelectStatement<Book>? = null
                 database suspendedScope {
-                    newResult = transaction {
-                        BookTable { table ->
-                            table UPDATE SET { price = book1NewPrice } WHERE (name EQ book1.name AND (price EQ book1.price))
-                            table UPDATE SET { price = book2NewPrice } WHERE (name EQ book2.name AND (price EQ book2.price))
-                            table SELECT X
+                    statement = BookTable { table ->
+                        table INSERT listOf(book1, book2)
+                        table SELECT X
+                    }
+                }
+
+                launch {
+                    val book1NewPrice = 18.96
+                    val book2NewPrice = 21.95
+                    val newBook1 = Book(name = "The Da Vinci Code", author = "Dan Brown", pages = 454, price = book1NewPrice)
+                    val newBook2 = Book(name = "The Lost Symbol", author = "Dan Brown", pages = 510, price = book2NewPrice)
+                    var newResult: SelectStatement<Book>? = null
+                    database suspendedScope {
+                        newResult = transaction {
+                            BookTable { table ->
+                                table UPDATE SET { price = book1NewPrice } WHERE (name EQ book1.name AND (price EQ book1.price))
+                                table UPDATE SET { price = book2NewPrice } WHERE (name EQ book2.name AND (price EQ book2.price))
+                                table SELECT X
+                            }
                         }
                     }
+
+                    assertEquals(true, newResult!!.getResults().any { it == newBook1 })
+                    assertEquals(true, newResult!!.getResults().any { it == newBook2 })
                 }
 
-                assertEquals(true, newResult!!.getResults().any { it == newBook1 })
-                assertEquals(true, newResult!!.getResults().any { it == newBook2 })
+                assertEquals(true, statement!!.getResults().any { it == book1 })
+                assertEquals(true, statement!!.getResults().any { it == book2 })
             }
-
-            assertEquals(true, statement!!.getResults().any { it == book1 })
-            assertEquals(true, statement!!.getResults().any { it == book2 })
         }
-        Unit
     }
 
     fun testPrimitiveTypeForKSP() {
@@ -428,7 +428,7 @@ class CommonBasicTest(private val path: DatabasePath) {
         }
     }
 
-    fun testNullInsertAndSelect() {
+    fun testNullValue() {
         val config = DatabaseConfiguration(
             name = DATABASE_NAME,
             path = path,
@@ -487,9 +487,9 @@ class CommonBasicTest(private val path: DatabasePath) {
             }
             val result2 = selectStatement.getResults().first()
             assertEquals(1, selectStatement.getResults().size)
-            assertEquals(8, result1.paramInt)
-            assertEquals(null, result1.paramString)
-            assertEquals(8.8, result1.paramDouble)
+            assertEquals(8, result2.paramInt)
+            assertEquals(null, result2.paramString)
+            assertEquals(8.8, result2.paramDouble)
         }
     }
 
diff --git a/sqllin-dsl/src/jvmTest/kotlin/com/ctrip/sqllin/dsl/JvmTest.kt b/sqllin-dsl/src/jvmTest/kotlin/com/ctrip/sqllin/dsl/JvmTest.kt
index 9f75932..b2096d0 100644
--- a/sqllin-dsl/src/jvmTest/kotlin/com/ctrip/sqllin/dsl/JvmTest.kt
+++ b/sqllin-dsl/src/jvmTest/kotlin/com/ctrip/sqllin/dsl/JvmTest.kt
@@ -66,7 +66,7 @@ class JvmTest {
     fun testPrimitiveTypeForKSP() = commonTest.testPrimitiveTypeForKSP()
 
     @Test
-    fun testNullInsertAndSelect() = commonTest.testNullInsertAndSelect()
+    fun testNullValue() = commonTest.testNullValue()
 
     @BeforeTest
     fun setUp() {
diff --git a/sqllin-dsl/src/nativeTest/kotlin/com/ctrip/sqllin/dsl/NativeTest.kt b/sqllin-dsl/src/nativeTest/kotlin/com/ctrip/sqllin/dsl/NativeTest.kt
index 4b4ab0c..a2943dd 100644
--- a/sqllin-dsl/src/nativeTest/kotlin/com/ctrip/sqllin/dsl/NativeTest.kt
+++ b/sqllin-dsl/src/nativeTest/kotlin/com/ctrip/sqllin/dsl/NativeTest.kt
@@ -69,7 +69,7 @@ class NativeTest {
     fun testPrimitiveTypeForKSP() = commonTest.testPrimitiveTypeForKSP()
 
     @Test
-    fun testNullInsertAndSelect() = commonTest.testNullInsertAndSelect()
+    fun testNullValue() = commonTest.testNullValue()
 
     @BeforeTest
     fun setUp() {

From c988a557a8bf3f860c2bf054b8aacc5bf0f2aac9 Mon Sep 17 00:00:00 2001
From: qiaoyuang <qiaoyuang2012@gmail.com>
Date: Wed, 24 Apr 2024 13:52:06 +0800
Subject: [PATCH 5/5] Update version to 1.3.1

---
 gradle.properties                  | 2 +-
 sqllin-driver/README.md            | 2 +-
 sqllin-driver/README_CN.md         | 2 +-
 sqllin-dsl/doc/getting-start-cn.md | 2 +-
 sqllin-dsl/doc/getting-start.md    | 2 +-
 5 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/gradle.properties b/gradle.properties
index 2b48f4d..c0c7bce 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,4 +1,4 @@
-VERSION=1.3.0
+VERSION=1.3.1
 GROUP=com.ctrip.kotlin
 
 kotlinVersion=1.9.23
diff --git a/sqllin-driver/README.md b/sqllin-driver/README.md
index 93920b2..0e49c57 100644
--- a/sqllin-driver/README.md
+++ b/sqllin-driver/README.md
@@ -102,7 +102,7 @@ databaseConnection.executeUpdateDelete(SQL.UPDATE, arrayOf(20, "Tom"))
 
 // SELECT
 val cursor: CommonCursor = databaseConnection.query(SQL.QUERY, arrayOf(20, "Tom"))
-cursor.forEachRows { index -> // Index of rows
+cursor.forEachRow { index -> // Index of rows
     val age: Int = cursor.getInt("age")
     val name: String = cursor.getString("name")
 }
diff --git a/sqllin-driver/README_CN.md b/sqllin-driver/README_CN.md
index 2845fb7..73776df 100644
--- a/sqllin-driver/README_CN.md
+++ b/sqllin-driver/README_CN.md
@@ -91,7 +91,7 @@ databaseConnection.executeUpdateDelete(SQL.UPDATE, arrayOf(20, "Tom"))
 
 // SELECT
 val cursor: CommonCursor = databaseConnection.query(SQL.QUERY, arrayOf(20, "Tom"))
-cursor.forEachRows { index -> // Index of rows
+cursor.forEachRow { index -> // Index of rows
     val age: Int = cursor.getInt("age")
     val name: String = cursor.getString("name")
 }
diff --git a/sqllin-dsl/doc/getting-start-cn.md b/sqllin-dsl/doc/getting-start-cn.md
index 5523dfe..7331274 100644
--- a/sqllin-dsl/doc/getting-start-cn.md
+++ b/sqllin-dsl/doc/getting-start-cn.md
@@ -14,7 +14,7 @@ plugins {
     id("com.google.devtools.ksp")
 }
 
-val sqllinVersion = "1.3.0"
+val sqllinVersion = "1.3.1"
 
 kotlin {
     // ......
diff --git a/sqllin-dsl/doc/getting-start.md b/sqllin-dsl/doc/getting-start.md
index 69c0c55..21c14a8 100644
--- a/sqllin-dsl/doc/getting-start.md
+++ b/sqllin-dsl/doc/getting-start.md
@@ -16,7 +16,7 @@ plugins {
     id("com.google.devtools.ksp")
 }
 
-val sqllinVersion = "1.3.0"
+val sqllinVersion = "1.3.1"
 
 kotlin {
     // ......