Skip to content

Commit

Permalink
Add SupportSQLite
Browse files Browse the repository at this point in the history
* Add Android Support SQLite to library

* Tests Support SQLite implementation
  • Loading branch information
ryanrampage1 authored May 7, 2019
1 parent ac85ae0 commit 95e5cf6
Show file tree
Hide file tree
Showing 5 changed files with 310 additions and 0 deletions.
1 change: 1 addition & 0 deletions batchlight/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ dokka {
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'androidx.sqlite:sqlite:2.0.1'

testImplementation 'junit:junit:4.12'
testImplementation "io.mockk:mockk:1.9"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package co.yello.db.batchlight.androidsupportsqlite

import androidx.sqlite.db.SupportSQLiteStatement
import co.yello.db.batchlight.Binder

/**
* The [Binder] implementation for Support SQLite on Android.
*
* @property sqLiteStatement A [SupportSQLiteStatement] to interact with.
*/
class AndroidSupportSQLiteBinder(
private val sqLiteStatement: SupportSQLiteStatement
) : Binder {

override fun execute() {
sqLiteStatement.execute()
}

override fun clear() {
sqLiteStatement.clearBindings()
}

override fun bindLong(position: Int, long: Long) {
sqLiteStatement.bindLong(position, long)
}

override fun bindString(position: Int, string: String) {
sqLiteStatement.bindString(position, string)
}

override fun bindDouble(position: Int, double: Double) {
sqLiteStatement.bindDouble(position, double)
}

override fun bindBlob(position: Int, blob: ByteArray) {
sqLiteStatement.bindBlob(position, blob)
}

override fun bindNull(position: Int) {
sqLiteStatement.bindNull(position)
}

override fun equals(other: Any?): Boolean {
return sqLiteStatement == (other as? AndroidSupportSQLiteBinder)?.sqLiteStatement
}

override fun hashCode(): Int {
return sqLiteStatement.hashCode()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package co.yello.db.batchlight.androidsupportsqlite

import androidx.sqlite.db.SupportSQLiteDatabase
import co.yello.db.batchlight.Binder
import co.yello.db.batchlight.BinderConfig
import co.yello.db.batchlight.sqlAndroidPreparedStatementStartIndex
import co.yello.db.batchlight.sqlMaxBinds

/**
* The [BinderConfig] for Android Support SQLite.
*
* @property db the Support SQLite database that is being written to.
* @property batchStatement the statement that will be executed. For SQLite it will be either REPLACE INTO TABLE_NAME or
* INSERT INTO TABLE_NAME
*
* @constructor
*
* @param fieldsPerItem the number of fields per row to be inserted.
* @param maxFields the maximum number of binds that can happen per statement.
*/
class SupportSQLiteBinderConfig(
private val db: SupportSQLiteDatabase,
private val batchStatement: String,
override val fieldsPerItem: Int,
override val maxFields: Int
) : BinderConfig {

/**
* The maximum number of items that can be inserted per statement.
*/
override val maxInsertSize = if (fieldsPerItem > 0) maxFields / fieldsPerItem else 1

override val maxInsertBinder: Binder by lazy {
buildBinder(maxInsertSize)
}

/**
* For a SQLite insert statement each item has the format (?, ?, ?)
*/
private val objectStatement = (1..fieldsPerItem).joinToString(
prefix = "(",
transform = { "?" },
separator = ",",
postfix= ")"
)

override val startIndex: Int = sqlAndroidPreparedStatementStartIndex

override fun buildBinder(insertCount: Int): Binder {
val allObjects = (1..insertCount).joinToString(",") { objectStatement }
val compiledStatement = db.compileStatement("$batchStatement $allObjects")
return AndroidSupportSQLiteBinder(compiledStatement)
}

companion object {

/**
* Creates a statement generator for batch replace statements.
*
* @param db the database to perform the replaces on.
* @param tableName the name of the table to preform the replaces on.
* @param columnCount the number of columns that will be replaced per row.
* @param maxBinds the total number of values that can be bound to the statement.
*/
fun getReplaceConfig(
db: SupportSQLiteDatabase,
tableName: String,
columnCount: Int,
maxBinds: Int = sqlMaxBinds
) = SupportSQLiteBinderConfig(
db,
"REPLACE INTO $tableName VALUES",
columnCount,
maxBinds
)

/**
* Creates a statement generator for batch insert statements.
*
* @param db the database to perform the insert on.
* @param tableName the name of the table to preform the insert on.
* @param columnCount the number of columns that will be insert per row.
* @param maxBinds the total number of values that can be bound to the statement.
*/
fun getInsertConfig(
db: SupportSQLiteDatabase,
tableName: String,
columnCount: Int,
maxBinds: Int = sqlMaxBinds
) = SupportSQLiteBinderConfig(
db,
"INSERT INTO $tableName VALUES",
columnCount,
maxBinds
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package co.yello.db.batchlight.androidsupportsqlite

import android.database.sqlite.SQLiteStatement
import androidx.sqlite.db.SupportSQLiteStatement
import co.yello.db.batchlight.androidsqlite.AndroidSQLiteBinder
import io.mockk.MockKAnnotations
import io.mockk.impl.annotations.MockK
import io.mockk.mockk
import io.mockk.verify
import org.junit.Assert
import org.junit.Before
import org.junit.Test

class AndroidSupportSQLiteBinderTest {
@MockK
lateinit var compiledStatement: SupportSQLiteStatement

private lateinit var androidSQLiteBinder: AndroidSupportSQLiteBinder

@Before
fun setUp() {
MockKAnnotations.init(this, relaxUnitFun = true)
androidSQLiteBinder = AndroidSupportSQLiteBinder(compiledStatement)
}

@Test
fun execute() {
androidSQLiteBinder.execute()
verify { compiledStatement.execute() }
}

@Test
fun clearBindings() {
androidSQLiteBinder.clear()
verify { compiledStatement.clearBindings() }
}

@Test
fun bindLong() {
androidSQLiteBinder.bindLong(1, 1)
verify { compiledStatement.bindLong(1, 1) }
}

@Test
fun bindString() {
androidSQLiteBinder.bindString(1, "")
verify { compiledStatement.bindString(1, "") }
}

@Test
fun bindDouble() {
androidSQLiteBinder.bindDouble(1, 0.0)
verify { compiledStatement.bindDouble(1, 0.0) }
}

@Test
fun bindBlob() {
val byteArray = ByteArray(1)
androidSQLiteBinder.bindBlob(1, byteArray)
verify { compiledStatement.bindBlob(1, byteArray) }
}

@Test
fun bindNull() {
androidSQLiteBinder.bindNull(1)
verify { compiledStatement.bindNull(1) }
}

@Test
fun testHashCode() {
Assert.assertEquals(compiledStatement.hashCode(), androidSQLiteBinder.hashCode())
}

@Test
fun `Test two AndroidSQLiteStatements with the same prepared statement are equal` () {
Assert.assertEquals(androidSQLiteBinder, AndroidSupportSQLiteBinder(compiledStatement))
}

@Test
fun `Test two AndroidSQLiteStatements with different prepared statement are equal` () {
val compiledStatement2 = mockk<SQLiteStatement>()
Assert.assertNotEquals(androidSQLiteBinder, AndroidSQLiteBinder(compiledStatement2))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package co.yello.db.batchlight.androidsupportsqlite

import androidx.sqlite.db.SupportSQLiteDatabase
import androidx.sqlite.db.SupportSQLiteStatement
import io.mockk.MockKAnnotations
import io.mockk.every
import io.mockk.impl.annotations.MockK
import io.mockk.verify
import org.junit.Assert
import org.junit.Before
import org.junit.Test

class SupportSQLiteBinderConfigTest {

@MockK
lateinit var sqLiteDatabase: SupportSQLiteDatabase

@MockK
lateinit var compiledStatement: SupportSQLiteStatement

@Before
fun setUp() = MockKAnnotations.init(this, relaxUnitFun = true)

@Test
fun `Test maxInsertBinder returns a binder with the maximum number of binds`() {
every { sqLiteDatabase.compileStatement("$insertSQL (?),(?)") } returns compiledStatement
val generator = SupportSQLiteBinderConfig(sqLiteDatabase,
insertSQL, 1, 2)

Assert.assertEquals(AndroidSupportSQLiteBinder(compiledStatement), generator.maxInsertBinder)
}

@Test
fun `Test generateBinder works with column size of 1`() {
every { sqLiteDatabase.compileStatement("$insertSQL (?)") } returns compiledStatement

val generator = SupportSQLiteBinderConfig(sqLiteDatabase, insertSQL, 1, 2)

Assert.assertEquals(AndroidSupportSQLiteBinder(compiledStatement), generator.buildBinder(1))
}

@Test
fun `Test generateStatement works with column size greater than 1`() {
every { sqLiteDatabase.compileStatement("$insertSQL (?,?),(?,?)") } returns compiledStatement

val generator = SupportSQLiteBinderConfig(sqLiteDatabase,
insertSQL, 2, 2)

Assert.assertEquals(AndroidSupportSQLiteBinder(compiledStatement), generator.buildBinder(2))
}

@Suppress("SyntaxError")
@Test
fun `Test getReplaceGenerator uses replace into to compile the query`() {
val tableName = "MediumObjects"
val expectedSql = "REPLACE INTO $tableName VALUES (?)"
every { sqLiteDatabase.compileStatement(expectedSql) } returns compiledStatement

SupportSQLiteBinderConfig.getReplaceConfig(sqLiteDatabase, tableName, 1, 1).maxInsertBinder

verify { sqLiteDatabase.compileStatement(expectedSql) }
}

@Test
fun `Test getReplaceGenerator uses insert into to compile the query`() {
val tableName = "MediumObjects"
val expectedSql = "INSERT INTO $tableName VALUES (?)"
every { sqLiteDatabase.compileStatement(expectedSql) } returns compiledStatement

SupportSQLiteBinderConfig.getInsertConfig(sqLiteDatabase, tableName, 1, 1).maxInsertBinder

verify { sqLiteDatabase.compileStatement(expectedSql) }
}

companion object {
private const val insertSQL = "INSERT INTO TABLE EXAMPLE"
}
}

0 comments on commit 95e5cf6

Please sign in to comment.