Skip to content

Commit f97bcf9

Browse files
authored
Merge pull request #110 from qiaoyuang/main
Support ByteArray in DSL syntax
2 parents 6e33548 + 17c2737 commit f97bcf9

File tree

37 files changed

+1265
-335
lines changed

37 files changed

+1265
-335
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
* New experimental API: `DatabaseScope#CREATE`
1616
* New experimental API: `DatabaseScope#DROP`
1717
* New experimental API: `DatabaseSceop#ALERT`
18+
* Support using ByteArray in DSL, that represents BLOB in SQLite
1819

1920
### sqllin-driver
2021

ROADMAP.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44

55
* Support the key word REFERENCE
66
* Support JOIN sub-query
7-
* Fix the bug of storing ByteArray in DSL
7+
* Support Enum type
8+
* Support typealias for primitive types
89

910
## Medium Priority
1011

sqllin-driver/src/androidMain/kotlin/com/ctrip/sqllin/driver/AndroidDatabaseConnection.kt

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616

1717
package com.ctrip.sqllin.driver
1818

19+
import android.database.sqlite.SQLiteCursor
1920
import android.database.sqlite.SQLiteDatabase
21+
import android.database.sqlite.SQLiteQuery
2022

2123
/**
2224
* Android implementation of [DatabaseConnection] using Android's SQLiteDatabase.
@@ -35,7 +37,52 @@ internal class AndroidDatabaseConnection(private val database: SQLiteDatabase) :
3537

3638
override fun executeUpdateDelete(sql: String, bindParams: Array<out Any?>?) = execSQL(sql, bindParams)
3739

38-
override fun query(sql: String, bindParams: Array<out String?>?): CommonCursor = AndroidCursor(database.rawQuery(sql, bindParams))
40+
override fun query(sql: String, bindParams: Array<out Any?>?): CommonCursor {
41+
val cursor = if (bindParams == null) {
42+
database.rawQuery(sql, null)
43+
} else {
44+
// Use rawQueryWithFactory to bind parameters with proper types
45+
// This allows us to bind parameters with their actual types (Int, Long, Double, etc.)
46+
// instead of converting everything to String like rawQuery does
47+
val cursorFactory = SQLiteDatabase.CursorFactory { _, masterQuery, editTable, query ->
48+
bindTypedParameters(query, bindParams)
49+
SQLiteCursor(masterQuery, editTable, query)
50+
}
51+
// Pass emptyArray() for selectionArgs since we bind parameters via the factory
52+
// Use empty string for editTable since it's only needed for updateable cursors
53+
database.rawQueryWithFactory(cursorFactory, sql, null, "")
54+
}
55+
return AndroidCursor(cursor)
56+
}
57+
58+
/**
59+
* Binds parameters to SQLiteQuery with proper type handling.
60+
*
61+
* Unlike rawQuery which only accepts String[], this method binds parameters
62+
* with their actual types (Long, Double, ByteArray, etc.) to ensure correct
63+
* SQLite type affinity and comparisons.
64+
*/
65+
private fun bindTypedParameters(query: SQLiteQuery, bindParams: Array<out Any?>) {
66+
bindParams.forEachIndexed { index, param ->
67+
val position = index + 1 // SQLite bind positions are 1-based
68+
when (param) {
69+
null -> query.bindNull(position)
70+
is ByteArray -> query.bindBlob(position, param)
71+
is Double -> query.bindDouble(position, param)
72+
is Float -> query.bindDouble(position, param.toDouble())
73+
is Long -> query.bindLong(position, param)
74+
is Int -> query.bindLong(position, param.toLong())
75+
is Short -> query.bindLong(position, param.toLong())
76+
is Byte -> query.bindLong(position, param.toLong())
77+
is Boolean -> query.bindLong(position, if (param) 1L else 0L)
78+
is ULong -> query.bindLong(position, param.toLong())
79+
is UInt -> query.bindLong(position, param.toLong())
80+
is UShort -> query.bindLong(position, param.toLong())
81+
is UByte -> query.bindLong(position, param.toLong())
82+
else -> query.bindString(position, param.toString())
83+
}
84+
}
85+
}
3986

4087
override fun beginTransaction() = database.beginTransaction()
4188
override fun endTransaction() = database.endTransaction()

sqllin-driver/src/commonMain/kotlin/com/ctrip/sqllin/driver/DatabaseConnection.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,10 +54,10 @@ public interface DatabaseConnection {
5454
* Executes a SELECT query and returns a cursor.
5555
*
5656
* @param sql The SELECT statement
57-
* @param bindParams Optional string parameters to bind to the query
57+
* @param bindParams Optional parameters to bind to the query
5858
* @return A cursor for iterating over query results
5959
*/
60-
public fun query(sql: String, bindParams: Array<out String?>? = null): CommonCursor
60+
public fun query(sql: String, bindParams: Array<out Any?>? = null): CommonCursor
6161

6262
/**
6363
* Begins a database transaction.

sqllin-driver/src/jvmMain/kotlin/com/ctrip/sqllin/driver/ConcurrentDatabaseConnection.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ internal class ConcurrentDatabaseConnection(private val delegateConnection: Data
4242
delegateConnection.executeUpdateDelete(sql, bindParams)
4343
}
4444

45-
override fun query(sql: String, bindParams: Array<out String?>?): CommonCursor = accessLock.withLock {
45+
override fun query(sql: String, bindParams: Array<out Any?>?): CommonCursor = accessLock.withLock {
4646
delegateConnection.query(sql, bindParams)
4747
}
4848

sqllin-driver/src/jvmMain/kotlin/com/ctrip/sqllin/driver/JdbcDatabaseConnection.kt

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -48,15 +48,11 @@ internal class JdbcDatabaseConnection(private val connection: Connection) : Abst
4848
it.executeUpdate()
4949
}
5050

51-
override fun query(sql: String, bindParams: Array<out String?>?): CommonCursor {
52-
val statement = connection.prepareStatement(sql)
53-
bindParams?.forEachIndexed { index, str ->
54-
str?.let {
55-
statement.setString(index + 1, it)
56-
}
57-
}
58-
return statement.executeQuery()?.let { JdbcCursor(it) } ?: throw IllegalStateException("The query result is null.")
59-
}
51+
override fun query(sql: String, bindParams: Array<out Any?>?): CommonCursor =
52+
bindParamsToSQL(sql, bindParams)
53+
.executeQuery()
54+
?.let { JdbcCursor(it) }
55+
?: throw IllegalStateException("The query result is null.")
6056

6157
private val isTransactionSuccess = AtomicBoolean(false)
6258

sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/ConcurrentDatabaseConnection.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ internal class ConcurrentDatabaseConnection(
4444
delegateConnection.executeUpdateDelete(sql, bindParams)
4545
}
4646

47-
override fun query(sql: String, bindParams: Array<out String?>?): CommonCursor = accessLock.withLock {
47+
override fun query(sql: String, bindParams: Array<out Any?>?): CommonCursor = accessLock.withLock {
4848
delegateConnection.query(sql, bindParams)
4949
}
5050

sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/RealDatabaseConnection.kt

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -69,15 +69,8 @@ internal class RealDatabaseConnection(
6969
}
7070
}
7171

72-
override fun query(sql: String, bindParams: Array<out String?>?): CommonCursor {
73-
val statement = createStatement(sql)
74-
bindParams?.forEachIndexed { index, str ->
75-
str?.let {
76-
statement.bindString(index + 1, it)
77-
}
78-
}
79-
return statement.query()
80-
}
72+
override fun query(sql: String, bindParams: Array<out Any?>?): CommonCursor =
73+
bindParamsToSQL(sql, bindParams).query()
8174

8275
override fun beginTransaction() = transactionLock.withLock {
8376
database.rawExecSql("BEGIN;")

sqllin-dsl-test/src/androidInstrumentedTest/kotlin/com/ctrip/sqllin/dsl/test/AndroidTest.kt

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,42 @@ class AndroidTest {
6161
@Test
6262
fun testNullValue() = commonTest.testNullValue()
6363

64+
@Test
65+
fun testCreateTableWithLongPrimaryKey() = commonTest.testCreateTableWithLongPrimaryKey()
66+
67+
@Test
68+
fun testCreateTableWithStringPrimaryKey() = commonTest.testCreateTableWithStringPrimaryKey()
69+
70+
@Test
71+
fun testCreateTableWithAutoincrement() = commonTest.testCreateTableWithAutoincrement()
72+
73+
@Test
74+
fun testCreateTableWithCompositePrimaryKey() = commonTest.testCreateTableWithCompositePrimaryKey()
75+
76+
@Test
77+
fun testInsertWithId() = commonTest.testInsertWithId()
78+
79+
@Test
80+
fun testCreateInDatabaseScope() = commonTest.testCreateInDatabaseScope()
81+
82+
@Test
83+
fun testUpdateAndDeleteWithPrimaryKey() = commonTest.testUpdateAndDeleteWithPrimaryKey()
84+
85+
@Test
86+
fun testByteArrayInsert() = commonTest.testByteArrayInsert()
87+
88+
@Test
89+
fun testByteArraySelect() = commonTest.testByteArraySelect()
90+
91+
@Test
92+
fun testByteArrayUpdate() = commonTest.testByteArrayUpdate()
93+
94+
@Test
95+
fun testByteArrayDelete() = commonTest.testByteArrayDelete()
96+
97+
@Test
98+
fun testByteArrayMultipleOperations() = commonTest.testByteArrayMultipleOperations()
99+
64100
@Before
65101
fun setUp() {
66102
val context = InstrumentationRegistry.getInstrumentation().targetContext

sqllin-dsl-test/src/commonMain/kotlin/com/ctrip/sqllin/dsl/test/Entities.kt

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616

1717
package com.ctrip.sqllin.dsl.test
1818

19+
import com.ctrip.sqllin.dsl.annotation.CompositePrimaryKey
1920
import com.ctrip.sqllin.dsl.annotation.DBRow
21+
import com.ctrip.sqllin.dsl.annotation.PrimaryKey
2022
import kotlinx.serialization.Serializable
2123

2224
/**
@@ -63,4 +65,68 @@ data class NullTester(
6365
val paramInt: Int?,
6466
val paramString: String?,
6567
val paramDouble: Double?,
66-
)
68+
)
69+
70+
@DBRow("person_with_id")
71+
@Serializable
72+
data class PersonWithId(
73+
@PrimaryKey val id: Long?,
74+
val name: String,
75+
val age: Int,
76+
)
77+
78+
@DBRow("product")
79+
@Serializable
80+
data class Product(
81+
@PrimaryKey val sku: String?,
82+
val name: String,
83+
val price: Double,
84+
)
85+
86+
@DBRow("student_with_autoincrement")
87+
@Serializable
88+
data class StudentWithAutoincrement(
89+
@PrimaryKey(isAutoincrement = true) val id: Long?,
90+
val studentName: String,
91+
val grade: Int,
92+
)
93+
94+
@DBRow("enrollment")
95+
@Serializable
96+
data class Enrollment(
97+
@CompositePrimaryKey val studentId: Long,
98+
@CompositePrimaryKey val courseId: Long,
99+
val semester: String,
100+
)
101+
102+
@DBRow("file_data")
103+
@Serializable
104+
data class FileData(
105+
@PrimaryKey(isAutoincrement = true) val id: Long?,
106+
val fileName: String,
107+
val content: ByteArray,
108+
val metadata: String,
109+
) {
110+
// ByteArray doesn't implement equals/hashCode properly for data class
111+
override fun equals(other: Any?): Boolean {
112+
if (this === other) return true
113+
if (other == null || this::class != other::class) return false
114+
115+
other as FileData
116+
117+
if (id != other.id) return false
118+
if (fileName != other.fileName) return false
119+
if (!content.contentEquals(other.content)) return false
120+
if (metadata != other.metadata) return false
121+
122+
return true
123+
}
124+
125+
override fun hashCode(): Int {
126+
var result = id?.hashCode() ?: 0
127+
result = 31 * result + fileName.hashCode()
128+
result = 31 * result + content.contentHashCode()
129+
result = 31 * result + metadata.hashCode()
130+
return result
131+
}
132+
}

0 commit comments

Comments
 (0)