diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8f2d33b --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +*.iml +.gradle +/local.properties +/.idea/caches/build_file_checksums.ser +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +.DS_Store +/build +/captures +.externalNativeBuild +*.idea diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..eaa990c --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,11 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [1.0.0] +### Added +- Initial version +- Provides a simple interface for creating batch inserts and replaces. +- Supports Android framework sqlite diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..c63336f --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,8 @@ +Contributing +============ + +If you would like to contribute code to this project you can do so through GitHub by +forking the repository and sending a pull request. + +When submitting code, please make every effort to follow existing conventions +and style in order to keep the code as readable as possible. diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..c487b66 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Yello + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..f1b5231 --- /dev/null +++ b/README.md @@ -0,0 +1,84 @@ +Batch Light +===== + +Batch Light is a library for batch inserting to SQLite on Android. + +A demo project can be found on Google Play, and as an Instant App. + + +Demo Project on Google Play + + +## Download + +## Usage +Batch Lite has 4 main steps to use. + +1. Create a configuration object. Currently SQLite is the only binder available with the batcher. +The SQLiteBinderConfig has to default types available, Insert (INSERT INTO) and Replace (REPLACE INTO). When getting +an instance of the SQLiteBinderConfig there are three params. A reference to the SQLite Database being written to, +the name of the table being written to and the number of columns that will be written to. + +```kotlin +val binderConfig = SQLiteBinderConfig.getInsertConfig( + db = writableDatabase, + tableName = batchTableName, + columnCount = 3 +) +``` + +2. Create a new batcher using the config from step 1. + +```kotlin +val batcher = BatchStatement(binderConfig) +``` + +3. call the execute function on the batcher, passing in the list to be inserted and a clojure that tells it how to bind +the items in the list. The config will generate a statement that looks like: + +```kotlin +data class InsertItem(val id: Int, val text: String) +``` + +Table structure: +``` +TABLE_NAME +----------- +id | text | +``` + +The generated SQLite for a list of 3 of the items above would be: + +```roomsql +INSERT INTO TABLE_NAME VALUES (?, ?), (?, ?), (?, ?) +``` + +To bind the list, the closure below should be used. The binder will use the clojure for each item to bind to the generated +statement. + +```kotlin +batcher.execute(list) { listItem -> + bindLong(listItem.id.toLong()) + bindString(listItem.text) +} +``` + +4. The Batch Lite does not handle transactions. It is left up to the end user to wrap the batch +insert into a transaction. If the list that is being batched is over 10,000 items it is recommended to chunk the list +and batch it in separate transactions to avoid blocking other inserts happening in the application. + +An example usage can be found in the sample project. + +Check out the [Kdoc](https://yelloco.github.io/Batchlight) for full documentation. + +## Contributing + +Please read [Contributing.md](CONTRIBUTING.md) for details on the process for submitting pull requests. + +## License + +This project is licensed under the MIT License - see the [License.txt](LICENSE.txt) file for details. + +## Questions + +Any questions comments or concerns can be directed to opensource@yello.co. diff --git a/batchlight/.gitignore b/batchlight/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/batchlight/.gitignore @@ -0,0 +1 @@ +/build diff --git a/batchlight/build.gradle b/batchlight/build.gradle new file mode 100644 index 0000000..c8dca90 --- /dev/null +++ b/batchlight/build.gradle @@ -0,0 +1,31 @@ +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' +apply plugin: 'com.github.dcendents.android-maven' +apply plugin: 'org.jetbrains.dokka-android' + +group = 'co.yello.batchlight' + +android { + compileSdkVersion 28 + testOptions.unitTests.includeAndroidResources = true + defaultConfig { + minSdkVersion 21 + targetSdkVersion 28 + } +} + +dokka { + outputFormat = 'html' + outputDirectory = "$buildDir/../javadoc" + reportUndocumented = false + skipDeprecated = true +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + + testImplementation 'junit:junit:4.12' + testImplementation "io.mockk:mockk:1.9" + testImplementation "org.robolectric:robolectric:4.2" +} \ No newline at end of file diff --git a/batchlight/src/main/AndroidManifest.xml b/batchlight/src/main/AndroidManifest.xml new file mode 100644 index 0000000..7dbd481 --- /dev/null +++ b/batchlight/src/main/AndroidManifest.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/batchlight/src/main/java/co/yello/db/batchlight/BatchBinder.kt b/batchlight/src/main/java/co/yello/db/batchlight/BatchBinder.kt new file mode 100644 index 0000000..aed238f --- /dev/null +++ b/batchlight/src/main/java/co/yello/db/batchlight/BatchBinder.kt @@ -0,0 +1,69 @@ +package co.yello.db.batchlight + +/** + * A wrapper around a [Binder] to make sure binds happen in sequential order. + * + * @param binder the specific [Binder] to bind values to. + * @param startBindIndex the index to start binding to the binder. + * + * @constructor Creates a BatchBinder with the given binder. The binder will be cleared on creation to ensure + * no pre existing values will be bound unintentionally. + */ +class BatchBinder( + private val binder: Binder, + startBindIndex: Int +) { + + /** + * Marks the current binding position. + */ + var currentBindIndex: Int = startBindIndex + private set + + init { + binder.clear() + } + + /** + * Binds a value of type [Long] to the [Binder] and increments the position. + * + * @param long a [Long] to be bound to the [Binder] + */ + fun bindLong(long: Long) { + binder.bindLong(currentBindIndex++, long) + } + + /** + * Binds a value of type [String] to the [Binder] and increments the position. + * + * @param string a [String] to be bound to the [Binder]. + */ + fun bindString(string: String) { + binder.bindString(currentBindIndex++, string) + } + + /** + * Binds a value of type [Double] to the [Binder] and increments the position. + * + * @param double a [Double] to be bound to the [Binder]. + */ + fun bindDouble(double: Double) { + binder.bindDouble(currentBindIndex++, double) + } + + /** + * Binds a null value to the [Binder] and increments the bind position. + */ + fun bindNull() { + binder.bindNull(currentBindIndex++) + } + + /** + * Binds a [ByteArray] to the [Binder] and increments the position. + * + * @param blob a [ByteArray] to be bound to the [Binder]. + */ + fun bindBlob(blob: ByteArray) { + binder.bindBlob(currentBindIndex++, blob) + } +} \ No newline at end of file diff --git a/batchlight/src/main/java/co/yello/db/batchlight/BatchStatement.kt b/batchlight/src/main/java/co/yello/db/batchlight/BatchStatement.kt new file mode 100644 index 0000000..9a70e9f --- /dev/null +++ b/batchlight/src/main/java/co/yello/db/batchlight/BatchStatement.kt @@ -0,0 +1,86 @@ +package co.yello.db.batchlight + +/** + * Drives the inserts into the database. + * + * @param T The type of the collection that will be inserted. This is used to pass into the bind function that is + * passed into the execute function. + * @property binderConfig A [BinderConfig] used to generate all of the [Binder]s that are required to perform + * the inserts. This flexibility lets the library work with a variety of databases. + */ +class BatchStatement( + private val binderConfig: BinderConfig +) { + + /** + * Chunks the list into the maximum sized list that can be inserted at a time, then executes the insert + * with the [bindItemFunction] passed into the class. + * + * @param itemsToInsert The collection of items of type [T] to batch insert into the database. + * @param bindItemFunction A function that performs the binds for each field in the [T] passed to the function. + * The function is an extension on [BatchBinder] so all bind functions can be accessed from within. + * + * When binding the order of the bind calls should match the structure of the table inserting. + * + * Example usage: + * + * A simple class would look something like + * + * ``` + * data class BatchClass(val id: Int, val text: String, val num: Int) + *``` + * + * The SQL Create statement for the corresponding table to that object would be: + * + * ``` + * CREATE TABLE BatchClass ( + * id INTEGER, + * text TEXT, + * num INTEGER + * ) + *``` + * + * The [Binder] generated for this table will will have the params (?,?,?). To correctly bind the three values + * to their correct ? in the prepared statement the function would be: + * + * ``` + * { batchObject - > + * bindLong(batchObject.id) + * bindString(batchObject.text) + * bindLong(batchObject.num) + * } + * ``` + */ + fun execute(itemsToInsert: Collection, bindItemFunction: BatchBinder.(T) -> Unit) { + itemsToInsert.asSequence() + .chunked(binderConfig.maxInsertSize) + .forEach { maxSizeList -> performInsert(maxSizeList, bindItemFunction) } + } + + /** + * Binds all values to the correct [Binder] for the given collection then executes the statement. + * + * @param collection The collection of items to be bound and executed. + * @param bindItem The bind statement to execute on each item of the collection. + */ + private fun performInsert(collection: Collection, bindItem: BatchBinder.(T) -> Unit) { + val statement = if (collection.size == binderConfig.maxInsertSize) { + binderConfig.maxInsertBinder + } else { + binderConfig.buildBinder(collection.size) + } + + val binder = BatchBinder(statement, binderConfig.startIndex) + + collection.forEach { item -> + binder.bindItem(item) + } + + check(binder.currentBindIndex - 1 == collection.size * binderConfig.fieldsPerItem) { + "Expected to bind ${binderConfig.fieldsPerItem} columns per record, " + + "found ${binder.currentBindIndex / collection.size} binds per record." + } + + statement.execute() + } +} \ No newline at end of file diff --git a/batchlight/src/main/java/co/yello/db/batchlight/Binder.kt b/batchlight/src/main/java/co/yello/db/batchlight/Binder.kt new file mode 100644 index 0000000..e39caf7 --- /dev/null +++ b/batchlight/src/main/java/co/yello/db/batchlight/Binder.kt @@ -0,0 +1,56 @@ +package co.yello.db.batchlight + +/** + * Provides way to bind values to any DB or ORM. + */ +interface Binder { + + /** + * Clears any bound values. + */ + fun clear() + + /** + * Executes with any bound values. + */ + fun execute() + + /** + * Binds a [Long] at the given position. + * + * @param position position to bind at. + * @param long a value of type [Long] to bind. + */ + fun bindLong(position: Int, long: Long) + + /** + * Binds a [String] at the given position. + * + * @param position position to bind at. + * @param string a value of type [String] to bind. + */ + fun bindString(position: Int, string: String) + + /** + * Binds a [Double] at the given position. + * + * @param position position to bind at. + * @param double a value of type [Double] to bind. + */ + fun bindDouble(position: Int, double: Double) + + /** + * Binds a [ByteArray] at the given position. + * + * @param position position to bind at. + * @param blob a value of type [ByteArray] to bind. + */ + fun bindBlob(position: Int, blob: ByteArray) + + /** + * Binds a null value at the given position. + * + * @param position position to bind at. + */ + fun bindNull(position: Int) +} \ No newline at end of file diff --git a/batchlight/src/main/java/co/yello/db/batchlight/BinderConfig.kt b/batchlight/src/main/java/co/yello/db/batchlight/BinderConfig.kt new file mode 100644 index 0000000..94ec28b --- /dev/null +++ b/batchlight/src/main/java/co/yello/db/batchlight/BinderConfig.kt @@ -0,0 +1,45 @@ +package co.yello.db.batchlight + +/** + * Holds all the configurations for a [Binder]. + */ +interface BinderConfig { + + /** + * Creates a [Binder] that has the given number of bind locations. + * + * @param insertCount the number of fields that will be bound. + * @return a [Binder] object that has [insertCount] number of fields + */ + fun buildBinder(insertCount: Int): Binder + + /** + * Holds a reference to the max insert binder. This will be the most common case so it should + * be cached. + * + * @return a [Binder] object that has maximum number of insert params available. + */ + val maxInsertBinder: Binder + + /** + * @return The start index of where to bind begin binding. + */ + val startIndex: Int + + /** + * If an object has 4 fields and only 3 should be written to the database this value should be set to 3. + * + * @return The number of fields to be inserted per item. + */ + val fieldsPerItem: Int + + /** + * @return The maximum number fields that can be bound at one time. For example this number is 999 for SQLite Android. + */ + val maxFields: Int + + /** + * @return The max number of items that can be inserted at one time. + */ + val maxInsertSize: Int +} \ No newline at end of file diff --git a/batchlight/src/main/java/co/yello/db/batchlight/Constants.kt b/batchlight/src/main/java/co/yello/db/batchlight/Constants.kt new file mode 100644 index 0000000..610cb39 --- /dev/null +++ b/batchlight/src/main/java/co/yello/db/batchlight/Constants.kt @@ -0,0 +1,15 @@ +@file:Suppress("SpellCheckingInspection") + +package co.yello.db.batchlight + +/** + * The maximum number of bind params that Android SQLite allows per prepared statement. + * + * See SQLITE_MAX_VARIABLE_NUMBER in the SQLite docs at https://sqlite.org/limits.html + */ +const val sqlMaxBinds = 999 + +/** + * The start index of Android SQLite prepared statement binds. + */ +const val sqlAndroidPreparedStatementStartIndex = 1 diff --git a/batchlight/src/main/java/co/yello/db/batchlight/androidsqlite/AndroidSQLiteBinder.kt b/batchlight/src/main/java/co/yello/db/batchlight/androidsqlite/AndroidSQLiteBinder.kt new file mode 100644 index 0000000..82d53d7 --- /dev/null +++ b/batchlight/src/main/java/co/yello/db/batchlight/androidsqlite/AndroidSQLiteBinder.kt @@ -0,0 +1,50 @@ +package co.yello.db.batchlight.androidsqlite + +import android.database.sqlite.SQLiteStatement +import co.yello.db.batchlight.Binder + +/** + * The [Binder] implementation for SQLite on Android. + * + * @property sqLiteStatement android SQLite statement to interact with. + */ +class AndroidSQLiteBinder( + private val sqLiteStatement: SQLiteStatement +) : 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? AndroidSQLiteBinder)?.sqLiteStatement + } + + override fun hashCode(): Int { + return sqLiteStatement.hashCode() + } +} \ No newline at end of file diff --git a/batchlight/src/main/java/co/yello/db/batchlight/androidsqlite/SQLiteBinderConfig.kt b/batchlight/src/main/java/co/yello/db/batchlight/androidsqlite/SQLiteBinderConfig.kt new file mode 100644 index 0000000..f62a812 --- /dev/null +++ b/batchlight/src/main/java/co/yello/db/batchlight/androidsqlite/SQLiteBinderConfig.kt @@ -0,0 +1,98 @@ +package co.yello.db.batchlight.androidsqlite + +import android.database.sqlite.SQLiteDatabase +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 SQLite. + * + * @property db the 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 SQLiteBinderConfig( + private val db: SQLiteDatabase, + 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 + + /** + * For a SQLite insert statement each item has the format (?, ?, ?) + */ + private val objectStatement = (1..fieldsPerItem).joinToString( + prefix = "(", + transform = { "?" }, + separator = ",", + postfix= ")" + ) + + override val maxInsertBinder: Binder by lazy { + buildBinder(maxInsertSize) + } + + override val startIndex: Int = sqlAndroidPreparedStatementStartIndex + + override fun buildBinder(insertCount: Int): Binder { + val allObjects = (1..insertCount).joinToString(",") { objectStatement } + val compiledStatement = db.compileStatement("$batchStatement $allObjects") + return AndroidSQLiteBinder(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: SQLiteDatabase, + tableName: String, + columnCount: Int, + maxBinds: Int = sqlMaxBinds + ) = SQLiteBinderConfig( + 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: SQLiteDatabase, + tableName: String, + columnCount: Int, + maxBinds: Int = sqlMaxBinds + ) = SQLiteBinderConfig( + db, + "INSERT INTO $tableName VALUES", + columnCount, + maxBinds + ) + } +} \ No newline at end of file diff --git a/batchlight/src/test/java/co/yello/db/batchlight/BatchBinderTest.kt b/batchlight/src/test/java/co/yello/db/batchlight/BatchBinderTest.kt new file mode 100644 index 0000000..53c7bf3 --- /dev/null +++ b/batchlight/src/test/java/co/yello/db/batchlight/BatchBinderTest.kt @@ -0,0 +1,110 @@ +package co.yello.db.batchlight + +import io.mockk.* +import io.mockk.impl.annotations.MockK +import org.junit.Assert.assertEquals +import org.junit.Before +import org.junit.Test + +class BatchBinderTest { + + @MockK + lateinit var binder: Binder + + @Before + fun setUp() = MockKAnnotations.init(this, relaxUnitFun = true) + + private val startIndex = sqlAndroidPreparedStatementStartIndex + + @Test + fun `Test init clears bindings and sets position to 1`() { + val binder = BatchBinder(binder, startIndex) + verify { this@BatchBinderTest.binder.clear() } + assertEquals(1, binder.currentBindIndex) + } + + @Test + fun `Test bindLong binds and increments the position` () { + val binder = BatchBinder(binder, startIndex) + binder.bindLong(1) + binder.bindLong(2) + binder.bindLong(3) + + verifyOrder { + this@BatchBinderTest.binder.bindLong(1, 1) + this@BatchBinderTest.binder.bindLong(2, 2) + this@BatchBinderTest.binder.bindLong(3, 3) + } + + assertEquals(4, binder.currentBindIndex) + } + + @Test + fun `Test bindNull binds and increments the position`() { + val binder = BatchBinder(binder, startIndex) + binder.bindNull() + binder.bindNull() + binder.bindNull() + + verifyOrder { + this@BatchBinderTest.binder.bindNull(1) + this@BatchBinderTest.binder.bindNull(2) + this@BatchBinderTest.binder.bindNull(3) + } + + assertEquals(4, binder.currentBindIndex) + } + + @Test + fun `Test bindDouble binds and increments the position`() { + val binder = BatchBinder(binder, startIndex) + binder.bindDouble(1.0) + binder.bindDouble(2.0) + binder.bindDouble(3.0) + + verifyOrder { + this@BatchBinderTest.binder.bindDouble(1, 1.0) + this@BatchBinderTest.binder.bindDouble(2, 2.0) + this@BatchBinderTest.binder.bindDouble(3, 3.0) + } + + assertEquals(4, binder.currentBindIndex) + } + + @Test + fun `Test bindString binds and increments the position`() { + val binder = BatchBinder(binder, startIndex) + binder.bindString("1") + binder.bindString("2") + binder.bindString("3") + + verifyOrder { + this@BatchBinderTest.binder.bindString(1, "1") + this@BatchBinderTest.binder.bindString(2, "2") + this@BatchBinderTest.binder.bindString(3, "3") + } + + assertEquals(4, binder.currentBindIndex) + } + + @Test + fun `Test bindBlob binds and increments the position`() { + val binder = BatchBinder(binder, startIndex) + + val oneByte = ByteArray(1) + val twoByte = ByteArray(2) + val threeByte = ByteArray(3) + + binder.bindBlob(oneByte) + binder.bindBlob(twoByte) + binder.bindBlob(threeByte) + + verifyOrder { + this@BatchBinderTest.binder.bindBlob(1, oneByte) + this@BatchBinderTest.binder.bindBlob(2, twoByte) + this@BatchBinderTest.binder.bindBlob(3, threeByte) + } + + assertEquals(4, binder.currentBindIndex) + } +} \ No newline at end of file diff --git a/batchlight/src/test/java/co/yello/db/batchlight/BatchStatementTest.kt b/batchlight/src/test/java/co/yello/db/batchlight/BatchStatementTest.kt new file mode 100644 index 0000000..b1d0d82 --- /dev/null +++ b/batchlight/src/test/java/co/yello/db/batchlight/BatchStatementTest.kt @@ -0,0 +1,138 @@ +package co.yello.db.batchlight + +import io.mockk.* +import io.mockk.impl.annotations.MockK +import org.junit.Before +import org.junit.Test + +class BatchStatementTest { + + @MockK + lateinit var maxBinder: Binder + + @Before + fun setUp() { + MockKAnnotations.init(this, relaxUnitFun = true) + } + + @Test + fun `Test bindEmptyList does nothing`() { + val binderConfig = mockBinderConfig(0) + + val batchStatement = BatchStatement(binderConfig) + + batchStatement.execute(listOf()) { } + + verify { listOf(maxBinder) wasNot called } + } + + @Test + fun `Test execute max statement`() { + val binderConfig = mockBinderConfig(1) + + val batchStatement = BatchStatement(binderConfig) + + val insertCount = maxInsertSize + val list = mutableListOf() + for (i in 1..insertCount) { list.add(TestClass(i)) } + + batchStatement.execute(list) {item -> + bindLong(item.id.toLong()) + } + + verifyOrder { + for (i in 1..insertCount/ maxInsertSize) { + binderConfig.maxInsertBinder + maxBinder.execute() + } + } + } + + @Test + fun `Test execute with list size max + 1`() { + val statement = mockk(relaxUnitFun = true) + val columnCount = 1 + + val binderConfig = mockBinderConfig(columnCount, 2) + every { binderConfig.buildBinder(any()) } returns statement + + + val batchStatement = BatchStatement(binderConfig) + + // 2 of the inserts will use the max binder and the last insert will need a new binder + val insertCount = 5 + val list = mutableListOf() + for (i in 1..insertCount) { list.add(TestClass(i)) } + + batchStatement.execute(list) { item -> + bindLong(item.id.toLong()) + } + + verifyOrder { + for (i in 1..insertCount/ (maxInsertSize/columnCount)) { + binderConfig.maxInsertBinder + maxBinder.execute() + } + binderConfig.buildBinder(insertCount.rem(maxInsertSize)) + statement.execute() + } + } + + @Test + fun `Test binder lambda gets executed`() { + val binderConfig = mockBinderConfig(1) + + val batchStatement = BatchStatement(binderConfig) + + val insertCount = 2 + val list = mutableListOf() + for (i in 1..insertCount) { list.add(TestClass(i)) } + + batchStatement.execute(list) { + bindLong(it.id.toLong()) + } + + verifyOrder { + for (i in 1..insertCount/ maxInsertSize) { + binderConfig.maxInsertBinder + maxBinder.bindLong(1, i*2L-1) + maxBinder.bindLong(2, i*2L) + maxBinder.execute() + } + } + } + + @Test(expected = IllegalStateException::class) + fun `Test too many binds in the lambda throws exception`() { + val binderConfig = mockBinderConfig(0, 1) + + val batchStatement = BatchStatement(binderConfig) + + batchStatement.execute(mutableListOf(TestObject)) { + bindLong(1) + } + } + + @Test(expected = IllegalStateException::class) + fun `Test too few binds in the lambda throws exception`() { + val binderConfig = mockBinderConfig(1, 1) + + val batchStatement = BatchStatement(binderConfig) + + batchStatement.execute(mutableListOf(TestClass(1))) {} + } + + private fun mockBinderConfig(fieldsPer: Int, maxInsert: Int = maxInsertSize) = mockk().apply { + every { maxInsertBinder } returns maxBinder + every { fieldsPerItem } returns fieldsPer + every { maxInsertSize } returns maxInsert + every { startIndex } returns 1 + } + + companion object { + private const val maxInsertSize = 2 + } + + object TestObject + data class TestClass(val id: Int) +} diff --git a/batchlight/src/test/java/co/yello/db/batchlight/androidsqlite/AndroidSQLiteBinderTest.kt b/batchlight/src/test/java/co/yello/db/batchlight/androidsqlite/AndroidSQLiteBinderTest.kt new file mode 100644 index 0000000..6f77413 --- /dev/null +++ b/batchlight/src/test/java/co/yello/db/batchlight/androidsqlite/AndroidSQLiteBinderTest.kt @@ -0,0 +1,83 @@ +package co.yello.db.batchlight.androidsqlite + +import android.database.sqlite.SQLiteStatement +import io.mockk.MockKAnnotations +import io.mockk.impl.annotations.MockK +import io.mockk.mockk +import io.mockk.verify +import org.junit.Before +import org.junit.Test + +import org.junit.Assert.* + +class AndroidSQLiteBinderTest { + @MockK + lateinit var compiledStatement: SQLiteStatement + + private lateinit var androidSQLiteBinder: AndroidSQLiteBinder + + @Before + fun setUp() { + MockKAnnotations.init(this, relaxUnitFun = true) + androidSQLiteBinder = AndroidSQLiteBinder(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() { + assertEquals(compiledStatement.hashCode(), androidSQLiteBinder.hashCode()) + } + + @Test + fun `Test two AndroidSQLiteStatements with the same prepared statement are equal` () { + assertEquals(androidSQLiteBinder, AndroidSQLiteBinder(compiledStatement)) + } + + @Test + fun `Test two AndroidSQLiteStatements with different prepared statement are equal` () { + val compiledStatement2 = mockk() + assertNotEquals(androidSQLiteBinder, AndroidSQLiteBinder(compiledStatement2)) + } +} \ No newline at end of file diff --git a/batchlight/src/test/java/co/yello/db/batchlight/androidsqlite/SQLiteBinderConfigTest.kt b/batchlight/src/test/java/co/yello/db/batchlight/androidsqlite/SQLiteBinderConfigTest.kt new file mode 100644 index 0000000..1188e23 --- /dev/null +++ b/batchlight/src/test/java/co/yello/db/batchlight/androidsqlite/SQLiteBinderConfigTest.kt @@ -0,0 +1,77 @@ +package co.yello.db.batchlight.androidsqlite + +import android.database.sqlite.SQLiteDatabase +import android.database.sqlite.SQLiteStatement +import io.mockk.MockKAnnotations +import io.mockk.every +import io.mockk.impl.annotations.MockK +import io.mockk.verify +import org.junit.Assert.assertEquals +import org.junit.Before +import org.junit.Test + +class SQLiteBinderConfigTest { + + @MockK + lateinit var sqLiteDatabase: SQLiteDatabase + @MockK + lateinit var compiledStatement: SQLiteStatement + + @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 = SQLiteBinderConfig(sqLiteDatabase, + insertSQL, 1, 2) + + assertEquals(AndroidSQLiteBinder(compiledStatement), generator.maxInsertBinder) + } + + @Test + fun `Test generateBinder works with column size of 1`() { + every { sqLiteDatabase.compileStatement("$insertSQL (?)") } returns compiledStatement + + val generator = SQLiteBinderConfig(sqLiteDatabase, insertSQL, 1, 2) + + assertEquals(AndroidSQLiteBinder(compiledStatement), generator.buildBinder(1)) + } + + @Test + fun `Test generateStatement works with column size greater than 1`() { + every { sqLiteDatabase.compileStatement("$insertSQL (?,?),(?,?)") } returns compiledStatement + + val generator = SQLiteBinderConfig(sqLiteDatabase, + insertSQL, 2, 2) + + assertEquals(AndroidSQLiteBinder(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 + + SQLiteBinderConfig.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 + + SQLiteBinderConfig.getInsertConfig(sqLiteDatabase, tableName, 1, 1).maxInsertBinder + + verify { sqLiteDatabase.compileStatement(expectedSql) } + } + + companion object { + private const val insertSQL = "INSERT INTO TABLE EXAMPLE" + } +} \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..e75473e --- /dev/null +++ b/build.gradle @@ -0,0 +1,26 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. + +buildscript { + ext.kotlin_version = '1.3.30' + repositories { + google() + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:3.4.0' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1' + classpath 'org.jetbrains.dokka:dokka-android-gradle-plugin:0.9.18' + } +} + +allprojects { + repositories { + google() + jcenter() + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/docs/alltypes/index.html b/docs/alltypes/index.html new file mode 100644 index 0000000..6978b14 --- /dev/null +++ b/docs/alltypes/index.html @@ -0,0 +1,56 @@ + + + +alltypes - batchlight + + + +

All Types

+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
+co.yello.db.batchlight.androidsqlite.AndroidSQLiteBinder +

The Binder implementation for SQLite on Android.

+
+co.yello.db.batchlight.BatchBinder +

A wrapper around a Binder to make sure binds happen in sequential order.

+
+co.yello.db.batchlight.BatchStatement +

Drives the inserts into the database.

+
+co.yello.db.batchlight.Binder +

Provides way to bind values to any DB or ORM.

+
+co.yello.db.batchlight.BinderConfig +

Holds all the configurations for a Binder.

+
+co.yello.db.batchlight.androidsqlite.SQLiteBinderConfig +

The BinderConfig for Android SQLite.

+
+ + diff --git a/docs/co.yello.db.batchlight.androidsqlite/-android-s-q-lite-binder/-init-.html b/docs/co.yello.db.batchlight.androidsqlite/-android-s-q-lite-binder/-init-.html new file mode 100644 index 0000000..e28b1fc --- /dev/null +++ b/docs/co.yello.db.batchlight.androidsqlite/-android-s-q-lite-binder/-init-.html @@ -0,0 +1,15 @@ + + + +AndroidSQLiteBinder.<init> - batchlight + + + +batchlight / co.yello.db.batchlight.androidsqlite / AndroidSQLiteBinder / <init>
+
+

<init>

+ +AndroidSQLiteBinder(sqLiteStatement: SQLiteStatement) +

The Binder implementation for SQLite on Android.

+ + diff --git a/docs/co.yello.db.batchlight.androidsqlite/-android-s-q-lite-binder/bind-blob.html b/docs/co.yello.db.batchlight.androidsqlite/-android-s-q-lite-binder/bind-blob.html new file mode 100644 index 0000000..1e2df5d --- /dev/null +++ b/docs/co.yello.db.batchlight.androidsqlite/-android-s-q-lite-binder/bind-blob.html @@ -0,0 +1,21 @@ + + + +AndroidSQLiteBinder.bindBlob - batchlight + + + +batchlight / co.yello.db.batchlight.androidsqlite / AndroidSQLiteBinder / bindBlob
+
+

bindBlob

+ +fun bindBlob(position: Int, blob: ByteArray): Unit +

Overrides Binder.bindBlob

+

Binds a ByteArray at the given position.

+

Parameters

+

+position - position to bind at.

+

+blob - a value of type ByteArray to bind.

+ + diff --git a/docs/co.yello.db.batchlight.androidsqlite/-android-s-q-lite-binder/bind-double.html b/docs/co.yello.db.batchlight.androidsqlite/-android-s-q-lite-binder/bind-double.html new file mode 100644 index 0000000..45718e7 --- /dev/null +++ b/docs/co.yello.db.batchlight.androidsqlite/-android-s-q-lite-binder/bind-double.html @@ -0,0 +1,21 @@ + + + +AndroidSQLiteBinder.bindDouble - batchlight + + + +batchlight / co.yello.db.batchlight.androidsqlite / AndroidSQLiteBinder / bindDouble
+
+

bindDouble

+ +fun bindDouble(position: Int, double: Double): Unit +

Overrides Binder.bindDouble

+

Binds a Double at the given position.

+

Parameters

+

+position - position to bind at.

+

+double - a value of type Double to bind.

+ + diff --git a/docs/co.yello.db.batchlight.androidsqlite/-android-s-q-lite-binder/bind-long.html b/docs/co.yello.db.batchlight.androidsqlite/-android-s-q-lite-binder/bind-long.html new file mode 100644 index 0000000..35837ce --- /dev/null +++ b/docs/co.yello.db.batchlight.androidsqlite/-android-s-q-lite-binder/bind-long.html @@ -0,0 +1,21 @@ + + + +AndroidSQLiteBinder.bindLong - batchlight + + + +batchlight / co.yello.db.batchlight.androidsqlite / AndroidSQLiteBinder / bindLong
+
+

bindLong

+ +fun bindLong(position: Int, long: Long): Unit +

Overrides Binder.bindLong

+

Binds a Long at the given position.

+

Parameters

+

+position - position to bind at.

+

+long - a value of type Long to bind.

+ + diff --git a/docs/co.yello.db.batchlight.androidsqlite/-android-s-q-lite-binder/bind-null.html b/docs/co.yello.db.batchlight.androidsqlite/-android-s-q-lite-binder/bind-null.html new file mode 100644 index 0000000..559f50f --- /dev/null +++ b/docs/co.yello.db.batchlight.androidsqlite/-android-s-q-lite-binder/bind-null.html @@ -0,0 +1,19 @@ + + + +AndroidSQLiteBinder.bindNull - batchlight + + + +batchlight / co.yello.db.batchlight.androidsqlite / AndroidSQLiteBinder / bindNull
+
+

bindNull

+ +fun bindNull(position: Int): Unit +

Overrides Binder.bindNull

+

Binds a null value at the given position.

+

Parameters

+

+position - position to bind at.

+ + diff --git a/docs/co.yello.db.batchlight.androidsqlite/-android-s-q-lite-binder/bind-string.html b/docs/co.yello.db.batchlight.androidsqlite/-android-s-q-lite-binder/bind-string.html new file mode 100644 index 0000000..f361aed --- /dev/null +++ b/docs/co.yello.db.batchlight.androidsqlite/-android-s-q-lite-binder/bind-string.html @@ -0,0 +1,21 @@ + + + +AndroidSQLiteBinder.bindString - batchlight + + + +batchlight / co.yello.db.batchlight.androidsqlite / AndroidSQLiteBinder / bindString
+
+

bindString

+ +fun bindString(position: Int, string: String): Unit +

Overrides Binder.bindString

+

Binds a String at the given position.

+

Parameters

+

+position - position to bind at.

+

+string - a value of type String to bind.

+ + diff --git a/docs/co.yello.db.batchlight.androidsqlite/-android-s-q-lite-binder/clear.html b/docs/co.yello.db.batchlight.androidsqlite/-android-s-q-lite-binder/clear.html new file mode 100644 index 0000000..34d5904 --- /dev/null +++ b/docs/co.yello.db.batchlight.androidsqlite/-android-s-q-lite-binder/clear.html @@ -0,0 +1,16 @@ + + + +AndroidSQLiteBinder.clear - batchlight + + + +batchlight / co.yello.db.batchlight.androidsqlite / AndroidSQLiteBinder / clear
+
+

clear

+ +fun clear(): Unit +

Overrides Binder.clear

+

Clears any bound values.

+ + diff --git a/docs/co.yello.db.batchlight.androidsqlite/-android-s-q-lite-binder/equals.html b/docs/co.yello.db.batchlight.androidsqlite/-android-s-q-lite-binder/equals.html new file mode 100644 index 0000000..47363a5 --- /dev/null +++ b/docs/co.yello.db.batchlight.androidsqlite/-android-s-q-lite-binder/equals.html @@ -0,0 +1,14 @@ + + + +AndroidSQLiteBinder.equals - batchlight + + + +batchlight / co.yello.db.batchlight.androidsqlite / AndroidSQLiteBinder / equals
+
+

equals

+ +fun equals(other: Any?): Boolean + + diff --git a/docs/co.yello.db.batchlight.androidsqlite/-android-s-q-lite-binder/execute.html b/docs/co.yello.db.batchlight.androidsqlite/-android-s-q-lite-binder/execute.html new file mode 100644 index 0000000..1398b30 --- /dev/null +++ b/docs/co.yello.db.batchlight.androidsqlite/-android-s-q-lite-binder/execute.html @@ -0,0 +1,16 @@ + + + +AndroidSQLiteBinder.execute - batchlight + + + +batchlight / co.yello.db.batchlight.androidsqlite / AndroidSQLiteBinder / execute
+
+

execute

+ +fun execute(): Unit +

Overrides Binder.execute

+

Executes with any bound values.

+ + diff --git a/docs/co.yello.db.batchlight.androidsqlite/-android-s-q-lite-binder/hash-code.html b/docs/co.yello.db.batchlight.androidsqlite/-android-s-q-lite-binder/hash-code.html new file mode 100644 index 0000000..def9a1b --- /dev/null +++ b/docs/co.yello.db.batchlight.androidsqlite/-android-s-q-lite-binder/hash-code.html @@ -0,0 +1,14 @@ + + + +AndroidSQLiteBinder.hashCode - batchlight + + + +batchlight / co.yello.db.batchlight.androidsqlite / AndroidSQLiteBinder / hashCode
+
+

hashCode

+ +fun hashCode(): Int + + diff --git a/docs/co.yello.db.batchlight.androidsqlite/-android-s-q-lite-binder/index.html b/docs/co.yello.db.batchlight.androidsqlite/-android-s-q-lite-binder/index.html new file mode 100644 index 0000000..c89ba57 --- /dev/null +++ b/docs/co.yello.db.batchlight.androidsqlite/-android-s-q-lite-binder/index.html @@ -0,0 +1,110 @@ + + + +AndroidSQLiteBinder - batchlight + + + +batchlight / co.yello.db.batchlight.androidsqlite / AndroidSQLiteBinder
+
+

AndroidSQLiteBinder

+class AndroidSQLiteBinder : Binder +

The Binder implementation for SQLite on Android.

+

Constructors

+ + + + + + + +
+

<init>

+
+AndroidSQLiteBinder(sqLiteStatement: SQLiteStatement) +

The Binder implementation for SQLite on Android.

+
+

Functions

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+

bindBlob

+
+fun bindBlob(position: Int, blob: ByteArray): Unit +

Binds a ByteArray at the given position.

+
+

bindDouble

+
+fun bindDouble(position: Int, double: Double): Unit +

Binds a Double at the given position.

+
+

bindLong

+
+fun bindLong(position: Int, long: Long): Unit +

Binds a Long at the given position.

+
+

bindNull

+
+fun bindNull(position: Int): Unit +

Binds a null value at the given position.

+
+

bindString

+
+fun bindString(position: Int, string: String): Unit +

Binds a String at the given position.

+
+

clear

+
+fun clear(): Unit +

Clears any bound values.

+
+

equals

+
+fun equals(other: Any?): Boolean
+

execute

+
+fun execute(): Unit +

Executes with any bound values.

+
+

hashCode

+
+fun hashCode(): Int
+ + diff --git a/docs/co.yello.db.batchlight.androidsqlite/-s-q-lite-binder-config/-init-.html b/docs/co.yello.db.batchlight.androidsqlite/-s-q-lite-binder-config/-init-.html new file mode 100644 index 0000000..f49b6b2 --- /dev/null +++ b/docs/co.yello.db.batchlight.androidsqlite/-s-q-lite-binder-config/-init-.html @@ -0,0 +1,21 @@ + + + +SQLiteBinderConfig.<init> - batchlight + + + +batchlight / co.yello.db.batchlight.androidsqlite / SQLiteBinderConfig / <init>
+
+

<init>

+ +SQLiteBinderConfig(db: SQLiteDatabase, batchStatement: String, fieldsPerItem: Int, maxFields: Int) +

Parameters

+

+fieldsPerItem - the number of fields per row to be inserted.

+

+maxFields - the maximum number of binds that can happen per statement.

+

Constructor
+

+ + diff --git a/docs/co.yello.db.batchlight.androidsqlite/-s-q-lite-binder-config/build-binder.html b/docs/co.yello.db.batchlight.androidsqlite/-s-q-lite-binder-config/build-binder.html new file mode 100644 index 0000000..89ac56c --- /dev/null +++ b/docs/co.yello.db.batchlight.androidsqlite/-s-q-lite-binder-config/build-binder.html @@ -0,0 +1,21 @@ + + + +SQLiteBinderConfig.buildBinder - batchlight + + + +batchlight / co.yello.db.batchlight.androidsqlite / SQLiteBinderConfig / buildBinder
+
+

buildBinder

+ +fun buildBinder(insertCount: Int): Binder +

Overrides BinderConfig.buildBinder

+

Creates a Binder that has the given number of bind locations.

+

Parameters

+

+insertCount - the number of fields that will be bound.

+

Return
+a Binder object that has insertCount number of fields

+ + diff --git a/docs/co.yello.db.batchlight.androidsqlite/-s-q-lite-binder-config/fields-per-item.html b/docs/co.yello.db.batchlight.androidsqlite/-s-q-lite-binder-config/fields-per-item.html new file mode 100644 index 0000000..71833ad --- /dev/null +++ b/docs/co.yello.db.batchlight.androidsqlite/-s-q-lite-binder-config/fields-per-item.html @@ -0,0 +1,16 @@ + + + +SQLiteBinderConfig.fieldsPerItem - batchlight + + + +batchlight / co.yello.db.batchlight.androidsqlite / SQLiteBinderConfig / fieldsPerItem
+
+

fieldsPerItem

+ +val fieldsPerItem: Int +

Overrides BinderConfig.fieldsPerItem

+

the number of fields per row to be inserted.

+ + diff --git a/docs/co.yello.db.batchlight.androidsqlite/-s-q-lite-binder-config/get-insert-config.html b/docs/co.yello.db.batchlight.androidsqlite/-s-q-lite-binder-config/get-insert-config.html new file mode 100644 index 0000000..0730a19 --- /dev/null +++ b/docs/co.yello.db.batchlight.androidsqlite/-s-q-lite-binder-config/get-insert-config.html @@ -0,0 +1,24 @@ + + + +SQLiteBinderConfig.getInsertConfig - batchlight + + + +batchlight / co.yello.db.batchlight.androidsqlite / SQLiteBinderConfig / getInsertConfig
+
+

getInsertConfig

+ +fun getInsertConfig(db: SQLiteDatabase, tableName: String, columnCount: Int, maxBinds: Int = sqlMaxBinds): SQLiteBinderConfig +

Creates a statement generator for batch insert statements.

+

Parameters

+

+db - the database to perform the insert on.

+

+tableName - the name of the table to preform the insert on.

+

+columnCount - the number of columns that will be insert per row.

+

+maxBinds - the total number of values that can be bound to the statement.

+ + diff --git a/docs/co.yello.db.batchlight.androidsqlite/-s-q-lite-binder-config/get-replace-config.html b/docs/co.yello.db.batchlight.androidsqlite/-s-q-lite-binder-config/get-replace-config.html new file mode 100644 index 0000000..32dea89 --- /dev/null +++ b/docs/co.yello.db.batchlight.androidsqlite/-s-q-lite-binder-config/get-replace-config.html @@ -0,0 +1,24 @@ + + + +SQLiteBinderConfig.getReplaceConfig - batchlight + + + +batchlight / co.yello.db.batchlight.androidsqlite / SQLiteBinderConfig / getReplaceConfig
+
+

getReplaceConfig

+ +fun getReplaceConfig(db: SQLiteDatabase, tableName: String, columnCount: Int, maxBinds: Int = sqlMaxBinds): SQLiteBinderConfig +

Creates a statement generator for batch replace statements.

+

Parameters

+

+db - the database to perform the replaces on.

+

+tableName - the name of the table to preform the replaces on.

+

+columnCount - the number of columns that will be replaced per row.

+

+maxBinds - the total number of values that can be bound to the statement.

+ + diff --git a/docs/co.yello.db.batchlight.androidsqlite/-s-q-lite-binder-config/index.html b/docs/co.yello.db.batchlight.androidsqlite/-s-q-lite-binder-config/index.html new file mode 100644 index 0000000..0e59da4 --- /dev/null +++ b/docs/co.yello.db.batchlight.androidsqlite/-s-q-lite-binder-config/index.html @@ -0,0 +1,112 @@ + + + +SQLiteBinderConfig - batchlight + + + +batchlight / co.yello.db.batchlight.androidsqlite / SQLiteBinderConfig
+
+

SQLiteBinderConfig

+class SQLiteBinderConfig : BinderConfig +

The BinderConfig for Android SQLite.

+

Constructors

+ + + + + + + +
+

<init>

+
+SQLiteBinderConfig(db: SQLiteDatabase, batchStatement: String, fieldsPerItem: Int, maxFields: Int)
+

Properties

+ + + + + + + + + + + + + + + + + + + + + + + +
+

fieldsPerItem

+
+val fieldsPerItem: Int +

the number of fields per row to be inserted.

+
+

maxFields

+
+val maxFields: Int +

the maximum number of binds that can happen per statement.

+
+

maxInsertBinder

+
+val maxInsertBinder: Binder +

Holds a reference to the max insert binder. This will be the most common case so it should +be cached.

+
+

maxInsertSize

+
+val maxInsertSize: Int +

The maximum number of items that can be inserted per statement.

+
+

startIndex

+
+val startIndex: Int
+

Functions

+ + + + + + + +
+

buildBinder

+
+fun buildBinder(insertCount: Int): Binder +

Creates a Binder that has the given number of bind locations.

+
+

Companion Object Functions

+ + + + + + + + + + + +
+

getInsertConfig

+
+fun getInsertConfig(db: SQLiteDatabase, tableName: String, columnCount: Int, maxBinds: Int = sqlMaxBinds): SQLiteBinderConfig +

Creates a statement generator for batch insert statements.

+
+

getReplaceConfig

+
+fun getReplaceConfig(db: SQLiteDatabase, tableName: String, columnCount: Int, maxBinds: Int = sqlMaxBinds): SQLiteBinderConfig +

Creates a statement generator for batch replace statements.

+
+ + diff --git a/docs/co.yello.db.batchlight.androidsqlite/-s-q-lite-binder-config/max-fields.html b/docs/co.yello.db.batchlight.androidsqlite/-s-q-lite-binder-config/max-fields.html new file mode 100644 index 0000000..1106c05 --- /dev/null +++ b/docs/co.yello.db.batchlight.androidsqlite/-s-q-lite-binder-config/max-fields.html @@ -0,0 +1,16 @@ + + + +SQLiteBinderConfig.maxFields - batchlight + + + +batchlight / co.yello.db.batchlight.androidsqlite / SQLiteBinderConfig / maxFields
+
+

maxFields

+ +val maxFields: Int +

Overrides BinderConfig.maxFields

+

the maximum number of binds that can happen per statement.

+ + diff --git a/docs/co.yello.db.batchlight.androidsqlite/-s-q-lite-binder-config/max-insert-binder.html b/docs/co.yello.db.batchlight.androidsqlite/-s-q-lite-binder-config/max-insert-binder.html new file mode 100644 index 0000000..0356922 --- /dev/null +++ b/docs/co.yello.db.batchlight.androidsqlite/-s-q-lite-binder-config/max-insert-binder.html @@ -0,0 +1,25 @@ + + + +SQLiteBinderConfig.maxInsertBinder - batchlight + + + +batchlight / co.yello.db.batchlight.androidsqlite / SQLiteBinderConfig / maxInsertBinder
+
+

maxInsertBinder

+ +val maxInsertBinder: Binder +

Overrides BinderConfig.maxInsertBinder

+

Holds a reference to the max insert binder. This will be the most common case so it should +be cached.

+

Return
+a Binder object that has maximum number of insert params available.

+

Getter
+

Holds a reference to the max insert binder. This will be the most common case so it should +be cached.

+

+

Getter Return
+a Binder object that has maximum number of insert params available.

+ + diff --git a/docs/co.yello.db.batchlight.androidsqlite/-s-q-lite-binder-config/max-insert-size.html b/docs/co.yello.db.batchlight.androidsqlite/-s-q-lite-binder-config/max-insert-size.html new file mode 100644 index 0000000..b9980a6 --- /dev/null +++ b/docs/co.yello.db.batchlight.androidsqlite/-s-q-lite-binder-config/max-insert-size.html @@ -0,0 +1,16 @@ + + + +SQLiteBinderConfig.maxInsertSize - batchlight + + + +batchlight / co.yello.db.batchlight.androidsqlite / SQLiteBinderConfig / maxInsertSize
+
+

maxInsertSize

+ +val maxInsertSize: Int +

Overrides BinderConfig.maxInsertSize

+

The maximum number of items that can be inserted per statement.

+ + diff --git a/docs/co.yello.db.batchlight.androidsqlite/-s-q-lite-binder-config/start-index.html b/docs/co.yello.db.batchlight.androidsqlite/-s-q-lite-binder-config/start-index.html new file mode 100644 index 0000000..5b6ecd5 --- /dev/null +++ b/docs/co.yello.db.batchlight.androidsqlite/-s-q-lite-binder-config/start-index.html @@ -0,0 +1,17 @@ + + + +SQLiteBinderConfig.startIndex - batchlight + + + +batchlight / co.yello.db.batchlight.androidsqlite / SQLiteBinderConfig / startIndex
+
+

startIndex

+ +val startIndex: Int +

Overrides BinderConfig.startIndex

+

Return
+The start index of where to bind begin binding.

+ + diff --git a/docs/co.yello.db.batchlight.androidsqlite/index.html b/docs/co.yello.db.batchlight.androidsqlite/index.html new file mode 100644 index 0000000..d9024b5 --- /dev/null +++ b/docs/co.yello.db.batchlight.androidsqlite/index.html @@ -0,0 +1,35 @@ + + + +co.yello.db.batchlight.androidsqlite - batchlight + + + +batchlight / co.yello.db.batchlight.androidsqlite
+
+

Package co.yello.db.batchlight.androidsqlite

+

Types

+ + + + + + + + + + + +
+

AndroidSQLiteBinder

+
+class AndroidSQLiteBinder : Binder +

The Binder implementation for SQLite on Android.

+
+

SQLiteBinderConfig

+
+class SQLiteBinderConfig : BinderConfig +

The BinderConfig for Android SQLite.

+
+ + diff --git a/docs/co.yello.db.batchlight/-batch-binder/-init-.html b/docs/co.yello.db.batchlight/-batch-binder/-init-.html new file mode 100644 index 0000000..ca7afa4 --- /dev/null +++ b/docs/co.yello.db.batchlight/-batch-binder/-init-.html @@ -0,0 +1,19 @@ + + + +BatchBinder.<init> - batchlight + + + +batchlight / co.yello.db.batchlight / BatchBinder / <init>
+
+

<init>

+ +BatchBinder(binder: Binder, startBindIndex: Int) +

Creates a BatchBinder with the given binder. The binder will be cleared on creation to ensure +no pre existing values will be bound unintentionally.

+

Constructor
+Creates a BatchBinder with the given binder. The binder will be cleared on creation to ensure +no pre existing values will be bound unintentionally.

+ + diff --git a/docs/co.yello.db.batchlight/-batch-binder/bind-blob.html b/docs/co.yello.db.batchlight/-batch-binder/bind-blob.html new file mode 100644 index 0000000..7ec6316 --- /dev/null +++ b/docs/co.yello.db.batchlight/-batch-binder/bind-blob.html @@ -0,0 +1,18 @@ + + + +BatchBinder.bindBlob - batchlight + + + +batchlight / co.yello.db.batchlight / BatchBinder / bindBlob
+
+

bindBlob

+ +fun bindBlob(blob: ByteArray): Unit +

Binds a ByteArray to the Binder and increments the position.

+

Parameters

+

+blob - a ByteArray to be bound to the Binder.

+ + diff --git a/docs/co.yello.db.batchlight/-batch-binder/bind-double.html b/docs/co.yello.db.batchlight/-batch-binder/bind-double.html new file mode 100644 index 0000000..2924908 --- /dev/null +++ b/docs/co.yello.db.batchlight/-batch-binder/bind-double.html @@ -0,0 +1,18 @@ + + + +BatchBinder.bindDouble - batchlight + + + +batchlight / co.yello.db.batchlight / BatchBinder / bindDouble
+
+

bindDouble

+ +fun bindDouble(double: Double): Unit +

Binds a value of type Double to the Binder and increments the position.

+

Parameters

+

+double - a Double to be bound to the Binder.

+ + diff --git a/docs/co.yello.db.batchlight/-batch-binder/bind-long.html b/docs/co.yello.db.batchlight/-batch-binder/bind-long.html new file mode 100644 index 0000000..42a450a --- /dev/null +++ b/docs/co.yello.db.batchlight/-batch-binder/bind-long.html @@ -0,0 +1,18 @@ + + + +BatchBinder.bindLong - batchlight + + + +batchlight / co.yello.db.batchlight / BatchBinder / bindLong
+
+

bindLong

+ +fun bindLong(long: Long): Unit +

Binds a value of type Long to the Binder and increments the position.

+

Parameters

+

+long - a Long to be bound to the Binder

+ + diff --git a/docs/co.yello.db.batchlight/-batch-binder/bind-null.html b/docs/co.yello.db.batchlight/-batch-binder/bind-null.html new file mode 100644 index 0000000..0da1c9e --- /dev/null +++ b/docs/co.yello.db.batchlight/-batch-binder/bind-null.html @@ -0,0 +1,15 @@ + + + +BatchBinder.bindNull - batchlight + + + +batchlight / co.yello.db.batchlight / BatchBinder / bindNull
+
+

bindNull

+ +fun bindNull(): Unit +

Binds a null value to the Binder and increments the bind position.

+ + diff --git a/docs/co.yello.db.batchlight/-batch-binder/bind-string.html b/docs/co.yello.db.batchlight/-batch-binder/bind-string.html new file mode 100644 index 0000000..3f93002 --- /dev/null +++ b/docs/co.yello.db.batchlight/-batch-binder/bind-string.html @@ -0,0 +1,18 @@ + + + +BatchBinder.bindString - batchlight + + + +batchlight / co.yello.db.batchlight / BatchBinder / bindString
+
+

bindString

+ +fun bindString(string: String): Unit +

Binds a value of type String to the Binder and increments the position.

+

Parameters

+

+string - a String to be bound to the Binder.

+ + diff --git a/docs/co.yello.db.batchlight/-batch-binder/current-bind-index.html b/docs/co.yello.db.batchlight/-batch-binder/current-bind-index.html new file mode 100644 index 0000000..90ac9dd --- /dev/null +++ b/docs/co.yello.db.batchlight/-batch-binder/current-bind-index.html @@ -0,0 +1,15 @@ + + + +BatchBinder.currentBindIndex - batchlight + + + +batchlight / co.yello.db.batchlight / BatchBinder / currentBindIndex
+
+

currentBindIndex

+ +var currentBindIndex: Int +

Marks the current binding position.

+ + diff --git a/docs/co.yello.db.batchlight/-batch-binder/index.html b/docs/co.yello.db.batchlight/-batch-binder/index.html new file mode 100644 index 0000000..b9efa71 --- /dev/null +++ b/docs/co.yello.db.batchlight/-batch-binder/index.html @@ -0,0 +1,98 @@ + + + +BatchBinder - batchlight + + + +batchlight / co.yello.db.batchlight / BatchBinder
+
+

BatchBinder

+class BatchBinder +

A wrapper around a Binder to make sure binds happen in sequential order.

+

Parameters

+

+binder - the specific Binder to bind values to.

+

+startBindIndex - the index to start binding to the binder.

+

Constructors

+ + + + + + + +
+

<init>

+
+BatchBinder(binder: Binder, startBindIndex: Int) +

Creates a BatchBinder with the given binder. The binder will be cleared on creation to ensure +no pre existing values will be bound unintentionally.

+
+

Properties

+ + + + + + + +
+

currentBindIndex

+
+var currentBindIndex: Int +

Marks the current binding position.

+
+

Functions

+ + + + + + + + + + + + + + + + + + + + + + + +
+

bindBlob

+
+fun bindBlob(blob: ByteArray): Unit +

Binds a ByteArray to the Binder and increments the position.

+
+

bindDouble

+
+fun bindDouble(double: Double): Unit +

Binds a value of type Double to the Binder and increments the position.

+
+

bindLong

+
+fun bindLong(long: Long): Unit +

Binds a value of type Long to the Binder and increments the position.

+
+

bindNull

+
+fun bindNull(): Unit +

Binds a null value to the Binder and increments the bind position.

+
+

bindString

+
+fun bindString(string: String): Unit +

Binds a value of type String to the Binder and increments the position.

+
+ + diff --git a/docs/co.yello.db.batchlight/-batch-statement/-init-.html b/docs/co.yello.db.batchlight/-batch-statement/-init-.html new file mode 100644 index 0000000..895ef18 --- /dev/null +++ b/docs/co.yello.db.batchlight/-batch-statement/-init-.html @@ -0,0 +1,19 @@ + + + +BatchStatement.<init> - batchlight + + + +batchlight / co.yello.db.batchlight / BatchStatement / <init>
+
+

<init>

+ +BatchStatement(binderConfig: BinderConfig) +

Drives the inserts into the database.

+

Parameters

+

+T - The type of the collection that will be inserted. This is used to pass into the bind function that is +passed into the execute function.

+ + diff --git a/docs/co.yello.db.batchlight/-batch-statement/execute.html b/docs/co.yello.db.batchlight/-batch-statement/execute.html new file mode 100644 index 0000000..e0588c7 --- /dev/null +++ b/docs/co.yello.db.batchlight/-batch-statement/execute.html @@ -0,0 +1,57 @@ + + + +BatchStatement.execute - batchlight + + + +batchlight / co.yello.db.batchlight / BatchStatement / execute
+
+

execute

+ +fun execute(itemsToInsert: Collection<T>, bindItemFunction: BatchBinder.(T) -> Unit): Unit +

Chunks the list into the maximum sized list that can be inserted at a time, then executes the insert +with the bindItemFunction passed into the class.

+

Parameters

+

+itemsToInsert - The collection of items of type T to batch insert into the database.

+

+bindItemFunction - +

A function that performs the binds for each field in the T passed to the function. +The function is an extension on BatchBinder so all bind functions can be accessed from within.

+ + +

When binding the order of the bind calls should match the structure of the table inserting.

+ + +

Example usage:

+ + +

A simple class would look something like

+ + +
data class BatchClass(val id: Int, val text: String, val num: Int)
+
+ +

The SQL Create statement for the corresponding table to that object would be:

+ + +
CREATE TABLE BatchClass (
+     id INTEGER,
+     text TEXT,
+     num INTEGER
+)
+
+ +

The Binder generated for this table will will have the params (?,?,?). To correctly bind the three values +to their correct ? in the prepared statement the function would be:

+ + +
{ batchObject - >
+     bindLong(batchObject.id)
+     bindString(batchObject.text)
+     bindLong(batchObject.num)
+}
+

+ + diff --git a/docs/co.yello.db.batchlight/-batch-statement/index.html b/docs/co.yello.db.batchlight/-batch-statement/index.html new file mode 100644 index 0000000..e8fce54 --- /dev/null +++ b/docs/co.yello.db.batchlight/-batch-statement/index.html @@ -0,0 +1,47 @@ + + + +BatchStatement - batchlight + + + +batchlight / co.yello.db.batchlight / BatchStatement
+
+

BatchStatement

+class BatchStatement<T> +

Drives the inserts into the database.

+

Parameters

+

+T - The type of the collection that will be inserted. This is used to pass into the bind function that is +passed into the execute function.

+

Constructors

+ + + + + + + +
+

<init>

+
+BatchStatement(binderConfig: BinderConfig) +

Drives the inserts into the database.

+
+

Functions

+ + + + + + + +
+

execute

+
+fun execute(itemsToInsert: Collection<T>, bindItemFunction: BatchBinder.(T) -> Unit): Unit +

Chunks the list into the maximum sized list that can be inserted at a time, then executes the insert +with the bindItemFunction passed into the class.

+
+ + diff --git a/docs/co.yello.db.batchlight/-binder-config/build-binder.html b/docs/co.yello.db.batchlight/-binder-config/build-binder.html new file mode 100644 index 0000000..898f83f --- /dev/null +++ b/docs/co.yello.db.batchlight/-binder-config/build-binder.html @@ -0,0 +1,20 @@ + + + +BinderConfig.buildBinder - batchlight + + + +batchlight / co.yello.db.batchlight / BinderConfig / buildBinder
+
+

buildBinder

+ +abstract fun buildBinder(insertCount: Int): Binder +

Creates a Binder that has the given number of bind locations.

+

Parameters

+

+insertCount - the number of fields that will be bound.

+

Return
+a Binder object that has insertCount number of fields

+ + diff --git a/docs/co.yello.db.batchlight/-binder-config/fields-per-item.html b/docs/co.yello.db.batchlight/-binder-config/fields-per-item.html new file mode 100644 index 0000000..c268004 --- /dev/null +++ b/docs/co.yello.db.batchlight/-binder-config/fields-per-item.html @@ -0,0 +1,17 @@ + + + +BinderConfig.fieldsPerItem - batchlight + + + +batchlight / co.yello.db.batchlight / BinderConfig / fieldsPerItem
+
+

fieldsPerItem

+ +abstract val fieldsPerItem: Int +

If an object has 4 fields and only 3 should be written to the database this value should be set to 3.

+

Return
+The number of fields to be inserted per item.

+ + diff --git a/docs/co.yello.db.batchlight/-binder-config/index.html b/docs/co.yello.db.batchlight/-binder-config/index.html new file mode 100644 index 0000000..53df231 --- /dev/null +++ b/docs/co.yello.db.batchlight/-binder-config/index.html @@ -0,0 +1,87 @@ + + + +BinderConfig - batchlight + + + +batchlight / co.yello.db.batchlight / BinderConfig
+
+

BinderConfig

+interface BinderConfig +

Holds all the configurations for a Binder.

+

Properties

+ + + + + + + + + + + + + + + + + + + + + + + +
+

fieldsPerItem

+
+abstract val fieldsPerItem: Int +

If an object has 4 fields and only 3 should be written to the database this value should be set to 3.

+
+

maxFields

+
+abstract val maxFields: Int
+

maxInsertBinder

+
+abstract val maxInsertBinder: Binder +

Holds a reference to the max insert binder. This will be the most common case so it should +be cached.

+
+

maxInsertSize

+
+abstract val maxInsertSize: Int
+

startIndex

+
+abstract val startIndex: Int
+

Functions

+ + + + + + + +
+

buildBinder

+
+abstract fun buildBinder(insertCount: Int): Binder +

Creates a Binder that has the given number of bind locations.

+
+

Inheritors

+ + + + + + + +
+

SQLiteBinderConfig

+
+class SQLiteBinderConfig : BinderConfig +

The BinderConfig for Android SQLite.

+
+ + diff --git a/docs/co.yello.db.batchlight/-binder-config/max-fields.html b/docs/co.yello.db.batchlight/-binder-config/max-fields.html new file mode 100644 index 0000000..02ce6de --- /dev/null +++ b/docs/co.yello.db.batchlight/-binder-config/max-fields.html @@ -0,0 +1,16 @@ + + + +BinderConfig.maxFields - batchlight + + + +batchlight / co.yello.db.batchlight / BinderConfig / maxFields
+
+

maxFields

+ +abstract val maxFields: Int +

Return
+The maximum number fields that can be bound at one time. For example this number is 999 for SQLite Android.

+ + diff --git a/docs/co.yello.db.batchlight/-binder-config/max-insert-binder.html b/docs/co.yello.db.batchlight/-binder-config/max-insert-binder.html new file mode 100644 index 0000000..40e6a03 --- /dev/null +++ b/docs/co.yello.db.batchlight/-binder-config/max-insert-binder.html @@ -0,0 +1,18 @@ + + + +BinderConfig.maxInsertBinder - batchlight + + + +batchlight / co.yello.db.batchlight / BinderConfig / maxInsertBinder
+
+

maxInsertBinder

+ +abstract val maxInsertBinder: Binder +

Holds a reference to the max insert binder. This will be the most common case so it should +be cached.

+

Return
+a Binder object that has maximum number of insert params available.

+ + diff --git a/docs/co.yello.db.batchlight/-binder-config/max-insert-size.html b/docs/co.yello.db.batchlight/-binder-config/max-insert-size.html new file mode 100644 index 0000000..ac67ebb --- /dev/null +++ b/docs/co.yello.db.batchlight/-binder-config/max-insert-size.html @@ -0,0 +1,16 @@ + + + +BinderConfig.maxInsertSize - batchlight + + + +batchlight / co.yello.db.batchlight / BinderConfig / maxInsertSize
+
+

maxInsertSize

+ +abstract val maxInsertSize: Int +

Return
+The max number of items that can be inserted at one time.

+ + diff --git a/docs/co.yello.db.batchlight/-binder-config/start-index.html b/docs/co.yello.db.batchlight/-binder-config/start-index.html new file mode 100644 index 0000000..e32046f --- /dev/null +++ b/docs/co.yello.db.batchlight/-binder-config/start-index.html @@ -0,0 +1,16 @@ + + + +BinderConfig.startIndex - batchlight + + + +batchlight / co.yello.db.batchlight / BinderConfig / startIndex
+
+

startIndex

+ +abstract val startIndex: Int +

Return
+The start index of where to bind begin binding.

+ + diff --git a/docs/co.yello.db.batchlight/-binder/bind-blob.html b/docs/co.yello.db.batchlight/-binder/bind-blob.html new file mode 100644 index 0000000..8edba08 --- /dev/null +++ b/docs/co.yello.db.batchlight/-binder/bind-blob.html @@ -0,0 +1,20 @@ + + + +Binder.bindBlob - batchlight + + + +batchlight / co.yello.db.batchlight / Binder / bindBlob
+
+

bindBlob

+ +abstract fun bindBlob(position: Int, blob: ByteArray): Unit +

Binds a ByteArray at the given position.

+

Parameters

+

+position - position to bind at.

+

+blob - a value of type ByteArray to bind.

+ + diff --git a/docs/co.yello.db.batchlight/-binder/bind-double.html b/docs/co.yello.db.batchlight/-binder/bind-double.html new file mode 100644 index 0000000..2411dff --- /dev/null +++ b/docs/co.yello.db.batchlight/-binder/bind-double.html @@ -0,0 +1,20 @@ + + + +Binder.bindDouble - batchlight + + + +batchlight / co.yello.db.batchlight / Binder / bindDouble
+
+

bindDouble

+ +abstract fun bindDouble(position: Int, double: Double): Unit +

Binds a Double at the given position.

+

Parameters

+

+position - position to bind at.

+

+double - a value of type Double to bind.

+ + diff --git a/docs/co.yello.db.batchlight/-binder/bind-long.html b/docs/co.yello.db.batchlight/-binder/bind-long.html new file mode 100644 index 0000000..774714d --- /dev/null +++ b/docs/co.yello.db.batchlight/-binder/bind-long.html @@ -0,0 +1,20 @@ + + + +Binder.bindLong - batchlight + + + +batchlight / co.yello.db.batchlight / Binder / bindLong
+
+

bindLong

+ +abstract fun bindLong(position: Int, long: Long): Unit +

Binds a Long at the given position.

+

Parameters

+

+position - position to bind at.

+

+long - a value of type Long to bind.

+ + diff --git a/docs/co.yello.db.batchlight/-binder/bind-null.html b/docs/co.yello.db.batchlight/-binder/bind-null.html new file mode 100644 index 0000000..bdecde2 --- /dev/null +++ b/docs/co.yello.db.batchlight/-binder/bind-null.html @@ -0,0 +1,18 @@ + + + +Binder.bindNull - batchlight + + + +batchlight / co.yello.db.batchlight / Binder / bindNull
+
+

bindNull

+ +abstract fun bindNull(position: Int): Unit +

Binds a null value at the given position.

+

Parameters

+

+position - position to bind at.

+ + diff --git a/docs/co.yello.db.batchlight/-binder/bind-string.html b/docs/co.yello.db.batchlight/-binder/bind-string.html new file mode 100644 index 0000000..e6bdde3 --- /dev/null +++ b/docs/co.yello.db.batchlight/-binder/bind-string.html @@ -0,0 +1,20 @@ + + + +Binder.bindString - batchlight + + + +batchlight / co.yello.db.batchlight / Binder / bindString
+
+

bindString

+ +abstract fun bindString(position: Int, string: String): Unit +

Binds a String at the given position.

+

Parameters

+

+position - position to bind at.

+

+string - a value of type String to bind.

+ + diff --git a/docs/co.yello.db.batchlight/-binder/clear.html b/docs/co.yello.db.batchlight/-binder/clear.html new file mode 100644 index 0000000..bf6036b --- /dev/null +++ b/docs/co.yello.db.batchlight/-binder/clear.html @@ -0,0 +1,15 @@ + + + +Binder.clear - batchlight + + + +batchlight / co.yello.db.batchlight / Binder / clear
+
+

clear

+ +abstract fun clear(): Unit +

Clears any bound values.

+ + diff --git a/docs/co.yello.db.batchlight/-binder/execute.html b/docs/co.yello.db.batchlight/-binder/execute.html new file mode 100644 index 0000000..6d5ec4a --- /dev/null +++ b/docs/co.yello.db.batchlight/-binder/execute.html @@ -0,0 +1,15 @@ + + + +Binder.execute - batchlight + + + +batchlight / co.yello.db.batchlight / Binder / execute
+
+

execute

+ +abstract fun execute(): Unit +

Executes with any bound values.

+ + diff --git a/docs/co.yello.db.batchlight/-binder/index.html b/docs/co.yello.db.batchlight/-binder/index.html new file mode 100644 index 0000000..2160b32 --- /dev/null +++ b/docs/co.yello.db.batchlight/-binder/index.html @@ -0,0 +1,96 @@ + + + +Binder - batchlight + + + +batchlight / co.yello.db.batchlight / Binder
+
+

Binder

+interface Binder +

Provides way to bind values to any DB or ORM.

+

Functions

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+

bindBlob

+
+abstract fun bindBlob(position: Int, blob: ByteArray): Unit +

Binds a ByteArray at the given position.

+
+

bindDouble

+
+abstract fun bindDouble(position: Int, double: Double): Unit +

Binds a Double at the given position.

+
+

bindLong

+
+abstract fun bindLong(position: Int, long: Long): Unit +

Binds a Long at the given position.

+
+

bindNull

+
+abstract fun bindNull(position: Int): Unit +

Binds a null value at the given position.

+
+

bindString

+
+abstract fun bindString(position: Int, string: String): Unit +

Binds a String at the given position.

+
+

clear

+
+abstract fun clear(): Unit +

Clears any bound values.

+
+

execute

+
+abstract fun execute(): Unit +

Executes with any bound values.

+
+

Inheritors

+ + + + + + + +
+

AndroidSQLiteBinder

+
+class AndroidSQLiteBinder : Binder +

The Binder implementation for SQLite on Android.

+
+ + diff --git a/docs/co.yello.db.batchlight/index.html b/docs/co.yello.db.batchlight/index.html new file mode 100644 index 0000000..a02c111 --- /dev/null +++ b/docs/co.yello.db.batchlight/index.html @@ -0,0 +1,76 @@ + + + +co.yello.db.batchlight - batchlight + + + +batchlight / co.yello.db.batchlight
+
+

Package co.yello.db.batchlight

+

Types

+ + + + + + + + + + + + + + + + + + + +
+

BatchBinder

+
+class BatchBinder +

A wrapper around a Binder to make sure binds happen in sequential order.

+
+

BatchStatement

+
+class BatchStatement<T> +

Drives the inserts into the database.

+
+

Binder

+
+interface Binder +

Provides way to bind values to any DB or ORM.

+
+

BinderConfig

+
+interface BinderConfig +

Holds all the configurations for a Binder.

+
+

Properties

+ + + + + + + + + + + +
+

sqlAndroidPreparedStatementStartIndex

+
+const val sqlAndroidPreparedStatementStartIndex: Int +

The start index of Android SQLite prepared statement binds.

+
+

sqlMaxBinds

+
+const val sqlMaxBinds: Int +

The maximum number of bind params that Android SQLite allows per prepared statement.

+
+ + diff --git a/docs/co.yello.db.batchlight/sql-android-prepared-statement-start-index.html b/docs/co.yello.db.batchlight/sql-android-prepared-statement-start-index.html new file mode 100644 index 0000000..e0542b9 --- /dev/null +++ b/docs/co.yello.db.batchlight/sql-android-prepared-statement-start-index.html @@ -0,0 +1,15 @@ + + + +sqlAndroidPreparedStatementStartIndex - batchlight + + + +batchlight / co.yello.db.batchlight / sqlAndroidPreparedStatementStartIndex
+
+

sqlAndroidPreparedStatementStartIndex

+ +const val sqlAndroidPreparedStatementStartIndex: Int +

The start index of Android SQLite prepared statement binds.

+ + diff --git a/docs/co.yello.db.batchlight/sql-max-binds.html b/docs/co.yello.db.batchlight/sql-max-binds.html new file mode 100644 index 0000000..03e3bb0 --- /dev/null +++ b/docs/co.yello.db.batchlight/sql-max-binds.html @@ -0,0 +1,16 @@ + + + +sqlMaxBinds - batchlight + + + +batchlight / co.yello.db.batchlight / sqlMaxBinds
+
+

sqlMaxBinds

+ +const val sqlMaxBinds: Int +

The maximum number of bind params that Android SQLite allows per prepared statement.

+

See SQLITE_MAX_VARIABLE_NUMBER in the SQLite docs at https://sqlite.org/limits.html

+ + diff --git a/docs/index-outline.html b/docs/index-outline.html new file mode 100644 index 0000000..830e13b --- /dev/null +++ b/docs/index-outline.html @@ -0,0 +1,285 @@ + + + +Module Contents + + + +batchlight
+ + + diff --git a/docs/index.html b/docs/index.html new file mode 100644 index 0000000..8a6f7ed --- /dev/null +++ b/docs/index.html @@ -0,0 +1,32 @@ + + + +batchlight + + + +batchlight
+
+

Packages

+ + + + + + + + + + + +
+

co.yello.db.batchlight

+
+
+

co.yello.db.batchlight.androidsqlite

+
+
+

Index

+All Types + + diff --git a/docs/package-list b/docs/package-list new file mode 100644 index 0000000..e2a41b1 --- /dev/null +++ b/docs/package-list @@ -0,0 +1,5 @@ +$dokka.format:html +$dokka.linkExtension:html + +co.yello.db.batchlight +co.yello.db.batchlight.androidsqlite diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..f65b6f7 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,19 @@ +# Project-wide Gradle settings. +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx1536m +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true +# Kotlin code style for this project: "official" or "obsolete": +kotlin.code.style=official +android.useAndroidX=true +android.enableJetifier=true +android.enableUnitTestBinaryResources=true +android.enableR8=true \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..f6b961f Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..07aff4a --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Thu Apr 18 11:50:46 CDT 2019 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..cccdd3d --- /dev/null +++ b/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..e95643d --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/sample/.gitignore b/sample/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/sample/.gitignore @@ -0,0 +1 @@ +/build diff --git a/sample/build.gradle b/sample/build.gradle new file mode 100644 index 0000000..1cb5cd3 --- /dev/null +++ b/sample/build.gradle @@ -0,0 +1,32 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' + +android { + compileSdkVersion 28 + defaultConfig { + applicationId "co.yello.db.batchinsertsample" + minSdkVersion 21 + targetSdkVersion 28 + versionCode 1 + versionName "1.0" + } + + buildTypes { + release { + minifyEnabled true + shrinkResources true + } + } +} + +dependencies { + implementation project(':batchlight') + implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation 'androidx.appcompat:appcompat:1.1.0-alpha04' + implementation 'androidx.constraintlayout:constraintlayout:1.1.3' + implementation 'com.google.android.material:material:1.1.0-alpha05' + + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.1.1' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.1.0' +} diff --git a/sample/src/main/AndroidManifest.xml b/sample/src/main/AndroidManifest.xml new file mode 100644 index 0000000..d20cdca --- /dev/null +++ b/sample/src/main/AndroidManifest.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/sample/src/main/java/co/yello/db/batchinsertsample/MainActivity.kt b/sample/src/main/java/co/yello/db/batchinsertsample/MainActivity.kt new file mode 100644 index 0000000..0697137 --- /dev/null +++ b/sample/src/main/java/co/yello/db/batchinsertsample/MainActivity.kt @@ -0,0 +1,149 @@ +package co.yello.db.batchinsertsample + +import androidx.appcompat.app.AppCompatActivity +import android.os.Bundle +import android.util.Log +import android.view.KeyEvent +import kotlinx.android.synthetic.main.activity_main.* +import kotlinx.coroutines.* +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.channels.ReceiveChannel +import kotlinx.coroutines.channels.produce +import kotlin.system.measureTimeMillis +import android.view.inputmethod.EditorInfo +import android.widget.TextView +import android.widget.TextView.OnEditorActionListener + + +class MainActivity : AppCompatActivity() { + + // A job that just tracks the database inserts. When the mainScopeJob is cancelled it will make the scope unusable + // so we need this independent job to be able to start and stop the inserts without stopping all future inserts. + private var dbInsertJob = Job() + // This job is a parent job for all jobs ran within the mainScope. When this is cancelled all children are also + // cancelled + private val mainScopeJob: Job = Job() + private val mainScope = CoroutineScope(mainScopeJob + Dispatchers.Main) + + private val databaseHelper: SQLiteHelper by lazy { + SQLiteHelper(this) + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_main) + + doInsertButton.setOnClickListener { + executeInserts() + } + + rowCount.setOnEditorActionListener(object : OnEditorActionListener { + override fun onEditorAction(v: TextView, actionId: Int, event: KeyEvent?): Boolean { + if (event?.keyCode == KeyEvent.KEYCODE_ENTER || actionId == EditorInfo.IME_ACTION_DONE) { + executeInserts() + } + return false + } + }) + } + + private fun executeInserts() { + generateAndRun(rowCount.text.toString().ifBlank { "0" }.toInt()) + } + + override fun onDestroy() { + super.onDestroy() + mainScopeJob.cancel() + } + + /** + * Helper function to generate a list of [MediumSizeObject]. + * + * @param count the number of objects to put in the list. + */ + private fun createMediumObjects(count: Int): List { + val text = getString(R.string.static_text) + val moreText = getString(R.string.more_static_text) + + return (1..count).map { + MediumSizeObject(it, text, moreText) + } + } + + private suspend fun insertItems(items: List) = withContext(Dispatchers.Main) { + Log.i(tag, "Starting individual insertion execution.") + val time = measureTimeMillis { + val channel = Channel(Channel.CONFLATED) + + launch { + databaseHelper.loopInsert(channel, items) + channel.close() + } + + val progressPipeline = calcProgress(channel, insertsPerOnePercent(items.size)) + + for (progress in progressPipeline) { + single_time.text = getString(R.string.progress_percentage, progress) + } + } + + val executionTime = time/1000.0 + single_time.text = getString(R.string.single_insert_Time, executionTime) + Log.i(tag, "Finished individual insertion execution in $executionTime seconds.") + } + + private suspend fun batchInsertItems(items: List) { + Log.i(tag, "Starting batch insertion execution.") + batch_time.text = getString(R.string.inserting) + val time = measureTimeMillis { + databaseHelper.batchInsert(items) + } + + val executionTime = time/1000.0 + batch_time.text = getString(R.string.batch_insert_time, executionTime) + Log.i(tag, "Finished batch insertion execution in $executionTime seconds.") + } + + private fun generateAndRun(size: Int) { + insertionCountText.text = resources.getQuantityString(R.plurals.inserting_medium_objects, size, size) + + batch_time.text = getString(R.string.waiting_to_start) + single_time.text = getString(R.string.waiting_to_start) + + // clears out potential existing transactions to restart the SQLite batch insertion job + Log.i(tag, "Cancel $dbInsertJob") + dbInsertJob.cancel() + + Log.i(tag, "Clearing Database") + databaseHelper.clearDb() + + dbInsertJob = mainScope.launch { + val list = createMediumObjects(size) + + insertItems(list) + batchInsertItems(list) + } + } + + private fun insertsPerOnePercent(totalCount: Int) = if (totalCount <= 100) { + 1 + } else { + totalCount / 100 + } + + companion object { + const val tag = "Batch Insert Demo" + } +} + +@ExperimentalCoroutinesApi +fun CoroutineScope.calcProgress( + insertCountChannel: ReceiveChannel, + moduloValue: Int +): ReceiveChannel = produce { + for (count in insertCountChannel) { + if (count.rem(moduloValue) == 0) { + send(count / moduloValue) + } + } +} diff --git a/sample/src/main/java/co/yello/db/batchinsertsample/MediumSizeObject.kt b/sample/src/main/java/co/yello/db/batchinsertsample/MediumSizeObject.kt new file mode 100644 index 0000000..afbdfd5 --- /dev/null +++ b/sample/src/main/java/co/yello/db/batchinsertsample/MediumSizeObject.kt @@ -0,0 +1,7 @@ +package co.yello.db.batchinsertsample + +data class MediumSizeObject( + val id: Int, + val text: String, + val moreText: String +) \ No newline at end of file diff --git a/sample/src/main/java/co/yello/db/batchinsertsample/SQLiteHelper.kt b/sample/src/main/java/co/yello/db/batchinsertsample/SQLiteHelper.kt new file mode 100644 index 0000000..5809b98 --- /dev/null +++ b/sample/src/main/java/co/yello/db/batchinsertsample/SQLiteHelper.kt @@ -0,0 +1,130 @@ +package co.yello.db.batchinsertsample + +import android.content.Context +import android.database.sqlite.SQLiteDatabase +import android.database.sqlite.SQLiteOpenHelper +import co.yello.db.batchlight.BatchStatement +import co.yello.db.batchlight.androidsqlite.SQLiteBinderConfig +import kotlinx.coroutines.* +import kotlinx.coroutines.channels.SendChannel + +class SQLiteHelper(context: Context) : SQLiteOpenHelper( + context, + databaseName, + null, + version +) { + override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) { /** no upgrades for now */ } + + override fun onCreate(db: SQLiteDatabase) { + db.execSQL(createBatchTableStatement) + db.execSQL(createSingleTableStatement) + } + + /** + * Uses Yello batch insert library to insert data into the database + */ + suspend fun batchInsert(list: List) = withContext(Dispatchers.IO) { + val binderConfig = SQLiteBinderConfig.getInsertConfig( + db = writableDatabase, + tableName = batchTableName, + columnCount = 3 + ) + val statement = BatchStatement(binderConfig) + + writableDatabase.transaction { + statement.execute(list) { mediumSizeObject -> + bindLong(mediumSizeObject.id.toLong()) + bindString(mediumSizeObject.text) + bindString(mediumSizeObject.moreText) + } + } + } + + /** + * Loops over collection of items and executes a insert for each item in the list + */ + suspend fun loopInsert(progressChannel: SendChannel, list: List) = withContext(Dispatchers.IO) { + val statement = writableDatabase.compileStatement(individualInsertStatement) + + writableDatabase.transaction { + for ((index, mediumSizeObject) in list.withIndex()) { + if (!isActive) { + System.out.println("Cancelling this transaction") + throw CancellationException() + } + + progressChannel.send(index) + + with(statement) { + clearBindings() + bindLong(1, mediumSizeObject.id.toLong()) + bindString(2, mediumSizeObject.text) + bindString(3, mediumSizeObject.moreText) + executeInsert() + } + } + } + } + + fun clearDb() { + writableDatabase.transaction { + delete(singleTableName, "", null) + delete(batchTableName, "", null) + } + } + + companion object { + const val singleTableName = "SingleInsertTable" + const val batchTableName = "BatchInsertTable" + private const val id = "id" + private const val text = "text" + private const val moreText = "moreText" + + const val databaseName = "batch_insert_demo.db" + + const val version = 1 + + val createSingleTableStatement = """ + CREATE TABLE $singleTableName( + $id INTEGER, + $text TEXT, + $moreText TEXT + )""".trimIndent() + + val createBatchTableStatement = """ + CREATE TABLE $batchTableName( + $id INTEGER, + $text TEXT, + $moreText TEXT + )""".trimIndent() + + val individualInsertStatement = """ + INSERT INTO $singleTableName VALUES + (?, ?, ?) + """.trimIndent() + } +} + +/** + * Run [body] in a transaction marking it as successful if it completes without exception. + * + * @param exclusive Run in `EXCLUSIVE` mode when true, `IMMEDIATE` mode otherwise. + */ +inline fun SQLiteDatabase.transaction( + exclusive: Boolean = true, + body: SQLiteDatabase.() -> T +): T { + if (exclusive) { + beginTransaction() + } else { + beginTransactionNonExclusive() + } + try { + val result = body() + setTransactionSuccessful() + return result + } finally { + endTransaction() + } +} diff --git a/sample/src/main/res/drawable-v24/ic_launcher_foreground.xml b/sample/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..6348baa --- /dev/null +++ b/sample/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + diff --git a/sample/src/main/res/drawable/ic_launcher_background.xml b/sample/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..a0ad202 --- /dev/null +++ b/sample/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sample/src/main/res/layout/activity_main.xml b/sample/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..1fbec75 --- /dev/null +++ b/sample/src/main/res/layout/activity_main.xml @@ -0,0 +1,61 @@ + + + + + + +